Appearance
Go
Traces and metrics for net/http services in about 30 lines, plus logs through log/slog.
Before you start
You need a stream and ingest key for each signal. See OpenTelemetry → Before you start.
1. Install
bash
go get go.opentelemetry.io/otel \
go.opentelemetry.io/otel/sdk \
go.opentelemetry.io/otel/sdk/metric \
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp \
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp \
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp2. Configure
The exporters read their endpoints and keys from the environment:
bash
OTEL_SERVICE_NAME=checkout-api
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://ws.latency.app/v1/telemetry/traces
OTEL_EXPORTER_OTLP_TRACES_HEADERS=authorization=Bearer%20lit_your_traces_key
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=https://ws.latency.app/v1/telemetry/metrics
OTEL_EXPORTER_OTLP_METRICS_HEADERS=authorization=Bearer%20lit_your_metrics_key3. Set up the SDK
Add a setup function:
go
// otel.go
package main
import (
"context"
"errors"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
func setupOTel(ctx context.Context) (func(context.Context) error, error) {
traceExp, err := otlptracehttp.New(ctx)
if err != nil {
return nil, err
}
tp := sdktrace.NewTracerProvider(sdktrace.WithBatcher(traceExp))
otel.SetTracerProvider(tp)
metricExp, err := otlpmetrichttp.New(ctx)
if err != nil {
return nil, err
}
mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(sdkmetric.NewPeriodicReader(metricExp)))
otel.SetMeterProvider(mp)
return func(ctx context.Context) error {
return errors.Join(tp.Shutdown(ctx), mp.Shutdown(ctx))
}, nil
}Then call it from main and wrap your handler:
go
func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
shutdown, err := setupOTel(ctx)
if err != nil {
log.Fatal(err)
}
mux := http.NewServeMux()
mux.HandleFunc("/", hello) // your routes
srv := &http.Server{Addr: ":8080", Handler: otelhttp.NewHandler(mux, "server")}
go srv.ListenAndServe()
<-ctx.Done()
srv.Shutdown(context.Background())
shutdown(context.Background()) // sends the last batch
}Imports: context, log, net/http, os, os/signal, syscall and go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp.
4. Check it's working
Run your service, make a few requests, then open Telemetry in the Console and pick your traces stream. Traces show up within seconds. Metrics follow within a minute.
Custom spans
go
tracer := otel.Tracer("checkout")
ctx, span := tracer.Start(ctx, "charge-card")
defer span.End()
span.SetAttributes(attribute.String("order.id", order.ID))Custom metrics
go
meter := otel.Meter("checkout")
orders, _ := meter.Int64Counter("orders.placed")
duration, _ := meter.Float64Histogram("checkout.duration", metric.WithUnit("ms"))
orders.Add(ctx, 1, metric.WithAttributes(attribute.String("plan", "pro")))
duration.Record(ctx, 182, metric.WithAttributes(attribute.String("route", "/pay")))Imports: go.opentelemetry.io/otel/attribute and go.opentelemetry.io/otel/metric.
Logs
Send log/slog output to your logs stream with the log exporter and the slog bridge.
bash
go get go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp \
go.opentelemetry.io/otel/log \
go.opentelemetry.io/otel/sdk/log \
go.opentelemetry.io/contrib/bridges/otelslogbash
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://ws.latency.app/v1/telemetry/logs
OTEL_EXPORTER_OTLP_LOGS_HEADERS=authorization=Bearer%20lit_your_logs_keyIn setupOTel, before the return:
go
logExp, err := otlploghttp.New(ctx)
if err != nil {
return nil, err
}
lp := sdklog.NewLoggerProvider(sdklog.WithProcessor(sdklog.NewBatchProcessor(logExp)))
global.SetLoggerProvider(lp)Add lp.Shutdown(ctx) to the errors.Join. Then log as usual:
go
logger := otelslog.NewLogger("checkout")
logger.InfoContext(ctx, "order placed", "order.id", order.ID)Imports: go.opentelemetry.io/otel/log/global, sdklog "go.opentelemetry.io/otel/sdk/log" and go.opentelemetry.io/contrib/bridges/otelslog.