Go setup
Picks up from the Go quickstart. Read that first.
OTLP over gRPC
The quickstart uses otlptracehttp. For high-volume services, gRPC is
slightly more efficient — fewer bytes on the wire and a persistent
connection:
import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
exp, err := otlptracegrpc.New(ctx,
otlptracegrpc.WithEndpoint("ingest.greenslope.io:443"),
otlptracegrpc.WithHeaders(map[string]string{
"x-greenslope-key": os.Getenv("GREENSLOPE_INGEST_KEY"),
}),
)Difference at normal traffic levels: negligible. Pick HTTP for easier debugging; pick gRPC when you measure the CPU savings and they matter.
Propagators
Set the W3C tracecontext propagator so traces survive crossing service boundaries:
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{},
))Without this, an incoming request from a JS service won't be linked to its parent span — you'll see two disconnected traces instead of one.
Middleware for third-party routers
otelhttp works with net/http and anything built on it (chi, gorilla).
For other routers you need the matching package:
| Router | Instrumentation |
|---|---|
chi, gorilla/mux, stdlib | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp |
gin-gonic/gin | go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin |
gofiber/fiber | go.opentelemetry.io/contrib/instrumentation/github.com/gofiber/fiber/otelfiber |
Each has its own middleware helper; the pattern is the same (wrap the top-level handler/engine).
Manual spans
For custom work inside a handler, start a child span from the request context:
func checkoutHandler(w http.ResponseWriter, r *http.Request) {
tracer := otel.Tracer("checkout")
ctx, span := tracer.Start(r.Context(), "checkout.process")
defer span.End()
if err := processOrder(ctx); err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
http.Error(w, err.Error(), 500)
return
}
}The span's context is passed down via ctx, and child spans created
from it are attached to the same trace.
Graceful shutdown
Call TracerProvider.Shutdown on exit so in-flight spans are flushed
before the process ends:
ctx, cancel := signal.NotifyContext(context.Background(),
syscall.SIGINT, syscall.SIGTERM)
defer cancel()
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = shutdown(shutdownCtx)Five seconds is enough for the batch processor to flush any queued spans; increase if you run at very high batch sizes.
Related