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

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

One setup function sends **logs, metrics, and traces** from your Go application to Bronto. Logs are bridged from `log/slog`, metrics flow through a meter provider, and traces through a tracer provider — all three sharing one resource identity and one OTLP/HTTP exporter configuration.

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 — the code is identical, only two environment variables change.

## Prerequisites

* Go 1.21 or later (required for `log/slog`)
* 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)

## Install dependencies

```bash theme={"dark"}
go get go.opentelemetry.io/otel \
  go.opentelemetry.io/otel/sdk \
  go.opentelemetry.io/otel/sdk/log \
  go.opentelemetry.io/otel/sdk/metric \
  go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp \
  go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp \
  go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp \
  go.opentelemetry.io/contrib/bridges/otelslog
```

## Configure the environment

The exporters read their endpoint from environment variables, so nothing in the code below is environment-specific.

```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"
```

The exporters append the signal path (`/v1/logs`, `/v1/metrics`, `/v1/traces`) to `OTEL_EXPORTER_OTLP_ENDPOINT`, and the URL scheme decides TLS — `http://` for a plaintext local Collector, `https://` for a secured one.

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>

## Initialise the SDK

```go otel.go theme={"dark"}
package main

import (
    "context"
    "errors"
    "log/slog"

    "go.opentelemetry.io/contrib/bridges/otelslog"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp"
    "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
    "go.opentelemetry.io/otel/sdk/log"
    "go.opentelemetry.io/otel/sdk/metric"
    "go.opentelemetry.io/otel/sdk/resource"
    "go.opentelemetry.io/otel/sdk/trace"
)

// setupOTel configures logs, metrics, and traces. The returned function flushes
// and shuts down all three providers.
func setupOTel(ctx context.Context) (func(context.Context) error, error) {
    // Picks up OTEL_SERVICE_NAME and OTEL_RESOURCE_ATTRIBUTES.
    res := resource.Default()

    var shutdownFuncs []func(context.Context) error
    shutdown := func(ctx context.Context) error {
        var err error
        for _, fn := range shutdownFuncs {
            err = errors.Join(err, fn(ctx))
        }
        return err
    }

    // Logs — replace the default slog handler with the OTel bridge.
    logExporter, err := otlploghttp.New(ctx)
    if err != nil {
        return nil, err
    }
    loggerProvider := log.NewLoggerProvider(
        log.WithResource(res),
        log.WithProcessor(log.NewBatchProcessor(logExporter)),
    )
    slog.SetDefault(slog.New(otelslog.NewHandler("my-service",
        otelslog.WithLoggerProvider(loggerProvider))))
    shutdownFuncs = append(shutdownFuncs, loggerProvider.Shutdown)

    // Metrics
    metricExporter, err := otlpmetrichttp.New(ctx)
    if err != nil {
        return nil, errors.Join(err, shutdown(ctx))
    }
    meterProvider := metric.NewMeterProvider(
        metric.WithResource(res),
        metric.WithReader(metric.NewPeriodicReader(metricExporter)),
    )
    otel.SetMeterProvider(meterProvider)
    shutdownFuncs = append(shutdownFuncs, meterProvider.Shutdown)

    // Traces
    traceExporter, err := otlptracehttp.New(ctx)
    if err != nil {
        return nil, errors.Join(err, shutdown(ctx))
    }
    tracerProvider := trace.NewTracerProvider(
        trace.WithResource(res),
        trace.WithBatcher(traceExporter),
    )
    otel.SetTracerProvider(tracerProvider)
    shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown)

    return shutdown, nil
}
```

## Instrument your application

Existing `slog` calls need no changes. Add spans and instruments for your own business logic:

```go main.go theme={"dark"}
package main

import (
    "context"
    "log/slog"
    "os"
    "os/signal"

    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/attribute"
)

func main() {
    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
    defer stop()

    shutdown, err := setupOTel(ctx)
    if err != nil {
        slog.Error("failed to configure OpenTelemetry", "error", err)
        os.Exit(1)
    }
    defer shutdown(context.Background())

    tracer := otel.Tracer("my-service")
    payments, _ := otel.Meter("my-service").Int64Counter("app.payments")

    ctx, span := tracer.Start(ctx, "process-payment")
    span.SetAttributes(attribute.Float64("payment.amount", 99.99))
    payments.Add(ctx, 1)
    slog.InfoContext(ctx, "Processing payment") // trace_id and span_id attached automatically
    span.End()
}
```

Use the `Context`-aware `slog` methods — `InfoContext`, `WarnContext`, `ErrorContext` — and pass the context returned by `tracer.Start`. That is what lets the bridge attach `trace_id` and `span_id`, so you can jump from a log line to its trace in Bronto.

<Tip>
  Go has no runtime auto-instrumentation. For HTTP servers and clients, wrap your handlers with [`otelhttp`](https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp); equivalent packages exist for gRPC, `database/sql`, and popular frameworks — see [Go instrumentation libraries](https://opentelemetry.io/docs/languages/go/libraries/). To get traces and RED metrics from Go services with no code changes at all, see [eBPF instrumentation](/opentelemetry/ebpf).
</Tip>

## 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.
* Confirm the endpoint includes a scheme. Without `http://`, the exporters attempt TLS against a plaintext Collector.
* The batch processors export on a background goroutine — keep the `defer shutdown(...)` call so short-lived programs flush before exit.

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 application exports straight to Bronto over OTLP/HTTP. The code in `otel.go` is unchanged — 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.

### Manual spans

There is no first-party GenAI auto-instrumentation package for Go, so set the attributes yourself around each model call:

```go theme={"dark"}
tracer := otel.Tracer("my-service")

ctx, span := tracer.Start(ctx, "chat gpt-4o-mini")
defer span.End()

span.SetAttributes(
    attribute.String("gen_ai.provider.name", "openai"),
    attribute.String("gen_ai.request.model", "gpt-4o-mini"),
    attribute.Int("gen_ai.usage.input_tokens", 33),
    attribute.Int("gen_ai.usage.output_tokens", 74),
    // Bronto flattens array attributes — this is searchable as finish_reasons.0
    attribute.StringSlice("gen_ai.response.finish_reasons", []string{"stop"}),
)
```

Message content (`gen_ai.input.messages` / `gen_ai.output.messages`) is opt-in by convention and off by default in the languages that have auto-instrumentation. Since you are setting attributes by hand, apply the same discipline — gate prompt and response content behind your own config flag rather than always sending it.

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