When debugging distributed systems, you often start from a trace and need the corresponding log entries, or the other way around. By injecting OpenTelemetry trace IDs and span IDs into your application logs, you can jump from a trace span to the exact log lines produced during that operation. This speeds up fault diagnosis and performance analysis.
This topic covers trace-log correlation for Java, Go, and Python. Java and Python support automatic injection through OpenTelemetry auto-instrumentation. Go requires manual injection.
Prerequisites
Before you begin, make sure that:
Your application is monitored by ARMS with OpenTelemetry tracing enabled
The OpenTelemetry Java agent, Go SDK, or Python SDK is installed, depending on your language
How it works
OpenTelemetry uses Mapped Diagnostic Context (MDC) to inject trace_id and span_id into each log entry produced within an active span. With auto-instrumentation (Java, Python), the agent handles injection automatically. Without auto-instrumentation (Go), extract these values from the span context and attach them to log fields manually.
After trace context appears in your logs, search by trace ID in Simple Log Service to locate every log entry for a specific request.
Java
Supported logging frameworks
| Logging framework | Auto-instrumentation support | Dependency for manual instrumentation |
|---|---|---|
| Log4j 1 | 1.2+ | N/A |
| Log4j 2 | 2.7+ | opentelemetry-log4j-context-data-2.17-autoconfigure |
| Logback | 1.0+ | opentelemetry-logback-mdc-1.0 |
For details on how MDC auto-instrumentation works, see Logger MDC auto-instrumentation.
Configure Log4j 2
Add the OpenTelemetry Log4j 2 dependency to your
pom.xml. ReplaceOPENTELEMETRY_VERSIONwith the latest version.<dependencies> <dependency> <groupId>io.opentelemetry.instrumentation</groupId> <artifactId>opentelemetry-log4j-context-data-2.17-autoconfigure</artifactId> <version>OPENTELEMETRY_VERSION</version> <scope>runtime</scope> </dependency> </dependencies>Add
%X{trace_id}and%X{span_id}to the pattern inlog4j2.xml.<?xml version="1.0" encoding="UTF-8"?> <Configuration> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} trace_id=%X{trace_id} span_id=%X{span_id} trace_flags=%X{trace_flags} - %msg%n"/> </Console> </Appenders> <Loggers> <Root> <AppenderRef ref="Console" level="All"/> </Root> </Loggers> </Configuration>
Configure Logback
Add the OpenTelemetry Logback dependency to your
pom.xml. ReplaceOPENTELEMETRY_VERSIONwith the latest version.<dependencies> <dependency> <groupId>io.opentelemetry.instrumentation</groupId> <artifactId>opentelemetry-logback-mdc-1.0</artifactId> <version>OPENTELEMETRY_VERSION</version> </dependency> </dependencies>Update
logback.xmlto include%X{trace_id}and%X{span_id}in the pattern, and wrap your appender withOpenTelemetryAppender.<?xml version="1.0" encoding="UTF-8"?> <configuration> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{HH:mm:ss.SSS} trace_id=%X{trace_id} span_id=%X{span_id} trace_flags=%X{trace_flags} %msg%n</pattern> </encoder> </appender> <!-- Wrap your logging appender with OpenTelemetryAppender --> <appender name="OTEL" class="io.opentelemetry.instrumentation.logback.mdc.v1_0.OpenTelemetryAppender"> <appender-ref ref="CONSOLE"/> </appender> <!-- Use the wrapped "OTEL" appender instead of the original "CONSOLE" one --> <root level="INFO"> <appender-ref ref="OTEL"/> </root> </configuration>
Configure Logback in Spring Boot
Spring Boot uses Logback by default. Instead of editing XML, add a single line to application.properties:
logging.pattern.level=trace_id=%mdc{trace_id} span_id=%mdc{span_id} %5pExample output:
2024-06-26 10:56:31.200 trace_id=8f7ebd8a73f9a8f50e6a00a87a20952a span_id=1b08f18b8858bb9a INFO 53724 --- [nio-8081-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2024-06-26 10:56:31.201 trace_id=8f7ebd8a73f9a8f50e6a00a87a20952a span_id=1b08f18b8858bb9a INFO 53724 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2024-06-26 10:56:31.209 trace_id=8f7ebd8a73f9a8f50e6a00a87a20952a span_id=1b08f18b8858bb9a INFO 53724 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 8 ms
2024-06-26 10:56:31.296 trace_id=8f7ebd8a73f9a8f50e6a00a87a20952a span_id=5743699405074f4e INFO 53724 --- [nio-8081-exec-1] com.example.httpserver.ot.OTServer : hello worldGo
OpenTelemetry in Go does not support automatic trace context injection into logs. Extract trace IDs and span IDs from the span context manually.
Configure logrus
Import the required packages.
import ( oteltrace "go.opentelemetry.io/otel/trace" "github.com/sirupsen/logrus" )Create a helper function that extracts trace IDs and span IDs from a span and returns them as logrus fields.
func LogrusFields(span oteltrace.Span) logrus.Fields { return logrus.Fields{ "trace_id": span.SpanContext().TraceID().String(), "span_id": span.SpanContext().SpanID().String(), } }Use the helper function after creating a span to log with trace context.
_, span := tracer.Start(ctx, "spanName") defer span.End() logEntry := logrus.WithFields(LogrusFields(span)) logEntry.Info("This is an info message with trace and span ID")
Python
OpenTelemetry in Python supports automatic trace context injection into logs. For details, see OpenTelemetry Logging Instrumentation.
Configure the logging module
Enable automatic injection by setting the following environment variable:
export OTEL_PYTHON_LOG_CORRELATION=trueAdd
%(otelTraceID)sand%(otelSpanID)sto your log format. OpenTelemetry replaces these placeholders with the actual trace ID and span ID at runtime.Log format example:
formatter = logging.Formatter( '%(asctime)s %(levelname)s [%(name)s] [%(filename)s:%(lineno)d] [trace_id=%(otelTraceID)s span_id=%(otelSpanID)s resource.service.name=%(otelServiceName)s] - %(message)s' )Example output:
2024-06-25 10:00:55,494 INFO [app] [test.py:80] [trace_id=70561de6a164ac991bfff2281b7f**** span_id=4b7f3c798460**** resource.service.name=ot-python-demo] - hello world
End-to-end example with Flask
This example walks through a complete setup: create a Flask app, instrument it with OpenTelemetry, and verify that trace IDs appear in the logs.
Prerequisites:
Python and pip installed (this example uses Python 3.12.4 and pip 24.1.1)
Step 1: Install dependencies
Install Flask:
pip3 install flaskInstall the OpenTelemetry distribution and auto-instrumentation libraries:
pip3 install opentelemetry-distro \
opentelemetry-exporter-otlp
opentelemetry-bootstrap -a installStep 2: Create the application
Save the following code as test.py:
import logging
from logging.handlers import RotatingFileHandler
from flask import Flask
app = Flask(__name__)
# Create a formatter that includes OpenTelemetry trace context
formatter = logging.Formatter(
'%(asctime)s %(levelname)s [%(name)s] [%(filename)s:%(lineno)d] [trace_id=%(otelTraceID)s span_id=%(otelSpanID)s resource.service.name=%(otelServiceName)s] - %(message)s'
)
# Write logs to a file with rotation
# Replace <log-file-path> with the actual path, for example: /var/log/app.log
file_handler = RotatingFileHandler('<log-file-path>', maxBytes=10000, backupCount=1)
file_handler.setLevel(logging.INFO)
file_handler.setFormatter(formatter)
app.logger.addHandler(file_handler)
@app.route('/')
def hello_world():
app.logger.info('Hello World!!!')
return 'Hello, World!'
if __name__ == '__main__':
app.run()Step 3: Run the application with OpenTelemetry instrumentation
Set the environment variables and start the app with the OpenTelemetry auto-instrumentation wrapper:
export OTEL_PYTHON_LOG_CORRELATION=true
export OTEL_SERVICE_NAME=ot-python-demo
export OTEL_TRACES_EXPORTER=otlp
export OTEL_METRICS_EXPORTER=none
# Replace test.py with your actual filename if different
opentelemetry-instrument python3 test.pyIf the app starts successfully, output similar to the following appears:
* Running on http://127.0.**.**:5000Step 4: Verify the log output
Send a request to the app and check the log file. Each entry now includes the trace ID and span ID:
2024-06-25 10:00:55,494 INFO [app] [test.py:80] [trace_id=70561de6a164ac991bfff2281b7f**** span_id=4b7f3c798460**** resource.service.name=ot-python-demo] - hello worldWhat's next
Associate logs with traces -- Navigate from log entries to distributed traces in the ARMS console.
Associate application logs with trace IDs in Simple Log Service -- Set up bidirectional correlation between ARMS traces and Simple Log Service logs.