Go OpenTelemetry instrumentation
Go services are instrumented in source. Unlike the JVM or .NET, Go compiles to a static binary with no runtime an agent can attach to and rewrite, so there is no drop-in auto-instrumentation agent. You add the OpenTelemetry SDK once in a small bootstrap file, then wrap your HTTP or gRPC entry points with the contrib instrumentation middleware. This page is the shared setup that every framework guide below builds on.
The instrumentation model
Section titled “The instrumentation model”- SDK bootstrap — a single
otel.gofile initialises aTracerProvider, sets the trace-context propagator, and attaches the resource that identifies your service. You call it once frommain. - Framework middleware — the OpenTelemetry contrib modules wrap your router or gRPC server so every inbound request becomes a span automatically.
- Custom spans — anywhere you want more detail, you take a tracer from the global provider and start your own spans. See custom instrumentation.
Install the SDK
Section titled “Install the SDK”Add the API, the SDK, and the OTLP trace exporter to your module.
go get go.opentelemetry.io/otelgo get go.opentelemetry.io/otel/sdkgo get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpcCreate the bootstrap file
Section titled “Create the bootstrap file”Create otel.go next to your main package. It reads the ingest endpoint and token from the standard OTEL_EXPORTER_OTLP_* environment variables, so no credentials live in source.
package main
import ( "context"
"go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "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.37.0")
func setupOTel(ctx context.Context, serviceName, environment string) (func(context.Context) error, error) { res, err := resource.New(ctx, resource.WithAttributes( semconv.ServiceName(serviceName), attribute.String("service.environment", environment), ), ) if err != nil { return nil, err }
exporter, err := otlptracegrpc.New(ctx) 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.Shutdown, nil}Call it at the top of main and defer the returned shutdown so the batch processor flushes before the process exits.
func main() { ctx := context.Background() shutdown, err := setupOTel(ctx, "checkout-service", "production") if err != nil { log.Fatalf("otel setup: %v", err) } defer func() { if err := shutdown(context.Background()); err != nil { log.Printf("otel shutdown: %v", err) } }()
// ... start your server ...}The service.name and service.environment attributes are how Osuite groups your traces into a service and separates environments. Skipping the shutdown call is the most common reason the last spans never arrive — see troubleshooting.
Set the ingest environment variables
Section titled “Set the ingest environment variables”Point the exporter at your Osuite region and supply the ingest token.
export OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.osuite.io:443"export OTEL_EXPORTER_OTLP_HEADERS="x-osuite-ingest-token=<your-ingest-token>"Choose your framework
Section titled “Choose your framework”The bootstrap above is identical across frameworks. Pick your server to wire in the request middleware.
Verify in Osuite
Section titled “Verify in Osuite”What you should see
After you wire in a framework and send a request, the service appears in APM within a minute under the service.name you set, tagged with its service.environment.