Skip to content

Tracing

go-zero uses OpenTelemetry for distributed tracing. Spans are created automatically for every inbound HTTP request, outbound zrpc call, and SQL query — no instrumentation code required for the happy path.

etc/app.yaml
Telemetry:
Name: order-api # service name shown in the trace UI
Endpoint: localhost:4317 # OTLP gRPC endpoint
Sampler: 1.0 # 1.0 = 100% sampling; use 0.1 in high-traffic production
Batcher: otlpgrpc # see Backends table below
LayerSpan details
Inbound HTTP requestURL, method, HTTP status code
Outbound zrpc callgRPC service + method name
SQL queries (via sqlx)query string, rows affected
Redis commandscommand name, key prefix

go-zero propagates trace context between services using the W3C TraceContext standard (traceparent header). When an API service calls an RPC service, the trace ID and span ID flow automatically — the entire call chain appears as a single trace in Jaeger or Zipkin.

Trace propagation

No code is needed on the RPC server side — the gRPC interceptor extracts the context from incoming metadata automatically.

Add application-level spans inside your logic:

import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
)
func (l *OrderLogic) processPayment(amount int64) error {
tracer := otel.Tracer("order-service")
ctx, span := tracer.Start(l.ctx, "process-payment")
defer span.End()
span.SetAttributes(
attribute.Int64("amount", amount),
attribute.String("currency", "USD"),
attribute.String("provider", "stripe"),
)
if err := chargeCard(ctx, amount); err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return err
}
return nil
}

When both logx and Telemetry are configured, go-zero injects the trace ID and span ID into every log line automatically:

{"level":"info","trace_id":"4bf92f3577b34da6a3ce929d0e0e4736","span_id":"00f067aa0ba902b7","msg":"order created","orderId":"ord_123"}

This means you can jump from a Jaeger trace span straight to the logs for that specific request.

StrategyConfigWhen to use
Always sampleSampler: 1.0Development, staging
10% sampleSampler: 0.1High-traffic production
Head-based ratioSampler: 0.01Very high throughput (>10k req/s)

A ratio sampler makes the decision at the trace root (the API gateway). All downstream spans in the same trace are automatically included or excluded together.

BackendBatcher valueEndpoint format
OTLP gRPCotlpgrpc (default)otel-collector:4317
OTLP HTTPotlphttphttp://otel-collector:4318
Zipkinzipkinhttp://zipkin:9411/api/v2/spans
Filefile(writes to local file)

Jaeger 1.35+ natively accepts OTLP. Use the all-in-one image with OTLP enabled:

Terminal window
docker run -d --name jaeger \
-e COLLECTOR_OTLP_ENABLED=true \
-p 16686:16686 \
-p 4317:4317 \
-p 4318:4318 \
jaegertracing/all-in-one:latest
# UI: http://localhost:16686
etc/app.yaml
Telemetry:
Name: order-api
Endpoint: localhost:4317
Sampler: 1.0
Batcher: otlpgrpc

For production, route traces through the OTel Collector to fan out to multiple backends:

etc/app.yaml
Telemetry:
Name: order-api
Endpoint: otel-collector:4317
Batcher: otlpgrpc
Sampler: 0.1

Set Sampler: 0 or remove the Telemetry block entirely to disable all tracing overhead.

Starting from go-zero v1.10.0, the jaeger batcher has been removed because the upstream OpenTelemetry Jaeger exporter is deprecated. Jaeger itself has adopted OTLP as its native protocol since v1.35, so you can continue using Jaeger — just switch to the OTLP exporter.

Step 1: Update the Docker Compose / Jaeger Deployment

Section titled “Step 1: Update the Docker Compose / Jaeger Deployment”

Make sure your Jaeger instance exposes the OTLP ports (4317 for gRPC, 4318 for HTTP):

docker-compose.yaml
services:
jaeger:
image: jaegertracing/all-in-one:latest
environment:
- COLLECTOR_OTLP_ENABLED=true
ports:
- "16686:16686" # Jaeger UI
- "4317:4317" # OTLP gRPC
- "4318:4318" # OTLP HTTP
restart: unless-stopped

Replace the old Jaeger-specific configuration with OTLP:

Telemetry:
Name: my-service
Endpoint: http://jaeger:14268/api/traces
Batcher: jaeger
Endpoint: jaeger:4317
Batcher: otlpgrpc
Sampler: 1.0

Or use OTLP HTTP:

Telemetry:
Name: my-service
Endpoint: http://jaeger:14268/api/traces
Batcher: jaeger
Endpoint: http://jaeger:4318
Batcher: otlphttp
Sampler: 1.0

Restart your service and open the Jaeger UI at http://localhost:16686. Your traces should appear exactly as before — the only difference is the transport protocol.

Before (< v1.10.0)After (>= v1.10.0)
Batcher: jaegerBatcher: otlpgrpc (recommended) or otlphttp
Endpoint: http://jaeger:14268/api/tracesEndpoint: jaeger:4317 (gRPC) or http://jaeger:4318 (HTTP)
Jaeger image: any versionJaeger image: 1.35+ (for native OTLP support)