Skip to content

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.

Markdown
On this page

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#

RuntimeLoggerHow it is bridgedRun by Belvador
Node.jsPinoAutomatic with the auto-instrumentationsYes
Node.jsBunyanAutomatic with the auto-instrumentationsNo
Node.jsWinstonInstall @opentelemetry/winston-transport next to WinstonNo
Node.jsconsole.*Not captured — use a logger—
Pythonlogging (and libraries on it)OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true with opentelemetry-instrumentYes
Golog/slogotelslog handler; call the …Context functions with the request's contextYes
Javajava.util.loggingAutomatic with the Java agentYes
JavaLogback, Log4j 2Automatic with the Java agentNo
Denoconsole.*Automatic with OTEL_DENO=trueYes
AnyLog filesCollector filelog receiverNo

"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, Python, Go, Java, Deno, Collector.

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. 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).

Spotted something wrong or missing? Tell us: hello@belvador.dev