Skip to content

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/otelhttp

2. 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_key

3. 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/otelslog
bash
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://ws.latency.app/v1/telemetry/logs
OTEL_EXPORTER_OTLP_LOGS_HEADERS=authorization=Bearer%20lit_your_logs_key

In 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.

© 2026 Latency Labs LLC