Skip to main content
GreenSlope

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:

RouterInstrumentation
chi, gorilla/mux, stdlibgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
gin-gonic/gingo.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin
gofiber/fibergo.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