SigNoz lets you jump from a trace directly to the logs that fired during it, and from a log entry back to the trace that caused it. This works because OpenTelemetry SDKs inject trace_id and span_id into every log emitted inside an active span. Without those fields in your logs, trace-to-log navigation returns no results.
How Correlation Works
SigNoz links logs to traces through two fields: trace_id and span_id. A log emitted outside any active span carries neither:
{
"timestamp": "2024-01-15T10:30:00Z",
"severity": "ERROR",
"body": "Payment failed",
"service.name": "payment-service"
}
A log emitted inside an active span carries both. SigNoz uses them to connect that log to the exact span:
{
"timestamp": "2024-01-15T10:30:00Z",
"severity": "ERROR",
"body": "Payment failed",
"trace_id": "5b8aa5a2d2c872e8321cf37308d69df2",
"span_id": "051581bf3cb55c13",
"trace_flags": "01",
"service.name": "payment-service"
}
The trace_id matches the trace in SigNoz. The span_id identifies which span produced the log.
Automatic Correlation with OpenTelemetry SDK
OpenTelemetry SDKs inject trace_id and span_id into every log emitted during an active span. The setup varies by language.
Install the logging instrumentation package and call .instrument() before your app logs anything:
pip install opentelemetry-instrumentation-logging
from opentelemetry.instrumentation.logging import LoggingInstrumentor
LoggingInstrumentor().instrument(set_logging_format=True)
Calling it patches Python's standard logging module. Logs emitted inside spans carry trace_id and span_id from that point on.
Using the Java agent (recommended)
Attach the agent at startup. It injects trace_id, span_id, and trace_flags into the MDC for Logback and Log4j2:
java -javaagent:opentelemetry-javaagent.jar -jar myapp.jar
Add those keys to your encoder pattern to emit them:
%d{HH:mm:ss.SSS} trace_id=%X{trace_id} span_id=%X{span_id} trace_flags=%X{trace_flags} %msg%n
Using Logback without the agent
Add the dependency:
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-logback-mdc-1.0</artifactId>
<version>2.27.0-alpha</version> <!-- check Maven Central for the latest version -->
</dependency>
Wrap your appender in logback.xml:
<appender name="OTEL"
class="io.opentelemetry.instrumentation.logback.mdc.v1_0.OpenTelemetryAppender">
<appender-ref ref="CONSOLE"/>
</appender>
<root level="INFO">
<appender-ref ref="OTEL"/>
</root>
Add the MDC keys to your encoder pattern:
%d{HH:mm:ss.SSS} trace_id=%X{trace_id} span_id=%X{span_id} trace_flags=%X{trace_flags} %msg%n
For Log4j2, use opentelemetry-log4j-context-data-2.17-autoconfigure instead.
Install the instrumentation package for your logger (Pino, Winston, or Bunyan), then register it in your SDK setup:
npm install @opentelemetry/instrumentation-winston
# or @opentelemetry/instrumentation-pino
# or @opentelemetry/instrumentation-bunyan
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { WinstonInstrumentation } = require('@opentelemetry/instrumentation-winston');
const sdk = new NodeSDK({
instrumentations: [new WinstonInstrumentation()],
});
sdk.start();
Each log entry gets trace_id, span_id, and trace_flags injected when a span is active.
Go has no automatic context propagation for logs. Set up a logger bridge and pass context.Context on every log call. The bridge extracts trace_id and span_id from the active span in that context.
Initialize your LoggerProvider and register it globally before calling NewLogger. Skip this and nothing exports. go.opentelemetry.io/otel/log/global is experimental; its import path may change when the Go log API stabilizes.
import (
"go.opentelemetry.io/otel/log/global"
sdklog "go.opentelemetry.io/otel/sdk/log"
)
provider := sdklog.NewLoggerProvider(/* your exporter + resource */)
global.SetLoggerProvider(provider)
Create the bridge logger and pass ctx on every log call:
go get go.opentelemetry.io/contrib/bridges/otelslog
import "go.opentelemetry.io/contrib/bridges/otelslog"
logger := otelslog.NewLogger("my-service")
// ctx must carry an active span
logger.InfoContext(ctx, "payment processed", "order_id", "abc123")
Other bridges: otelzap for Zap, otellogrus for Logrus, otellogr for logr.
See the instrumentation docs for full per-language setup.
Manual Correlation for File-based and Kubernetes Logs
If logs go to files that a collector reads, or Kubernetes agents collect them from stdout/stderr, use SigNoz Log Pipelines to extract trace context. See the setup guides for FluentBit and FluentD.
Use this path when:
- Your application writes logs to files read by a collector
- Kubernetes agents collect logs and they reach SigNoz without OTel SDK injection
- Logs carry trace IDs in the message body or non-standard attributes
Setting Up Log Pipelines
Follow this guide:
Parse Trace Information for your Logs
It covers:
- Creating a log pipeline in SigNoz
- Using the Trace Parser processor to extract
trace_idandspan_id - Configuring regex or JSON parsers when needed
- Testing and deploying your pipeline
Verifying Correlation
- Generate traffic in your application
- Open Traces in SigNoz and click on any trace
- Click "Go to related logs"
- Check that
trace_idandspan_idfields appear in the matching log entries

In Logs Explorer, filter by trace_id to find all logs from a specific trace.
Troubleshooting
"Go to related logs" shows no results
Your logs reach SigNoz but lack trace_id. You haven't initialized SDK logging instrumentation, or you called it after creating your loggers. Move .instrument() (Python) or sdk.start() (Node.js) before your first log statement.
trace_id in logs is 00000000000000000000000000000000
This log fired outside an active span. Move logging calls inside instrumented request handlers, not startup code or background goroutines without span context.
Logs have a trace_id but the trace doesn't appear in SigNoz
Sampling dropped the trace. The log exported but the trace did not. Lower your sampling rate or switch to tail-based sampling so SigNoz receives traces with errors.
Go: logs emit but trace_id stays empty
The bridge reads context from the ctx argument, not a global. Every log call must pass the ctx that holds the active span.
Best Practices
- Prefer OpenTelemetry SDKs over manual pipeline parsing. Auto-injection needs no collector config.
- Use structured logging (JSON) so pipelines can extract
trace_idandspan_id - Propagate trace context across all services so logs from downstream calls link to the same root trace
- Add service-level attributes (
service.name,host.name) to filter correlated logs when debugging multi-service traces
Get Help
If you need help with the steps in this topic, please reach out to us on SigNoz Community Slack.
If you are a SigNoz Cloud user, please use in product chat support located at the bottom right corner of your SigNoz instance or contact us at cloud-support@signoz.io.