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.
This page covers instrumenting a Go application with the OpenTelemetry SDK to send logs and traces to Bronto over OTLP/HTTP via a local OTel Collector. Logs are bridged from log/slog — no log statement changes needed. Traces are added by configuring a tracer provider alongside the log provider.
If you don’t have a Collector and want to export directly from your application to Bronto, see Direct export to Bronto at the bottom of this page.
Prerequisites
Install dependencies
go get go.opentelemetry.io/otel/sdk/log
go get go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp
go get go.opentelemetry.io/contrib/bridges/otelslog
go get go.opentelemetry.io/otel/sdk/resource
go get go.opentelemetry.io/otel/semconv/v1.26.0
| Package | Purpose |
|---|
sdk/log | LoggerProvider and processors |
exporters/otlp/otlplog/otlploghttp | OTLP/HTTP exporter |
contrib/bridges/otelslog | Bridges log/slog into OTel |
sdk/resource | Resource builder |
semconv/v1.26.0 | Standard attribute key constants |
The otelslog bridge replaces the default slog handler. All slog.Info(), slog.Warn(), and slog.Error() calls are forwarded to the OTel pipeline automatically.
package logging
import (
"context"
"log/slog"
"go.opentelemetry.io/contrib/bridges/otelslog"
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp"
"go.opentelemetry.io/otel/sdk/log"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
)
func Configure(ctx context.Context) (shutdown func(context.Context) error, err error) {
res, err := resource.New(ctx,
resource.WithAttributes(
semconv.ServiceName("my-service"),
semconv.ServiceNamespace("my-team"),
semconv.DeploymentEnvironment("production"),
),
)
if err != nil {
return nil, err
}
exporter, err := otlploghttp.New(ctx,
otlploghttp.WithEndpoint("localhost:4318"),
otlploghttp.WithInsecure(),
)
if err != nil {
return nil, err
}
provider := log.NewLoggerProvider(
log.WithResource(res),
log.WithProcessor(log.NewBatchProcessor(exporter)),
)
// Replace the default slog handler with the OTel bridge
slog.SetDefault(slog.New(otelslog.NewHandler("my-service",
otelslog.WithLoggerProvider(provider))))
return provider.Shutdown, nil
}
The otlploghttp.New call in the snippet above connects to the OTel Collector on localhost:4318 without TLS. If your Collector runs on a different host or port, update WithEndpoint accordingly.
To use TLS, remove WithInsecure() and ensure your Collector is configured with a valid certificate.
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.WithAttributes in the setup above.
Complete example
package main
import (
"context"
"log/slog"
"os"
"os/signal"
"myapp/logging"
)
func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
shutdown, err := logging.Configure(ctx)
if err != nil {
slog.Error("failed to configure OTel logging", "error", err)
os.Exit(1)
}
defer shutdown(context.Background())
slog.Info("Application started")
slog.Warn("Low disk space", "disk_free_gb", 2.1)
slog.Error("Database connection failed", "error", "timeout")
}
defer shutdown(context.Background()) flushes any buffered log records before the process exits.
Verify log collection
After running your application, open the Search page in Bronto. Filter by the dataset name you set in service.name — your log records 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.
logging.Configure is called before the first slog statement.
- The
shutdown function is called before process exit to flush buffered records.
Traces
Install tracing dependencies
go get go.opentelemetry.io/otel
go get go.opentelemetry.io/otel/sdk/trace
go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp
package logging
import (
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
func ConfigureTracing(ctx context.Context, res *resource.Resource) (shutdown func(context.Context) error, err error) {
exporter, err := otlptracehttp.New(ctx,
otlptracehttp.WithEndpoint("localhost:4318"),
otlptracehttp.WithInsecure(),
)
if err != nil {
return nil, err
}
provider := sdktrace.NewTracerProvider(
sdktrace.WithResource(res),
sdktrace.WithBatcher(exporter),
)
otel.SetTracerProvider(provider)
return provider.Shutdown, nil
}
Pass the same res from Configure so both signals share service.name and service.namespace.
Creating spans
tracer := otel.Tracer("my-service")
ctx, span := tracer.Start(ctx, "process-payment")
defer span.End()
span.SetAttributes(attribute.Float64("payment.amount", 99.99))
slog.InfoContext(ctx, "Processing payment") // trace_id and span_id injected automatically
Use slog.InfoContext (with the span context) to ensure the active span’s IDs are propagated to the log record.
Complete example
package main
import (
"context"
"log/slog"
"os"
"os/signal"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"myapp/logging"
)
func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
logShutdown, res, err := logging.Configure(ctx)
if err != nil {
slog.Error("failed to configure logging", "error", err)
os.Exit(1)
}
defer logShutdown(context.Background())
traceShutdown, err := logging.ConfigureTracing(ctx, res)
if err != nil {
slog.Error("failed to configure tracing", "error", err)
os.Exit(1)
}
defer traceShutdown(context.Background())
tracer := otel.Tracer("my-service")
ctx, span := tracer.Start(ctx, "handle-request")
defer span.End()
span.SetAttributes(attribute.String("http.method", "GET"))
slog.InfoContext(ctx, "Handling request") // trace_id + span_id attached automatically
}
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:
// Logs
logExporter, err := otlploghttp.New(ctx,
otlploghttp.WithEndpoint("ingestion.eu.bronto.io"), // or ingestion.us.bronto.io
otlploghttp.WithURLPath("/v1/logs"),
otlploghttp.WithHeaders(map[string]string{"x-bronto-api-key": "<YOUR_API_KEY>"}),
)
// Traces
traceExporter, err := otlptracehttp.New(ctx,
otlptracehttp.WithEndpoint("ingestion.eu.bronto.io"), // or ingestion.us.bronto.io
otlptracehttp.WithURLPath("/v1/traces"),
otlptracehttp.WithHeaders(map[string]string{"x-bronto-api-key": "<YOUR_API_KEY>"}),
)
| 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 for how to create a key with ingestion permissions. No other changes to the rest of the setup are required.