OpenTelemetry Logback logging [Java]

Vladimir Mihailenco
December 28, 2025
7 min read

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:

xml Maven
<dependency>
  <groupId>io.opentelemetry.instrumentation</groupId>
  <artifactId>opentelemetry-logback-appender-1.0</artifactId>
  <version>2.11.0-alpha</version>
</dependency>
gradle Gradle
implementation("io.opentelemetry.instrumentation:opentelemetry-logback-appender-1.0:2.11.0-alpha")

You also need the OpenTelemetry SDK and OTLP exporter:

xml Maven
<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>
gradle Gradle
implementation("io.opentelemetry:opentelemetry-sdk:1.45.0")
implementation("io.opentelemetry:opentelemetry-exporter-otlp:1.45.0")

Configuration

Add the OpenTelemetry appender to your logback.xml or logback-spring.xml:

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:

java
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:

AttributeTypeDefaultDescription
captureExperimentalAttributesbooleanfalseCapture thread.name and thread.id as attributes
captureCodeAttributesbooleanfalseCapture source code location (class, method, line number)
captureMarkerAttributebooleanfalseCapture Logback markers as attributes
captureKeyValuePairAttributesbooleanfalseCapture Logback key-value pairs as attributes
captureLoggerContextbooleanfalseCapture Logback logger context properties
captureTemplatebooleanfalseCapture the message template
captureArgumentsbooleanfalseCapture log event arguments
captureLogstashMarkerAttributesbooleanfalseCapture Logstash markers
captureLogstashStructuredArgumentsbooleanfalseCapture Logstash StructuredArguments
captureMdcAttributesstring-Comma-separated list of MDC keys to capture, or * for all
captureEventNamebooleanfalseUse event.name attribute as the log event name
numLogsCapturedBeforeOtelInstallint1000Number 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:

xml Maven
<dependency>
  <groupId>io.opentelemetry.instrumentation</groupId>
  <artifactId>opentelemetry-logback-mdc-1.0</artifactId>
  <version>2.11.0-alpha</version>
  <scope>runtime</scope>
</dependency>
gradle Gradle
runtimeOnly("io.opentelemetry.instrumentation:opentelemetry-logback-mdc-1.0:2.11.0-alpha")

Configuration

The MDC appender wraps existing Logback appenders to inject span context. Configure it in your logback.xml:

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:

KeyDescription
trace_idThe current trace ID
span_idThe current span ID
trace_flagsTrace flags (e.g., sampling decision)

Customizing Key Names

You can customize the MDC key names:

xml
<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:

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

bash
# 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 PropertyDefaultDescription
otel.instrumentation.logback-appender.enabledtrueEnable/disable log export
otel.instrumentation.logback-mdc.enabledtrueEnable/disable MDC injection

For example, to only inject trace context without exporting logs:

bash
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 PropertyTypeDefaultDescription
otel.instrumentation.logback-appender.experimental-log-attributesbooleanfalseCapture thread.name and thread.id
otel.instrumentation.logback-appender.experimental.capture-code-attributesbooleanfalseCapture source code attributes (may add overhead)
otel.instrumentation.logback-appender.experimental.capture-marker-attributebooleanfalseCapture Logback markers as attributes
otel.instrumentation.logback-appender.experimental.capture-key-value-pair-attributesbooleanfalseCapture Logback key-value pairs
otel.instrumentation.logback-appender.experimental.capture-logger-context-attributesbooleanfalseCapture Logback logger context properties
otel.instrumentation.logback-appender.experimental.capture-mdc-attributesstring-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:

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
<?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:

java
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
<?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:

xml Maven
<dependency>
  <groupId>net.logstash.logback</groupId>
  <artifactId>logstash-logback-encoder</artifactId>
  <version>8.0</version>
</dependency>
gradle Gradle
implementation("net.logstash.logback:logstash-logback-encoder:8.0")

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 Overview

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.