> ## 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 Kotlin logs, metrics, and traces to Bronto

> Instrument Kotlin and JVM applications with the OpenTelemetry Java agent to send logs, metrics, and traces to Bronto over OTLP/HTTP, via an OTel Collector or directly.

Kotlin runs on the JVM, so the OpenTelemetry Java agent instruments it exactly as it does Java. Attach the agent at startup and it sends **logs, metrics, and traces** to Bronto with no code changes — instrumenting Spring, Ktor, Hibernate, JDBC, Kafka, and hundreds of other libraries, bridging your existing Logback or Log4j output into the OTLP logs pipeline, and collecting JVM metrics under one resource identity.

The application exports to an [OpenTelemetry Collector](/agent-setup/open-telemetry), which forwards to Bronto. If you don't run a Collector, [export directly to Bronto](#direct-export-to-bronto) instead — only the endpoint and an API key header change.

## Prerequisites

* Kotlin 1.8 or later, JVM 8 or later
* An OTel Collector reachable from your application, with `logs`, `metrics`, and `traces` pipelines forwarding to Bronto — see [Connect OpenTelemetry Collector to Bronto](/agent-setup/open-telemetry)

## Attach the agent

Download the agent jar:

```bash theme={"dark"}
curl -L -O https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar
```

Configure it through environment variables and attach it with `-javaagent`:

```bash theme={"dark"}
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
export OTEL_SERVICE_NAME="my-service"
export OTEL_RESOURCE_ATTRIBUTES="service.namespace=my-team,deployment.environment=production"

java -javaagent:./opentelemetry-javaagent.jar -jar myapp.jar
```

That's the whole setup. The agent exports all three signals over OTLP by default, and agent 2.x uses `http/protobuf`, appending the signal path (`/v1/logs`, `/v1/metrics`, `/v1/traces`) to the endpoint automatically.

Two resource attributes determine how Bronto organises your data:

| OTel attribute      | Bronto concept | Set with                   |
| ------------------- | -------------- | -------------------------- |
| `service.name`      | Dataset        | `OTEL_SERVICE_NAME`        |
| `service.namespace` | Collection     | `OTEL_RESOURCE_ATTRIBUTES` |

<Note>
  `http://localhost:4318` is the standard OTLP/HTTP address for a Collector on the same host. Use the address reachable from your application if the Collector runs in another container, pod, or host. No authentication is needed between the application and the Collector — the Collector holds the Bronto API key.
</Note>

## Instrument your application

Existing SLF4J, Logback, and Log4j statements need no changes — the agent picks them up and attaches `trace_id` and `span_id` to any log emitted inside an active span, so you can jump from a log line to its trace in Bronto.

For spans and metrics around your own business logic, add the API dependency — not the SDK, which the agent already supplies:

<CodeGroup>
  ```kotlin Gradle (build.gradle.kts) theme={"dark"}
  // check https://mvnrepository.com/artifact/io.opentelemetry/opentelemetry-api for latest
  implementation("io.opentelemetry:opentelemetry-api:LATEST")
  ```

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

```kotlin PaymentService.kt theme={"dark"}
import io.opentelemetry.api.GlobalOpenTelemetry
import org.slf4j.LoggerFactory

private val logger = LoggerFactory.getLogger("PaymentService")
private val tracer = GlobalOpenTelemetry.getTracer("my-service")
private val payments = GlobalOpenTelemetry.getMeter("my-service")
    .counterBuilder("app.payments")
    .build()

fun processPayment(amount: Double) {
    val span = tracer.spanBuilder("process-payment").startSpan()
    try {
        span.makeCurrent().use {
            span.setAttribute("payment.amount", amount)
            payments.add(1)
            logger.info("Processing payment")  // trace_id and span_id attached automatically
        }
    } finally {
        span.end()
    }
}
```

<Note>
  **Can't use the agent?** Where a javaagent isn't an option — GraalVM native images, for example — build the SDK in process with `opentelemetry-sdk-extension-autoconfigure`, which reads the same `OTEL_*` variables, and add `opentelemetry-logback-appender-1.0` plus `OpenTelemetryAppender.install(openTelemetry)` to bridge your logs. See [Configure the SDK](https://opentelemetry.io/docs/languages/java/configuration/).
</Note>

## Verify

Run your application, then check each signal in Bronto, filtering by the `service.name` you set:

* **Logs** — the [Search](https://app.bronto.io/search) page, in the dataset named after your service
* **Metrics** — the Metric Explorer
* **Traces** — the Explore Traces page

If nothing arrives:

* Confirm the Collector is running and reachable at `OTEL_EXPORTER_OTLP_ENDPOINT`, and that its pipelines include an `otlp` receiver and the Bronto exporters.
* Port `4318` is OTLP/HTTP and `4317` is OTLP/gRPC. If you point at `4317`, also set `OTEL_EXPORTER_OTLP_PROTOCOL=grpc`.
* Run with `-Dotel.javaagent.debug=true` to log what the agent instruments and exports at startup.

For signal-specific reference material, see [Send Metrics to Bronto](/metrics/send-metrics) and [Send Traces to Bronto](/tracing/send-traces).

## Direct export to Bronto

Without a Collector, the agent exports straight to Bronto over OTLP/HTTP. Point the endpoint at your Bronto region and add your API key:

```bash theme={"dark"}
export OTEL_EXPORTER_OTLP_ENDPOINT="https://ingestion.eu.bronto.io"   # or ingestion.us.bronto.io
export OTEL_EXPORTER_OTLP_HEADERS="x-bronto-api-key=<YOUR_API_KEY>"
```

| Region | Base endpoint                    |
| ------ | -------------------------------- |
| EU     | `https://ingestion.eu.bronto.io` |
| US     | `https://ingestion.us.bronto.io` |

See [API Keys](/Account-Management/API-Keys) for how to create a key with ingestion permissions.

<Tip>
  Prefer the Collector for multi-service environments — it batches, filters, and enriches telemetry, and keeps the API key out of every application. Direct export suits single services and simple architectures.
</Tip>

## GenAI semantic conventions

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

The Java agent covers Kotlin applications too — there is no separate Kotlin package. See [Java: GenAI semantic conventions](/opentelemetry/java#genai-semantic-conventions) for current auto-instrumentation coverage and the content-capture setting.

### Manual spans

Where the agent doesn't cover your provider, set the same attributes yourself:

```kotlin theme={"dark"}
val span = tracer.spanBuilder("chat gpt-4o-mini").startSpan()
try {
    span.makeCurrent().use {
        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)
        // Bronto flattens array attributes — this is searchable as finish_reasons.0
        span.setAttribute(
            AttributeKey.stringArrayKey("gen_ai.response.finish_reasons"),
            listOf("stop"))
    }
} finally {
    span.end()
}
```

See the [GenAI span conventions](https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-spans.md) for the full attribute list.

For the recommended attribute set and Bronto search queries, see [LLM Observability](/ai-features/llm-observability).
