Tracing Providers

Choose and configure trace exporters for your application

The tracing package supports multiple providers for exporting traces. Choose the provider that best fits your environment and infrastructure.

Available Providers

ProviderUse CaseNetwork RequiredBest For
NoopDefault, no tracesNoTesting, disabled tracing
StdoutConsole outputNoDevelopment, debugging
OTLP (gRPC)OpenTelemetry collectorYesProduction (preferred)
OTLP (HTTP)OpenTelemetry collectorYesProduction (alternative)

Basic Configuration

tracer := tracing.MustNew(
    tracing.WithServiceName("my-service"),
    tracing.WithNoop(),
)
defer tracer.Shutdown(context.Background())
tracer := tracing.MustNew(
    tracing.WithServiceName("my-service"),
    tracing.WithStdout(),
)
defer tracer.Shutdown(context.Background())
tracer := tracing.MustNew(
    tracing.WithServiceName("my-service"),
    tracing.WithServiceVersion("v1.0.0"),
    tracing.WithOTLP("localhost:4317"),
)
if err := tracer.Start(context.Background()); err != nil {
    log.Fatal(err)
}
defer tracer.Shutdown(context.Background())
tracer := tracing.MustNew(
    tracing.WithServiceName("my-service"),
    tracing.WithServiceVersion("v1.0.0"),
    tracing.WithOTLPHTTP("http://localhost:4318"),
)
if err := tracer.Start(context.Background()); err != nil {
    log.Fatal(err)
}
defer tracer.Shutdown(context.Background())

Noop Provider

The noop provider doesn’t export any traces. It’s the default when no provider is configured.

When to Use

  • Testing environments where tracing isn’t needed
  • Temporarily disabling tracing without code changes
  • Safe default for new projects

Configuration

tracer := tracing.MustNew(
    tracing.WithServiceName("my-service"),
    tracing.WithNoop(),
)
defer tracer.Shutdown(context.Background())

Or simply omit the provider option (noop is the default):

tracer := tracing.MustNew(
    tracing.WithServiceName("my-service"),
    // No provider = Noop
)

Behavior

  • Spans are created but not recorded
  • No network calls or file I/O
  • Minimal performance overhead
  • Safe for production if tracing is disabled

Stdout Provider

The stdout provider prints traces to standard output in a human-readable format.

When to Use

  • Local development and debugging
  • Troubleshooting span creation and attributes
  • Testing trace propagation
  • Quick validation of tracing logic

Configuration

tracer := tracing.MustNew(
    tracing.WithServiceName("my-service"),
    tracing.WithStdout(),
)
defer tracer.Shutdown(context.Background())

Output Format

Traces are printed as pretty-printed JSON to stdout:

{
  "Name": "GET /api/users",
  "SpanContext": {
    "TraceID": "3f3c5e4d...",
    "SpanID": "a1b2c3d4...",
    "TraceFlags": "01"
  },
  "Parent": {
    "TraceID": "3f3c5e4d...",
    "SpanID": "e5f6g7h8..."
  },
  "SpanKind": "Server",
  "StartTime": "2025-01-18T10:15:30.123Z",
  "EndTime": "2025-01-18T10:15:30.456Z",
  "Attributes": [
    {
      "Key": "http.method",
      "Value": {"Type": "STRING", "Value": "GET"}
    }
  ]
}

Limitations

  • Not for production: Output can be noisy and slow
  • No persistence: Traces are only printed, not stored
  • No visualization: Use an actual backend for trace visualization

OTLP Provider (gRPC)

The OTLP gRPC provider exports traces to an OpenTelemetry collector using the gRPC protocol.

When to Use

  • Production environments
  • OpenTelemetry collector infrastructure
  • Jaeger, Zipkin, or other OTLP-compatible backends
  • Best performance and reliability

Basic Configuration

tracer := tracing.MustNew(
    tracing.WithServiceName("my-service"),
    tracing.WithServiceVersion("v1.0.0"),
    tracing.WithOTLP("localhost:4317"),
)

// Start is required for OTLP providers
if err := tracer.Start(context.Background()); err != nil {
    log.Fatal(err)
}

defer tracer.Shutdown(context.Background())

Secure Connection (TLS)

By default, OTLP uses TLS:

tracer := tracing.MustNew(
    tracing.WithServiceName("my-service"),
    tracing.WithOTLP("collector.example.com:4317"),
    // TLS is enabled by default
)

Insecure Connection (Development)

For local development without TLS:

tracer := tracing.MustNew(
    tracing.WithServiceName("my-service"),
    tracing.WithOTLP("localhost:4317", tracing.OTLPInsecure()),
)

Configuration Options

import "rivaas.dev/tracing"

// Secure (production)
tracing.WithOTLP("collector.example.com:4317")

// Insecure (development)
tracing.WithOTLP("localhost:4317", tracing.OTLPInsecure())

OTLP Provider (HTTP)

The OTLP HTTP provider exports traces to an OpenTelemetry collector using the HTTP protocol.

When to Use

  • Alternative to gRPC when firewalls block gRPC
  • Simpler infrastructure without gRPC support
  • HTTP-only environments
  • Debugging with curl/httpie

Configuration

tracer := tracing.MustNew(
    tracing.WithServiceName("my-service"),
    tracing.WithServiceVersion("v1.0.0"),
    tracing.WithOTLPHTTP("http://localhost:4318"),
)

// Start is required for OTLP providers
if err := tracer.Start(context.Background()); err != nil {
    log.Fatal(err)
}

defer tracer.Shutdown(context.Background())

HTTPS Endpoint

Use HTTPS for secure connections:

tracer := tracing.MustNew(
    tracing.WithServiceName("my-service"),
    tracing.WithOTLPHTTP("https://collector.example.com:4318"),
)

Endpoint Format

The endpoint should include the protocol:

// HTTP (insecure - development only)
tracing.WithOTLPHTTP("http://localhost:4318")

// HTTPS (secure - production)
tracing.WithOTLPHTTP("https://collector.example.com:4318")

Provider Comparison

Performance

ProviderLatencyThroughputCPUMemory
Noop~10nsUnlimitedMinimalMinimal
Stdout~100µsLowLowLow
OTLP (gRPC)~1-2msHighLowMedium
OTLP (HTTP)~2-3msMediumLowMedium

Use Case Matrix

// Development
tracer := tracing.MustNew(
    tracing.WithServiceName("my-api"),
    tracing.WithStdout(), // ← See traces in console
)

// Testing
tracer := tracing.MustNew(
    tracing.WithServiceName("my-api"),
    tracing.WithNoop(), // ← No tracing overhead
)

// Production (recommended)
tracer := tracing.MustNew(
    tracing.WithServiceName("my-api"),
    tracing.WithOTLP("collector:4317"), // ← gRPC to collector
)

// Production (HTTP alternative)
tracer := tracing.MustNew(
    tracing.WithServiceName("my-api"),
    tracing.WithOTLPHTTP("https://collector:4318"), // ← HTTP to collector
)

Switching Providers

Only one provider can be configured at a time. Attempting to configure multiple providers results in a validation error:

// ✗ Error: multiple providers configured
tracer, err := tracing.New(
    tracing.WithServiceName("my-service"),
    tracing.WithStdout(),
    tracing.WithOTLP("localhost:4317"), // Error!
)
// Returns: "validation errors: provider: multiple providers configured"

To switch providers, use environment variables or configuration:

func createTracer(env string) *tracing.Tracer {
    opts := []tracing.Option{
        tracing.WithServiceName("my-service"),
        tracing.WithServiceVersion("v1.0.0"),
    }
    
    switch env {
    case "production":
        opts = append(opts, tracing.WithOTLP("collector:4317"))
    case "development":
        opts = append(opts, tracing.WithStdout())
    default:
        opts = append(opts, tracing.WithNoop())
    }
    
    return tracing.MustNew(opts...)
}

OpenTelemetry Collector Setup

For OTLP providers, you need an OpenTelemetry collector.

Docker Compose Example

version: '3.8'
services:
  otel-collector:
    image: otel/opentelemetry-collector:latest
    command: ["--config=/etc/otel-collector-config.yaml"]
    volumes:
      - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
    ports:
      - "4317:4317"   # OTLP gRPC
      - "4318:4318"   # OTLP HTTP
      - "13133:13133" # health_check

Collector Configuration

Basic otel-collector-config.yaml:

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:

exporters:
  logging:
    loglevel: debug
  # Add your backend (Jaeger, Zipkin, etc.)
  jaeger:
    endpoint: jaeger:14250
    tls:
      insecure: true

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [logging, jaeger]

Provider Selection Guide

Choose Noop When:

  • Tracing is disabled via feature flags
  • Running in CI/CD without trace backend
  • Performance testing without observability overhead

Choose Stdout When:

  • Developing locally and need to see traces
  • Debugging span creation and attributes
  • Quick validation of tracing setup

Choose OTLP (gRPC) When:

  • Deploying to production
  • Need high throughput and low latency
  • Using OpenTelemetry collector
  • Standard production setup

Choose OTLP (HTTP) When:

  • gRPC is blocked by firewalls
  • Simpler infrastructure requirements
  • Need HTTP-friendly debugging
  • Backend only supports HTTP

Next Steps

  • Learn Configuration options for service metadata and sampling
  • Set up Middleware for automatic HTTP tracing
  • Explore Examples for production-ready configurations