# OpenTelemetry Logs for Java

> Collect Java application logs with OpenTelemetry using Log4j2, Logback, or SLF4J and send structured logs to Uptrace.

![undefined](/devicon/java-original.svg)This document covers OpenTelemetry Logs for Java, focusing on integration with popular logging frameworks.

## Prerequisites

<partial path="otel-prereq-java">



</partial>

If you are not familiar with logs terminology like structured logging or log-trace correlation, read the introduction to [OpenTelemetry Logs](/opentelemetry/logs) first.

## Overview

OpenTelemetry provides two approaches for collecting logs in Java:

1. **Java Agent (recommended)**: The OpenTelemetry Java Agent automatically captures logs from popular logging frameworks (Log4j2, Logback, java.util.logging) and correlates them with traces.
2. **Manual instrumentation**: Use OpenTelemetry logging libraries directly for fine-grained control.

The Java Agent approach is recommended because it requires no code changes and automatically adds trace context (trace_id, span_id) to your logs.

## Automatic Log Collection with Java Agent

The OpenTelemetry Java Agent automatically captures logs from:

- **Log4j2** (2.x)
- **Logback** (1.x)
- **java.util.logging** (JUL)

### Configuration

Enable log export by setting the `OTEL_LOGS_EXPORTER` environment variable:

```shell
export OTEL_LOGS_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_ENDPOINT=https://api.uptrace.dev:4317
export OTEL_EXPORTER_OTLP_HEADERS="uptrace-dsn=<FIXME>"
```

Then run your application with the Java Agent:

```shell
java -javaagent:path/to/opentelemetry-javaagent.jar \
     -jar myapp.jar
```

All logs emitted by your application will be automatically captured and exported to Uptrace with trace context.

## Log4j2 Integration

[Log4j2](https://logging.apache.org/log4j/2.x/) is a popular logging framework for Java. The OpenTelemetry Java Agent automatically captures Log4j2 logs.

### Basic Usage

```java
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class MyService {
    private static final Logger logger = LogManager.getLogger(MyService.class);

    public void processRequest(String userId) {
        logger.info("Processing request for user: {}", userId);

        try {
            // Business logic
            performOperation();
            logger.debug("Operation completed successfully");
        } catch (Exception e) {
            logger.error("Failed to process request", e);
        }
    }

    private void performOperation() {
        // Your business logic
    }
}
```

### Manual Instrumentation with OpenTelemetry Appender

For more control, you can use the OpenTelemetry Log4j2 Appender directly:

Add the dependency:

<code-group>

```xml [Maven]
<dependency>
    <groupId>io.opentelemetry.instrumentation</groupId>
    <artifactId>opentelemetry-log4j-appender-2.17</artifactId>
    <version>2.11.0-alpha</version>
    <scope>runtime</scope>
</dependency>
```

```gradle [Gradle]
runtimeOnly("io.opentelemetry.instrumentation:opentelemetry-log4j-appender-2.17:2.11.0-alpha")
```

</code-group>

Configure the appender in `log4j2.xml`:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
        <OpenTelemetry name="OpenTelemetry"/>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="OpenTelemetry"/>
        </Root>
    </Loggers>
</Configuration>
```

See [OpenTelemetry Log4j2](/guides/opentelemetry-log4j) for details.

## Logback Integration

[Logback](https://logback.qos.ch/) is another popular logging framework, often used with Spring Boot. The OpenTelemetry Java Agent automatically captures Logback logs.

### Basic Usage

```java
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);

        try {
            // Business logic
            performOperation();
            logger.debug("Operation completed successfully");
        } catch (Exception e) {
            logger.error("Failed to process request", e);
        }
    }

    private void performOperation() {
        // Your business logic
    }
}
```

### Manual Instrumentation with OpenTelemetry Appender

For more control, you can use the OpenTelemetry Logback Appender directly:

Add the dependency:

<code-group>

```xml [Maven]
<dependency>
    <groupId>io.opentelemetry.instrumentation</groupId>
    <artifactId>opentelemetry-logback-appender-1.0</artifactId>
    <version>2.11.0-alpha</version>
    <scope>runtime</scope>
</dependency>
```

```gradle [Gradle]
runtimeOnly("io.opentelemetry.instrumentation:opentelemetry-logback-appender-1.0:2.11.0-alpha")
```

</code-group>

Configure the appender in `logback.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">
    </appender>

    <root level="INFO">
        <appender-ref ref="console"/>
        <appender-ref ref="OpenTelemetry"/>
    </root>
</configuration>
```

See [OpenTelemetry Logback](/guides/opentelemetry-logback) for details.

## Log-Trace Correlation

When you emit a log within an active trace span, OpenTelemetry automatically includes:

- **trace_id**: Links log to the entire distributed trace
- **span_id**: Links log to the specific operation
- **trace_flags**: Indicates if the trace is sampled

This enables bidirectional navigation between logs and traces in Uptrace.

### Example with Tracing

```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 static final Tracer tracer = GlobalOpenTelemetry.getTracer("order-service");

    public void processOrder(String orderId) {
        Span span = tracer.spanBuilder("process-order").startSpan();
        try (Scope scope = span.makeCurrent()) {
            // Log automatically includes trace_id and span_id
            logger.info("Starting order processing for orderId={}", orderId);

            validateOrder(orderId);
            chargePayment(orderId);
            sendConfirmation(orderId);

            logger.info("Order processing completed for orderId={}", orderId);
        } catch (Exception e) {
            logger.error("Order processing failed for orderId={}", orderId, e);
            span.recordException(e);
            throw e;
        } finally {
            span.end();
        }
    }

    private void validateOrder(String orderId) {
        logger.debug("Validating order orderId={}", orderId);
        // Validation logic
    }

    private void chargePayment(String orderId) {
        logger.debug("Charging payment for orderId={}", orderId);
        // Payment logic
    }

    private void sendConfirmation(String orderId) {
        logger.debug("Sending confirmation for orderId={}", orderId);
        // Confirmation logic
    }
}
```

### Manual Correlation

If you can't use automatic instrumentation, manually inject trace context using MDC (Mapped Diagnostic Context):

```java
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanContext;
import org.slf4j.MDC;

public class TraceContextHelper {

    public static void addTraceContext() {
        SpanContext spanContext = Span.current().getSpanContext();
        if (spanContext.isValid()) {
            MDC.put("trace_id", spanContext.getTraceId());
            MDC.put("span_id", spanContext.getSpanId());
            MDC.put("trace_flags", spanContext.getTraceFlags().asHex());
        }
    }

    public static void clearTraceContext() {
        MDC.remove("trace_id");
        MDC.remove("span_id");
        MDC.remove("trace_flags");
    }
}
```

Then update your logging pattern to include MDC values:

```xml
<!-- Logback pattern -->
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} trace_id=%X{trace_id} span_id=%X{span_id} - %msg%n</pattern>
```

## Best Practices

### Use Structured Logging

Use key-value pairs for structured logging to enable filtering:

```java
// Good: Structured logging with key-value pairs
logger.info("Database query executed query_type={} table={} duration_ms={} rows_affected={}",
    "SELECT", "users", 45, 1);

// Bad: Unstructured message
logger.info("Executed SELECT query on users table, took 45ms, returned 1 row");
```

### Use Appropriate Log Levels

```java
// TRACE: Very detailed information, typically only useful during development
logger.trace("Entering method processRequest with param={}", param);

// DEBUG: Detailed information useful for debugging
logger.debug("Cache miss for key={}", cacheKey);

// INFO: General information about application flow
logger.info("User logged in userId={}", userId);

// WARN: Potentially harmful situations
logger.warn("Retry attempt count={} for operation={}", retryCount, operationName);

// ERROR: Error events that might still allow the application to continue
logger.error("Failed to process request", exception);
```

### Avoid Logging Sensitive Data

Never log passwords, tokens, or PII:

```java
// Bad: Logging sensitive data
logger.info("User login password={}", password);

// Good: Redact sensitive fields
logger.info("User login userId={}", userId);
```

### Avoid String Concatenation

Use parameterized logging instead of string concatenation:

```java
// Bad: String concatenation (always evaluates arguments)
logger.debug("Processing order " + orderId + " for user " + userId);

// Good: Parameterized logging (lazy evaluation)
logger.debug("Processing order orderId={} userId={}", orderId, userId);
```

## What's next?

- [Get started](/get/opentelemetry-java)
- [Learn about OpenTelemetry Java Tracing API](/get/opentelemetry-java/tracing)
- [Learn about OpenTelemetry Java Metrics API](/get/opentelemetry-java/metrics)
- [Learn about OpenTelemetry Java Resource detectors](/get/opentelemetry-java/resources)
- [OpenTelemetry Log4j2](/guides/opentelemetry-log4j)
- [OpenTelemetry Logback](/guides/opentelemetry-logback)
