OpenTelemetry Logback logging [Java]
OpenTelemetry Logback integrates the Logback logging framework with OpenTelemetry. This integration provides two complementary capabilities for correlating your Java logs with distributed traces.
Two Workflows for Log Instrumentation
OpenTelemetry provides two workflows for consuming Logback instrumentation:
Direct to Collector
Logs are emitted directly from your application to a collector using OTLP via the Logback Appender. This approach:
- Is simple to set up with no additional log forwarding components
- Allows emitting structured logs conforming to the OpenTelemetry log data model
- May add overhead for queuing and exporting logs over the network
Use this when: You want a simple setup and your application can handle the export overhead.
Via File or Stdout
Logs are written to files or stdout, then collected by another component (e.g., FluentBit, OpenTelemetry Collector filelog receiver) via the MDC context injection. This approach:
- Has minimal application overhead
- Requires parsing logs to extract structured data
- Needs trace context injected into log output for correlation
Use this when: You have existing log collection infrastructure or need minimal application overhead.
What is OpenTelemetry?
OpenTelemetry is an open-source observability framework that aims to standardize and simplify the collection, processing, and export of telemetry data from applications and systems.
OpenTelemetry supports multiple programming languages and platforms, making it suitable for a wide range of applications and environments.
OpenTelemetry enables developers to instrument their code and collect telemetry data, which can then be exported to various OpenTelemetry backends or observability platforms for analysis and visualization.
Logback Appender (Direct to Collector)
The OpenTelemetry Logback appender forwards Logback log events to the OpenTelemetry Log SDK, allowing logs to be exported alongside traces and metrics.
Installation
Add the dependency to your project:
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-logback-appender-1.0</artifactId>
<version>2.11.0-alpha</version>
</dependency>
You also need the OpenTelemetry SDK and OTLP exporter:
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
<version>1.45.0</version>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
<version>1.45.0</version>
</dependency>
Configuration
Add the OpenTelemetry appender to your logback.xml or logback-spring.xml:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="OpenTelemetry"
class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender">
<captureExperimentalAttributes>true</captureExperimentalAttributes>
<captureCodeAttributes>true</captureCodeAttributes>
<captureMarkerAttribute>true</captureMarkerAttribute>
<captureKeyValuePairAttributes>true</captureKeyValuePairAttributes>
<captureLoggerContext>true</captureLoggerContext>
<captureMdcAttributes>*</captureMdcAttributes>
</appender>
<root level="INFO">
<appender-ref ref="console"/>
<appender-ref ref="OpenTelemetry"/>
</root>
</configuration>
SDK Initialization
Initialize the OpenTelemetry SDK and install the appender in your application startup:
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter;
import io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.logs.SdkLoggerProvider;
import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.semconv.ResourceAttributes;
public class Application {
public static void main(String[] args) {
// Configure OTLP exporter
OtlpGrpcLogRecordExporter logExporter = OtlpGrpcLogRecordExporter.builder()
.setEndpoint("https://api.uptrace.dev:4317")
.addHeader("uptrace-dsn", System.getenv("UPTRACE_DSN"))
.build();
// Configure resource
Resource resource = Resource.getDefault().toBuilder()
.put(ResourceAttributes.SERVICE_NAME, "my-java-service")
.put(ResourceAttributes.SERVICE_VERSION, "1.0.0")
.build();
// Build logger provider with batch processor
SdkLoggerProvider loggerProvider = SdkLoggerProvider.builder()
.setResource(resource)
.addLogRecordProcessor(BatchLogRecordProcessor.builder(logExporter).build())
.build();
// Build OpenTelemetry SDK
OpenTelemetrySdk openTelemetrySdk = OpenTelemetrySdk.builder()
.setLoggerProvider(loggerProvider)
.build();
// Install the appender - connects Logback to OpenTelemetry
OpenTelemetryAppender.install(openTelemetrySdk);
// Your application code
runApplication();
// Shutdown gracefully
openTelemetrySdk.close();
}
}
Appender Configuration Options
The OpenTelemetry Logback appender supports these configuration attributes:
| Attribute | Type | Default | Description |
|---|---|---|---|
captureExperimentalAttributes | boolean | false | Capture thread.name and thread.id as attributes |
captureCodeAttributes | boolean | false | Capture source code location (class, method, line number) |
captureMarkerAttribute | boolean | false | Capture Logback markers as attributes |
captureKeyValuePairAttributes | boolean | false | Capture Logback key-value pairs as attributes |
captureLoggerContext | boolean | false | Capture Logback logger context properties |
captureTemplate | boolean | false | Capture the message template |
captureArguments | boolean | false | Capture log event arguments |
captureLogstashMarkerAttributes | boolean | false | Capture Logstash markers |
captureLogstashStructuredArguments | boolean | false | Capture Logstash StructuredArguments |
captureMdcAttributes | string | - | Comma-separated list of MDC keys to capture, or * for all |
captureEventName | boolean | false | Use event.name attribute as the log event name |
numLogsCapturedBeforeOtelInstall | int | 1000 | Number of logs to cache before SDK initialization |
MDC Context Injection (Via File/Stdout)
For applications that already output logs to files or stdout, you can inject trace context into Logback's MDC. This enables log-trace correlation without changing your log output destination.
Installation
Add the MDC library dependency:
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-logback-mdc-1.0</artifactId>
<version>2.11.0-alpha</version>
<scope>runtime</scope>
</dependency>
Configuration
The MDC appender wraps existing Logback appenders to inject span context. Configure it in your logback.xml:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- Define the actual console appender -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} trace_id=%X{trace_id} span_id=%X{span_id} trace_flags=%X{trace_flags} - %msg%n</pattern>
</encoder>
</appender>
<!-- Wrap it with the OpenTelemetry MDC appender -->
<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>
</configuration>
Injected MDC Keys
When a span is active, these keys are automatically added to Logback's MDC:
| Key | Description |
|---|---|
trace_id | The current trace ID |
span_id | The current span ID |
trace_flags | Trace flags (e.g., sampling decision) |
Customizing Key Names
You can customize the MDC key names:
<appender name="OTEL" class="io.opentelemetry.instrumentation.logback.mdc.v1_0.OpenTelemetryAppender">
<traceIdKey>custom_trace_id</traceIdKey>
<spanIdKey>custom_span_id</spanIdKey>
<traceFlagsKey>custom_trace_flags</traceFlagsKey>
<appender-ref ref="CONSOLE"/>
</appender>
Baggage Support
Enable baggage propagation to MDC by setting:
<appender name="OTEL" class="io.opentelemetry.instrumentation.logback.mdc.v1_0.OpenTelemetryAppender">
<addBaggage>true</addBaggage>
<appender-ref ref="CONSOLE"/>
</appender>
Baggage entries will appear as baggage.<key> in the MDC.
Using with Java Agent (Zero-Code)
If you're using the OpenTelemetry Java Agent, Logback instrumentation is included automatically. No additional dependencies or code changes are needed.
# Download OpenTelemetry Java Agent
wget https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar
# Configure and run
java -javaagent:opentelemetry-javaagent.jar \
-Dotel.service.name=my-service \
-Dotel.exporter.otlp.endpoint=https://api.uptrace.dev:4317 \
-Dotel.exporter.otlp.headers=uptrace-dsn=<your-dsn> \
-Dotel.logs.exporter=otlp \
-jar your-app.jar
The agent automatically:
- Injects trace context into Logback MDC (
logback-mdc) - Captures log records and exports via OTLP (
logback-appender)
Controlling Logback Instrumentation
You can selectively enable or disable Logback instrumentation:
| System Property | Default | Description |
|---|---|---|
otel.instrumentation.logback-appender.enabled | true | Enable/disable log export |
otel.instrumentation.logback-mdc.enabled | true | Enable/disable MDC injection |
For example, to only inject trace context without exporting logs:
java -javaagent:opentelemetry-javaagent.jar \
-Dotel.instrumentation.logback-appender.enabled=false \
-Dotel.instrumentation.logback-mdc.enabled=true \
-jar your-app.jar
Java Agent Appender Options
Configure the appender via system properties when using the Java agent:
| System Property | Type | Default | Description |
|---|---|---|---|
otel.instrumentation.logback-appender.experimental-log-attributes | boolean | false | Capture thread.name and thread.id |
otel.instrumentation.logback-appender.experimental.capture-code-attributes | boolean | false | Capture source code attributes (may add overhead) |
otel.instrumentation.logback-appender.experimental.capture-marker-attribute | boolean | false | Capture Logback markers as attributes |
otel.instrumentation.logback-appender.experimental.capture-key-value-pair-attributes | boolean | false | Capture Logback key-value pairs |
otel.instrumentation.logback-appender.experimental.capture-logger-context-attributes | boolean | false | Capture Logback logger context properties |
otel.instrumentation.logback-appender.experimental.capture-mdc-attributes | string | - | Comma-separated MDC keys, or * for all |
Spring Boot Starter
When using the OpenTelemetry Spring Boot Starter, Logback instrumentation is enabled by default. Both appender and MDC instrumentation are automatically configured.
Control the instrumentation via application properties:
# Enable/disable Logback appender (log export)
otel.instrumentation.logback-appender.enabled=true
# Enable/disable Logback MDC (trace context injection)
otel.instrumentation.logback-mdc.enabled=true
# Capture experimental attributes
otel.instrumentation.logback-appender.experimental-log-attributes=true
otel.instrumentation.logback-appender.experimental.capture-mdc-attributes=*
You can also configure via logback-spring.xml for more control:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="OpenTelemetry"
class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender">
<captureExperimentalAttributes>true</captureExperimentalAttributes>
<captureCodeAttributes>true</captureCodeAttributes>
<captureMdcAttributes>*</captureMdcAttributes>
</appender>
<root level="INFO">
<appender-ref ref="console"/>
<appender-ref ref="OpenTelemetry"/>
</root>
</configuration>
Complete Example
Here's a complete example combining tracing and logging:
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class OrderService {
private static final Logger logger = LoggerFactory.getLogger(OrderService.class);
private final Tracer tracer;
public OrderService() {
this.tracer = GlobalOpenTelemetry.getTracer("OrderService", "1.0.0");
}
public void processOrder(String orderId) {
Span span = tracer.spanBuilder("processOrder")
.setAttribute("order.id", orderId)
.startSpan();
try (Scope scope = span.makeCurrent()) {
// Log with automatic trace correlation
logger.info("Processing order: {}", orderId);
validateOrder(orderId);
logger.debug("Order validated successfully");
chargePayment(orderId);
logger.info("Payment processed for order: {}", orderId);
fulfillOrder(orderId);
logger.info("Order fulfilled: {}", orderId);
} catch (Exception e) {
logger.error("Failed to process order: {}", orderId, e);
span.recordException(e);
throw e;
} finally {
span.end();
}
}
private void validateOrder(String orderId) {
Span span = tracer.spanBuilder("validateOrder").startSpan();
try (Scope scope = span.makeCurrent()) {
logger.debug("Validating order {}", orderId);
// Validation logic
} finally {
span.end();
}
}
private void chargePayment(String orderId) {
Span span = tracer.spanBuilder("chargePayment").startSpan();
try (Scope scope = span.makeCurrent()) {
logger.info("Charging payment for order {}", orderId);
// Payment logic
} finally {
span.end();
}
}
private void fulfillOrder(String orderId) {
Span span = tracer.spanBuilder("fulfillOrder").startSpan();
try (Scope scope = span.makeCurrent()) {
logger.info("Fulfilling order {}", orderId);
// Fulfillment logic
} finally {
span.end();
}
}
}
JSON Logging for Kubernetes
For production environments, especially Kubernetes deployments, use JSON logging with the Logstash encoder for structured output:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<includeMdcKeyName>trace_id</includeMdcKeyName>
<includeMdcKeyName>span_id</includeMdcKeyName>
<includeMdcKeyName>trace_flags</includeMdcKeyName>
</encoder>
</appender>
<!-- MDC wrapper for trace context injection -->
<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>
</configuration>
Add the Logstash encoder dependency:
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>8.0</version>
</dependency>
See the Kubernetes stdout logging example for a complete end-to-end demonstration.
What is Uptrace?
Uptrace is a OpenTelemetry APM that supports distributed tracing, metrics, and logs. You can use it to monitor applications and troubleshoot issues.

Uptrace comes with an intuitive query builder, rich dashboards, alerting rules with notifications, and integrations for most languages and frameworks.
Uptrace can process billions of spans and metrics on a single server and allows you to monitor your applications at 10x lower cost.
In just a few minutes, you can try Uptrace by visiting the cloud demo (no login required) or running it locally with Docker. The source code is available on GitHub.
What's next?
Logback is now integrated with OpenTelemetry, providing automatic trace correlation and export capabilities. For alternative logging frameworks in Java, explore Log4j integration. For complete Java instrumentation, see the OpenTelemetry Java guide or Spring Boot integration.