OpenTelemetry Logs Bridge lets you attach structured telemetry data to logs that were never designed to be structured.
Let’s see how this looks with a simple Java application using slf4j and logback. Imagine you have an existing application that logs messages like this:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyService {
private static final Logger logger = LoggerFactory.getLogger(MyService.class);
public void processRequest(String userId) {
logger.info("Processing request for user: {}", userId);
// ... actual processing ...
logger.warn("Potential issue detected for user: {}", userId);
}
public static void main(String[] args) {
new MyService().processRequest("user123");
}
}
This is great for human readability, but machines can’t easily query "all requests for user123". The OpenTelemetry Logs Bridge allows us to inject structured attributes into these existing log statements without modifying the application’s core logging logic.
The key is the opentelemetry-api and a specific bridge implementation (like opentelemetry-sdk-logs). You configure your logging framework to use a custom appender or layout that intercepts log events. This appender then uses the OpenTelemetry SDK to create a LogRecord and attach attributes from the current OpenTelemetry SpanContext.
Here’s a simplified conceptual flow:
- Log Event Occurs:
logger.info("Processing request for user: {}", userId); - Log Framework Captures:
slf4j/logbackprocesses the log event. - Bridge Appender Intercepts: A custom appender is triggered.
- Span Context Retrieval: The appender checks for an active
SpanContext. - Attribute Injection: If a
SpanContextexists, attributes liketrace_id,span_id, and any customSpanAttributesare extracted. - LogRecord Creation: A new
LogRecordis created with the log message, level, and the extracted attributes. - Export: The
LogRecordis sent to the OpenTelemetry Collector for processing and export to a backend.
The problem this solves is bridging the gap between legacy, unstructured logging and modern, structured observability. You don’t need to rewrite your entire application to add trace IDs or custom metadata to your logs. The bridge does it for you by leveraging the active trace context.
Internally, the bridge works by:
- SpanContext Propagation: It relies on the trace context being propagated (e.g., via W3C Trace Context headers in HTTP requests).
- ThreadLocal Storage: The OpenTelemetry SDK typically stores the active
SpanContextinThreadLocalstorage. - Custom Appender Logic: Your logging framework’s appender/layout is configured to access this
ThreadLocalcontext. - LogRecord Builder: The bridge uses the
LoggerProviderfrom the OpenTelemetry SDK to buildLogRecordobjects, populating them with data from theSpanContext.
Consider this logback.xml configuration snippet for a bridge:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="OTEL_LOG" class="io.opentelemetry.sdk.logs.export.LogExporter$LogRecordAppender">
<exporter>
<class>io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor</class>
<properties>
<property name="exporter">
<class>io.opentelemetry.exporter.logging.LogRecordStringFormatExporter</class>
</property>
</properties>
</exporter>
</appender>
<root level="info">
<appender-ref ref="STDOUT" />
<appender-ref ref="OTEL_LOG" />
</root>
</configuration>
This LogRecordAppender is the crucial piece. It’s designed to pick up the active SpanContext and associate it with the log event. The LogRecordStringFormatExporter is for demonstration; in production, you’d use an exporter like OTLP.
When a log message is generated, the LogRecordAppender checks if there’s an active span. If there is, it creates an OpenTelemetry LogRecord. This LogRecord will automatically contain the trace_id and span_id of the currently active span. Any custom attributes you added to the span (e.g., userId) are also typically propagated and can be found within the LogRecord’s attributes.
The most surprising thing is how seamlessly this integrates without requiring explicit log message formatting changes. You can have a mix of plain-text logs and structured logs, and the bridge ensures that all logs originating from a traced request automatically inherit trace context. This means your existing, human-readable logs become queryable by trace ID and span ID, enabling you to correlate them with traces in your observability backend.
The next step is understanding how to configure the OpenTelemetry Collector to receive and process these bridged logs.