# Logging bridges: send your existing logger's lines as OpenTelemetry logs

> Pino, Bunyan, Winston, Python logging, slog, Logback and Deno's console as log records with severity, attributes and the request's trace id.

A log line becomes useful next to a trace when it is an OpenTelemetry **log record**: a severity, a body, structured attributes, and the trace id and span id of the request it was written in. A *bridge* turns your logger's calls into those records while your normal output (stdout, files) keeps working. You keep your logger; you do not log through an OpenTelemetry API.

## By runtime

| Runtime | Logger | How it is bridged | Run by Belvador |
|---|---|---|---|
| Node.js | Pino | Automatic with the auto-instrumentations | Yes |
| Node.js | Bunyan | Automatic with the auto-instrumentations | No |
| Node.js | Winston | Install `@opentelemetry/winston-transport` next to Winston | No |
| Node.js | `console.*` | Not captured — use a logger | — |
| Python | `logging` (and libraries on it) | `OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true` with `opentelemetry-instrument` | Yes |
| Go | `log/slog` | `otelslog` handler; call the `…Context` functions with the request's context | Yes |
| Java | `java.util.logging` | Automatic with the Java agent | Yes |
| Java | Logback, Log4j 2 | Automatic with the Java agent | No |
| Deno | `console.*` | Automatic with `OTEL_DENO=true` | Yes |
| Any | Log files | Collector `filelog` receiver | No |

"Run by Belvador" means the certification run used that logger and checked severity and trace linking; the others use the same mechanism of the same packages but were not part of the run. Setup lines for each are on the runtime pages: [Node.js](https://belvador.dev/en/docs/otel/node#logs), [Python](https://belvador.dev/en/docs/otel/python#logs), [Go](https://belvador.dev/en/docs/otel/go), [Java](https://belvador.dev/en/docs/otel/java#logs), [Deno](https://belvador.dev/en/docs/otel/deno#logs), [Collector](https://belvador.dev/en/docs/otel/collector#log-files).

## What makes a log linked to its trace

The bridge copies the *active* span's ids into the record. A record therefore carries a trace id only when it is written while a span is active in that code path:

- **Inside a request handler** — the incoming request's server span is active, so lines written there are linked.
- **In background work** (timers, queue consumers, startup) there is often no active span, so lines have no trace id. That is normal; they are still stored and searchable.
- **Go** has no implicit context: pass the request's `ctx` (`logger.InfoContext(ctx, …)`).
- **Text that mentions a trace id** is not a link. A logger that writes `trace_id=…` into the message string leaves the record's own trace fields empty.

In Belvador, a linked record shows its trace on the **Logs** page and appears on the trace page's **Logs** tab and on an issue's **Logs around this error**.

## Severity

Bridges map each logger's levels to OpenTelemetry severities (`TRACE` … `FATAL`); Belvador's severity filter uses those. Custom levels map to the nearest standard one, so check the result once if your logger defines its own.

## Structured fields

Fields you pass as an object (`logger.info({ orderId }, "paid")`, `logging.info("paid", extra={"order_id": …})`, `slog.Int("order_id", …)`) become attributes you can filter on, and privacy rules see their names — see [Privacy](https://belvador.dev/en/docs/otel/privacy#log-lines). Values interpolated into the message text are only text.

## How much to send

The bridge sends what your logger's level lets through. Keep production at `info` or `warn`; logs are not reduced by trace sampling ([Sampling and cost](https://belvador.dev/en/docs/otel/sampling-and-cost)).
