OpenTelemetry Logs for Go

This document covers OpenTelemetry Logs for Go, focusing on the standard library's slog package (Go 1.21+). To learn how to install and configure OpenTelemetry Go SDK, see Getting started with OpenTelemetry Go.

Prerequisites

Make sure your exporter is configured before you start instrumenting code. Follow Getting started with OpenTelemetry Go or set up Direct OTLP Configuration first.

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

Overview

OpenTelemetry provides two approaches for collecting logs in Go:

  1. Log bridges (recommended): Integrate with existing logging libraries to automatically capture logs and correlate them with traces.
  2. Logs API: Use the native OpenTelemetry Logs API directly for maximum control.

Log bridges are the recommended approach because they allow you to use familiar logging APIs while automatically adding trace context (trace_id, span_id) to your logs.

Slog integration

The slog package is Go's standard library for structured logging (Go 1.21+). OpenTelemetry provides an official bridge via otelslog.

Installation

shell
go get go.opentelemetry.io/contrib/bridges/otelslog

Basic configuration

go
import (
    "log/slog"
    "go.opentelemetry.io/contrib/bridges/otelslog"
)

// Set otelslog as the default global logger
slog.SetDefault(otelslog.NewLogger("app_or_package_name"))

Complete example

go
package main

import (
    "context"
    "fmt"
    "log/slog"

    "github.com/uptrace/uptrace-go/uptrace"
    "go.opentelemetry.io/contrib/bridges/otelslog"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/trace"
)

func main() {
    ctx := context.Background()

    // Initialize OpenTelemetry
    uptrace.ConfigureOpentelemetry(
        // copy your project DSN here or use UPTRACE_DSN env var
        // uptrace.WithDSN("<FIXME>"),

        uptrace.WithServiceName("myservice"),
        uptrace.WithServiceVersion("1.0.0"),
    )
    defer uptrace.Shutdown(ctx)

    // Configure slog with OTel handler
    slog.SetDefault(otelslog.NewLogger("myservice"))

    // Create tracer
    tracer := otel.Tracer("myservice")

    // Start a traced operation
    ctx, span := tracer.Start(ctx, "process-request")
    defer span.End()

    // Logs automatically include trace context
    slog.InfoContext(ctx, "Processing request",
        "user_id", "12345",
        "action", "login")

    slog.ErrorContext(ctx, "Authentication failed",
        "reason", "invalid_credentials")

    fmt.Println("Trace URL:", uptrace.TraceURL(trace.SpanFromContext(ctx)))
}

See OpenTelemetry Slog and GitHub example for details.

Other logging libraries

If you're using a different logging library, see these dedicated guides:

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 your observability backend.

Manual correlation

If you can't use log bridges, manually inject trace context:

go
import "go.opentelemetry.io/otel/trace"

func logWithContext(ctx context.Context, msg string) {
    span := trace.SpanFromContext(ctx)
    if span.SpanContext().IsValid() {
        spanCtx := span.SpanContext()
        log.Printf("%s trace_id=%s span_id=%s",
            msg,
            spanCtx.TraceID().String(),
            spanCtx.SpanID().String(),
        )
    } else {
        log.Printf("%s", msg)
    }
}

Best practices

Use context-aware logging

Always use context-aware logging methods to include trace context:

go
// Good: Includes trace context
slog.InfoContext(ctx, "User logged in", "user_id", userId)

// Bad: Missing trace context
slog.Info("User logged in", "user_id", userId)

Use structured fields

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

go
slog.InfoContext(ctx, "Database query executed",
    "query_type", "SELECT",
    "table", "users",
    "duration_ms", 45,
    "rows_affected", 1,
)

Avoid logging sensitive data

Never log passwords, tokens, or PII:

go
// Bad: Logging sensitive data
slog.Info("User login", "password", password)

// Good: Redact sensitive fields
slog.Info("User login", "user_id", userID)

What's next?