> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bronto.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Send Java logs and traces to Bronto via OpenTelemetry

> Instrument Java applications with the OpenTelemetry Java agent or SDK to send logs and traces to Bronto over OTLP via a local Collector.

This page covers instrumenting a Java application with the OpenTelemetry SDK to send **logs and traces** to Bronto over OTLP/HTTP via a local OTel Collector. Both signals are co-equal: logs are bridged via the Logback or Log4j2 appender — no log statement changes needed — and traces are emitted via a tracer provider.

<Tip>
  If you don't have a Collector and want to export directly from your application to Bronto, see [Direct export to Bronto](#direct-export-to-bronto) at the bottom of this page.
</Tip>

## Prerequisites

* Java 8 or later
* Maven or Gradle
* A running OTel Collector configured to forward logs and traces to Bronto — see [Connect Open Telemetry to Bronto](/agent-setup/open-telemetry)
* [OpenTelemetry Java SDK documentation](https://opentelemetry.io/docs/languages/java/)

## Install dependencies

<Tip>
  Use the [OpenTelemetry BOM](https://mvnrepository.com/artifact/io.opentelemetry/opentelemetry-bom) to manage all SDK versions in one place and avoid version conflicts.
</Tip>

<CodeGroup>
  ```xml Maven (pom.xml) theme={"dark"}
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-bom</artifactId>
        <!-- check https://mvnrepository.com/artifact/io.opentelemetry/opentelemetry-bom for latest -->
        <version>LATEST</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <dependencies>
    <dependency>
      <groupId>io.opentelemetry</groupId>
      <artifactId>opentelemetry-api</artifactId>
    </dependency>
    <dependency>
      <groupId>io.opentelemetry</groupId>
      <artifactId>opentelemetry-sdk</artifactId>
    </dependency>
    <dependency>
      <groupId>io.opentelemetry</groupId>
      <artifactId>opentelemetry-exporter-otlp</artifactId>
    </dependency>
    <!-- check https://mvnrepository.com/artifact/io.opentelemetry.instrumentation/opentelemetry-logback-appender-1.0 for latest -->
    <dependency>
      <groupId>io.opentelemetry.instrumentation</groupId>
      <artifactId>opentelemetry-logback-appender-1.0</artifactId>
      <version>LATEST</version>
    </dependency>
  </dependencies>
  ```

  ```groovy Gradle (build.gradle) theme={"dark"}
  // check https://mvnrepository.com/artifact/io.opentelemetry/opentelemetry-bom for latest BOM version
  implementation platform('io.opentelemetry:opentelemetry-bom:LATEST')

  dependencies {
      implementation 'io.opentelemetry:opentelemetry-api'
      implementation 'io.opentelemetry:opentelemetry-sdk'
      implementation 'io.opentelemetry:opentelemetry-exporter-otlp'
      // check https://mvnrepository.com/artifact/io.opentelemetry.instrumentation/opentelemetry-logback-appender-1.0 for latest
      implementation 'io.opentelemetry.instrumentation:opentelemetry-logback-appender-1.0:LATEST'
  }
  ```
</CodeGroup>

| Package                              | Purpose                               |
| ------------------------------------ | ------------------------------------- |
| `opentelemetry-api`                  | Core OTel API                         |
| `opentelemetry-sdk`                  | SDK — `SdkLoggerProvider`, processors |
| `opentelemetry-exporter-otlp`        | OTLP/HTTP exporter                    |
| `opentelemetry-logback-appender-1.0` | Bridges Logback into OTel             |

<Note>
  Using Log4j2 instead of Logback? Replace the appender dependency with `opentelemetry-log4j-appender-2.17` from `io.opentelemetry.instrumentation`.
</Note>

## Configure the log bridge

Add the `OpenTelemetryAppender` to your `logback.xml`. Every log record Logback handles will be forwarded to the OTel pipeline.

```xml logback.xml theme={"dark"}
<configuration>
  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{HH:mm:ss} %-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>
  </appender>

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

`captureExperimentalAttributes` includes thread name and logger name as OTel attributes. `captureCodeAttributes` includes the source file, class, method, and line number.

## Configure the OTLP exporter

Create an `OpenTelemetrySdk` instance with a `SdkLoggerProvider` and install it as the global instance. Call `OpenTelemetryAppender.install()` to wire the Logback appender to the SDK.

```java OtelConfig.java theme={"dark"}
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter;
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 OtelConfig {
    public static void configure() {
        OtlpHttpLogRecordExporter exporter = OtlpHttpLogRecordExporter.builder()
            .setEndpoint("http://localhost:4318/v1/logs")
            .build();

        Resource resource = Resource.getDefault().merge(
            Resource.create(Attributes.of(
                ResourceAttributes.SERVICE_NAME, "my-service",
                ResourceAttributes.SERVICE_NAMESPACE, "my-team",
                ResourceAttributes.DEPLOYMENT_ENVIRONMENT, "production"
            ))
        );

        SdkLoggerProvider loggerProvider = SdkLoggerProvider.builder()
            .setResource(resource)
            .addLogRecordProcessor(BatchLogRecordProcessor.builder(exporter).build())
            .build();

        OpenTelemetrySdk openTelemetry = OpenTelemetrySdk.builder()
            .setLoggerProvider(loggerProvider)
            .buildAndRegisterGlobal();

        OpenTelemetryAppender.install(openTelemetry);
    }
}
```

Call `OtelConfig.configure()` once at application startup, before the first log statement.

## Set resource attributes

Resource attributes are attached to every log record exported from this process. Two attributes drive how Bronto organises incoming logs:

| OTel attribute      | Bronto concept | Description                                  |
| ------------------- | -------------- | -------------------------------------------- |
| `service.name`      | Dataset        | Groups logs from one service                 |
| `service.namespace` | Collection     | Groups related services or a team's services |

These are set via `Resource.create()` in the SDK configuration above.

## Complete example

```java Main.java theme={"dark"}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {
    private static final Logger logger = LoggerFactory.getLogger(Main.class);

    public static void main(String[] args) {
        OtelConfig.configure();

        logger.info("Application started");
        logger.warn("Low disk space, free_gb={}", 2.1);
        logger.error("Database connection failed", new RuntimeException("timeout"));
    }
}
```

Existing SLF4J / Logback log statements require no changes.

## Verify delivery

After running your application, check both signals in Bronto:

* **Logs**: open the [Search](https://app.bronto.io/search) page and filter by the dataset name you set in `service.name` — your log records should appear within a few seconds.
* **Traces**: open the Explore Traces page and filter by the same `service.name` — your spans should appear within a few seconds.

If no logs appear, check:

* The OTel Collector is running and reachable at the configured endpoint.
* The Collector's pipeline includes a `logs` pipeline with an `otlp` receiver and the Bronto exporter — see [Connect Open Telemetry to Bronto](/agent-setup/open-telemetry).
* `OpenTelemetryAppender.install()` is called before the first log statement.
* `BatchLogRecordProcessor` exports on a background thread — for short-lived programs, add a shutdown hook: `loggerProvider.shutdown()`.

## Traces

<Tip>
  The [OpenTelemetry Java Agent](https://opentelemetry.io/docs/zero-code/java/agent/) provides zero-code auto-instrumentation for Spring, Hibernate, gRPC, Kafka, JDBC, and many more frameworks. Download the jar and attach it at startup with `-javaagent:opentelemetry-javaagent.jar` — no SDK code changes are needed. It is the recommended starting point for most Java applications.
</Tip>

### Configure the tracer provider

No additional packages are needed — `opentelemetry-sdk` and `opentelemetry-exporter-otlp` already include tracing support.

Create a `SdkTracerProvider` and combine it with the `SdkLoggerProvider` in the same `OpenTelemetrySdk` builder:

```java OtelConfig.java theme={"dark"}
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;

SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
    .setResource(resource)
    .addSpanProcessor(BatchSpanProcessor.builder(
        OtlpHttpSpanExporter.builder()
            .setEndpoint("http://localhost:4318/v1/traces")
            .build()
    ).build())
    .build();

OpenTelemetrySdk openTelemetry = OpenTelemetrySdk.builder()
    .setLoggerProvider(loggerProvider)
    .setTracerProvider(tracerProvider)
    .buildAndRegisterGlobal();

OpenTelemetryAppender.install(openTelemetry);
```

The shared `resource` ensures `service.name` and `service.namespace` are identical on both logs and traces.

### Creating spans

Get a tracer from the global instance and use it to create spans:

```java theme={"dark"}
Tracer tracer = GlobalOpenTelemetry.getTracer("my-service");

Span span = tracer.spanBuilder("process-payment")
    .startSpan();
try (Scope scope = span.makeCurrent()) {
    span.setAttribute("payment.amount", 99.99);
    logger.info("Processing payment");  // trace_id and span_id injected automatically
} finally {
    span.end();
}
```

Any Logback or Log4j2 statement inside an active span will automatically have `trace_id` and `span_id` attached.

## GenAI semantic conventions

If your application calls an LLM, OpenTelemetry defines [GenAI semantic conventions](https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-spans.md) for model, token usage, and prompt/response content.

<Note>
  This page was verified on **July 17, 2026**. GenAI libraries and semantic conventions are evolving rapidly, so package configuration and emitted attributes can change between releases. Keep your instrumentation current and verify the fields emitted by the version you deploy.
</Note>

### Auto-instrumentation

The [OpenTelemetry Java Agent](https://opentelemetry.io/docs/zero-code/java/agent/) includes GenAI instrumentation for the OpenAI Java client and AWS SDK v2 Bedrock calls — no extra instrumentation dependency is needed. Model calls carry `gen_ai.provider.name`, `gen_ai.request.model`, and token-usage attributes instead of only a plain HTTP client span.

<Note>
  GenAI instrumentation coverage in the Java agent is newer and narrower than Python's — check the [agent's supported libraries list](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/docs/supported-libraries.md) for your provider/framework. If it isn't covered, use manual spans below.
</Note>

### Experimental: capturing prompt and response content

For the Java agent's OpenAI and AWS SDK instrumentations, content capture is off by default:

```bash theme={"dark"}
export OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true
```

<Warning>
  Captured content may be emitted as OTel log events rather than span attributes, depending on the instrumented client and agent version. It then travels through the logs pipeline and is not queryable on the trace span itself.

  To make it searchable, also emit it as a structured **log record** correlated by `trace_id`, using the log bridge configured above. See [LLM Observability](/ai-features/llm-observability) for the recommended attribute names, a worked logging example, and Bronto search queries.
</Warning>

### Manual spans

Where the agent doesn't cover your provider or framework, set the `gen_ai.*` attributes yourself:

```java theme={"dark"}
Tracer tracer = GlobalOpenTelemetry.getTracer("my-service");

Span span = tracer.spanBuilder("chat gpt-4o-mini").startSpan();
try (Scope scope = span.makeCurrent()) {
    span.setAttribute("gen_ai.provider.name", "openai");
    span.setAttribute("gen_ai.request.model", "gpt-4o-mini");
    span.setAttribute("gen_ai.usage.input_tokens", 33);
    span.setAttribute("gen_ai.usage.output_tokens", 74);
    span.setAttribute(
        io.opentelemetry.api.common.AttributeKey.stringArrayKey("gen_ai.response.finish_reasons"),
        java.util.List.of("stop")
    );
} finally {
    span.end();
}
```

See [LLM Observability](/ai-features/llm-observability) for the full recommended attribute set, including `gen_ai.input.messages` / `gen_ai.output.messages`, and how to search and aggregate GenAI fields in Bronto.

## Direct export to Bronto

If you are not using an OTel Collector, export directly to Bronto by replacing the exporter configurations with the Bronto OTLP endpoints and your API key:

```java theme={"dark"}
OtlpHttpLogRecordExporter logExporter = OtlpHttpLogRecordExporter.builder()
    .setEndpoint("https://ingestion.eu.bronto.io/v1/logs") // or ingestion.us.bronto.io
    .addHeader("x-bronto-api-key", "<YOUR_API_KEY>")
    .build();

OtlpHttpSpanExporter traceExporter = OtlpHttpSpanExporter.builder()
    .setEndpoint("https://ingestion.eu.bronto.io/v1/traces") // or ingestion.us.bronto.io
    .addHeader("x-bronto-api-key", "<YOUR_API_KEY>")
    .build();
```

| Region | Logs endpoint                            | Traces endpoint                            |
| ------ | ---------------------------------------- | ------------------------------------------ |
| EU     | `https://ingestion.eu.bronto.io/v1/logs` | `https://ingestion.eu.bronto.io/v1/traces` |
| US     | `https://ingestion.us.bronto.io/v1/logs` | `https://ingestion.us.bronto.io/v1/traces` |

See [API Keys](/Account-Management/API-Keys) for how to create a key with ingestion permissions. No other changes to the rest of the setup are required.
