For most use cases Grafana Labs recommends Beyla, eBPF network-level auto-instrumentation, which is easy to set up and supports all languages and frameworks.
If you need process-level telemetry for Go, follow this documentation to set up the upstream OpenTelemetry SDK for Go for Application Observability.
Install the SDK
Before you begin ensure you have a Go 1.22+ development environment a Go application to instrument.
Run the following command in the project folder:
sh
go get "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"\"go.opentelemetry.io/contrib/instrumentation/runtime"\"go.opentelemetry.io/otel"\"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"\"go.opentelemetry.io/otel/exporters/otlp/otlptrace"\"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"\"go.opentelemetry.io/otel/sdk"\"go.opentelemetry.io/otel/sdk/metric"
Instrument your application
Create an otel.go file with the following bootstrap code to initialize the SDK to export telemetry:
Go
package main
import("context""errors""go.opentelemetry.io/contrib/instrumentation/runtime""go.opentelemetry.io/otel""go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp""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/metric""go.opentelemetry.io/otel/sdk/trace""log""time")// setupOTelSDK bootstraps the OpenTelemetry pipeline.// If it does not return an error, make sure to call shutdown for proper cleanup.funcsetupOTelSDK(ctx context.Context)(shutdown func(context.Context)error, err error){var shutdownFuncs []func(context.Context)error// shutdown calls cleanup functions registered via shutdownFuncs.// The errors from the calls are joined.// Each registered cleanup will be invoked once.
shutdown =func(ctx context.Context)error{var err errorfor_, fn :=range shutdownFuncs {
err = errors.Join(err,fn(ctx))}
shutdownFuncs =nilreturn err
}// handleErr calls shutdown for cleanup and makes sure that all errors are returned.
handleErr :=func(inErr error){
err = errors.Join(inErr,shutdown(ctx))}
prop := propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{},)
otel.SetTextMapPropagator(prop)
traceExporter, err := otlptrace.New(ctx, otlptracehttp.NewClient())if err !=nil{returnnil, err
}
tracerProvider := trace.NewTracerProvider(trace.WithBatcher(traceExporter))if err !=nil{handleErr(err)return}
shutdownFuncs =append(shutdownFuncs, tracerProvider.Shutdown)
otel.SetTracerProvider(tracerProvider)
metricExporter, err := otlpmetrichttp.New(ctx)if err !=nil{returnnil, err
}
meterProvider := metric.NewMeterProvider(metric.WithReader(metric.NewPeriodicReader(metricExporter)))if err !=nil{handleErr(err)return}
shutdownFuncs =append(shutdownFuncs, meterProvider.Shutdown)
otel.SetMeterProvider(meterProvider)
err = runtime.Start(runtime.WithMinimumReadMemStatsInterval(time.Second))if err !=nil{
log.Fatal(err)}return}
Edit your main.go to set up the SDK and instrument the HTTP server using the go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp instrumentation library:
Go
package main
import("context""errors""log""net""net/http""os""os/signal""time""go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp")funcmain(){if err :=run(); err !=nil{
log.Fatalln(err)}}funcrun()(err error){// Handle SIGINT (CTRL+C) gracefully.
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)deferstop()// Set up OpenTelemetry.
otelShutdown, err :=setupOTelSDK(ctx)if err !=nil{return}// Handle shutdown properly so nothing leaks.deferfunc(){
err = errors.Join(err,otelShutdown(context.Background()))}()// Start HTTP server.
srv :=&http.Server{
Addr:":8080",
BaseContext:func(_ net.Listener) context.Context {return ctx },
ReadTimeout: time.Second,
WriteTimeout:10* time.Second,
Handler:newHTTPHandler(),}
srvErr :=make(chanerror,1)gofunc(){
srvErr <- srv.ListenAndServe()}()// Wait for interruption.select{case err =<-srvErr:// Error when starting HTTP server.returncase<-ctx.Done():// Wait for first CTRL+C.// Stop receiving signal notifications as soon as possible.stop()}// When Shutdown is called, ListenAndServe immediately returns ErrServerClosed.
err = srv.Shutdown(context.Background())return}funcnewHTTPHandler() http.Handler {
mux := http.NewServeMux()// handleFunc is a replacement for mux.HandleFunc// which enriches the handler's HTTP instrumentation with the pattern as the http.route.
handleFunc :=func(pattern string, handlerFunc func(http.ResponseWriter,*http.Request)){// Configure the "http.route" for the HTTP instrumentation.
handler := otelhttp.WithRouteTag(pattern, http.HandlerFunc(handlerFunc))
mux.Handle(pattern, handler)}// Register handlers.handleFunc("/rolldice", rolldice)// Add HTTP instrumentation for the whole server.
handler := otelhttp.NewHandler(mux,"/")return handler
}
Test your instrumentation
To test if you’ve successfully instrumented your application, run your application, generate some traffic, and you should see metrics and logs outputted to the console.
sh
# use http instead of https# needed because of https://github.com/open-telemetry/opentelemetry-go/issues/4834exportOTEL_EXPORTER_OTLP_INSECURE=“true”
go run .