OpenTelemetry for Go
OpenTelemetry for Go uses an explicit SDK model — there's no auto-instrumentation agent. You add a small amount of setup code once, then sprinkle in middleware or driver wrappers wherever you want spans.
Unlike Java or Python, Go doesn't have bytecode manipulation — so there's no "magic" auto-instrumentation. In practice this is a one-time setup per service. Once wired up, the same SDK middleware covers every HTTP request, and driver wrappers handle every database call automatically.
Before You Begin
You'll need:
- A Go application (Go 1.20+)
- The URL of your CodeXray collector (e.g.,
https://your-codexray.example.com) - A project API key from the CodeXray UI (Project Settings → API Keys)
Step 1 — Add the OpenTelemetry Dependencies
go get \
go.opentelemetry.io/otel \
go.opentelemetry.io/otel/sdk \
go.opentelemetry.io/otel/sdk/trace \
go.opentelemetry.io/otel/sdk/resource \
go.opentelemetry.io/otel/exporters/otlp/otlptrace \
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp \
go.opentelemetry.io/otel/propagation \
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
The first block is the SDK + OTLP exporter. The last line adds the HTTP middleware that captures inbound/outbound HTTP traffic.
Step 2 — Initialise the Tracer
Create a small telemetry.go (or similar) alongside main.go:
package main
import (
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
)
func initTracer(ctx context.Context) (*sdktrace.TracerProvider, error) {
// OTLP exporter — reads OTEL_EXPORTER_OTLP_* env vars
exporter, err := otlptrace.New(ctx, otlptracehttp.NewClient())
if err != nil {
return nil, err
}
res, err := resource.New(ctx,
resource.WithAttributes(
semconv.ServiceName("your-service-name"),
),
)
if err != nil {
return nil, err
}
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithResource(res),
)
otel.SetTracerProvider(tp)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{}, propagation.Baggage{},
))
return tp, nil
}
Call initTracer from main():
ctx := context.Background()
tp, err := initTracer(ctx)
if err != nil { log.Fatal(err) }
defer tp.Shutdown(ctx)
Step 3 — Configure via Environment Variables
The OTLP exporter reads these automatically:
export OTEL_SERVICE_NAME="your-service-name"
export OTEL_EXPORTER_OTLP_ENDPOINT="https://your-codexray.example.com"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
export OTEL_EXPORTER_OTLP_HEADERS="x-api-key=your-project-api-key"
| Variable | Purpose |
|---|---|
OTEL_SERVICE_NAME | The name that appears in CodeXray's Applications list |
OTEL_EXPORTER_OTLP_ENDPOINT | Your CodeXray collector URL (no trailing slash) |
OTEL_EXPORTER_OTLP_PROTOCOL | http/protobuf (recommended) or grpc |
OTEL_EXPORTER_OTLP_HEADERS | Authentication — your project API key as x-api-key |
Step 4 — Instrument Your HTTP Handlers
Wrap your handlers with otelhttp.NewHandler — one line per route, or once at the server level:
import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
func main() {
// ... initTracer call ...
mux := http.NewServeMux()
mux.Handle("/hello", otelhttp.NewHandler(
http.HandlerFunc(helloHandler),
"hello",
))
http.ListenAndServe(":8080", mux)
}
With a router (chi, gorilla/mux, gin)
Middleware is one line:
// chi
r := chi.NewRouter()
r.Use(func(next http.Handler) http.Handler {
return otelhttp.NewHandler(next, "http.server")
})
// gin
r := gin.Default()
r.Use(otelgin.Middleware("your-service-name"))
// gorilla/mux
r := mux.NewRouter()
r.Use(otelmux.Middleware("your-service-name"))
Each router has its own contrib package — otelgin, otelmux, otelecho, etc. See OpenTelemetry Go Contrib for the full list.
Step 5 — Instrument Outbound HTTP Calls
Wrap your HTTP client's transport with otelhttp.NewTransport:
client := &http.Client{
Transport: otelhttp.NewTransport(http.DefaultTransport),
}
// Every call made with this client now produces spans
resp, err := client.Get("https://api.example.com/data")
Step 6 — Instrument Database Calls
Wrap your database driver with the OTel SQL wrapper:
go get github.com/uptrace/opentelemetry-go-extra/otelsql
import (
_ "github.com/lib/pq"
"github.com/uptrace/opentelemetry-go-extra/otelsql"
)
db, err := otelsql.Open("postgres", dsn)
Every SQL query now produces a span with the statement, target database, and latency. Works identically for MySQL, SQLite, and any other database/sql driver.
For gRPC, MongoDB, Redis, and Kafka, similar wrappers exist under OpenTelemetry Go Contrib.
Additional Resource Attributes
Attach environment, version, and any custom labels to every span:
res, _ := resource.New(ctx,
resource.WithAttributes(
semconv.ServiceName("your-service-name"),
semconv.ServiceVersion("1.3.0"),
semconv.DeploymentEnvironment("production"),
),
)
Or set OTEL_RESOURCE_ATTRIBUTES as an env var for the same effect without a redeploy:
export OTEL_RESOURCE_ATTRIBUTES="deployment.environment=production,service.version=1.3.0"
Verifying It's Working
- Build and run your service with the OTel env vars set
- Hit an instrumented endpoint (any route wrapped with
otelhttp.NewHandler) - In CodeXray, open Applications → find your service → Traces tab
- Traces should appear within a minute
If nothing shows up, the OTel SDK logs export failures explicitly — watch your service's stderr for OTel messages.
Custom Spans for Business Logic
Add spans around your own code whenever you want richer detail:
import "go.opentelemetry.io/otel"
tracer := otel.Tracer("my-service")
func processOrder(ctx context.Context, orderID string) error {
ctx, span := tracer.Start(ctx, "processOrder")
defer span.End()
span.SetAttributes(attribute.String("order.id", orderID))
// ... your logic, passing ctx down ...
return nil
}
Propagate ctx through every call — the SDK uses it to keep spans joined to their parent trace.
Next Steps
- Overview — the full Traces tab walkthrough
- OpenTelemetry for Java · for Python — instrument your other services
- eBPF-based Tracing — immediate coverage for services you can't modify