Skip to content

Go OpenTelemetry troubleshooting

Most Go instrumentation problems fall into three buckets: context that is not threaded through, spans that never flush because the process exits first, and an exporter that cannot reach the ingest endpoint. Work through them in that order.

Go carries the active span in context.Context. If the context is dropped anywhere in the call chain, the trace breaks at that point.

  • Reassign the context from Start. ctx, span := tracer.Start(ctx, "op") returns a new context. If you write _, span := or keep using the old context, child spans attach to the wrong parent or begin a new trace.
  • Use the request context, not context.Background(). Pass r.Context() (net/http), c.Request.Context() (Gin), or c.Request().Context() (Echo) into every downstream call. Starting downstream work from context.Background() silently orphans it.
  • Instrument outbound calls. A wrapped server still produces disconnected traces if its clients are not wrapped. Use otelhttp.NewTransport for HTTP clients and otelgrpc.NewClientHandler for gRPC clients, and pass the incoming context into each request.
  • Propagate into goroutines explicitly. A goroutine that closes over context.Background() loses the span. Capture the request context and pass it in.

The SDK batches spans and flushes them on an interval. If the process exits before a flush, the last spans never leave the host — this is the most common reason a short-lived job or a service that crashed shows no data.

  • Always defer the shutdown. The setupOTel function returns a shutdown function; defer it in main so the batch processor flushes.
  • Handle termination signals. os.Exit, a panic, or an unhandled SIGTERM skips deferred calls. Catch signals and call shutdown with a bounded context.
package main
import (
"context"
"log"
"os"
"os/signal"
"syscall"
"time"
"go.opentelemetry.io/otel"
)
func main() {
otel.SetErrorHandler(otel.ErrorHandlerFunc(func(err error) {
log.Printf("otel error: %v", err)
}))
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
shutdown, err := setupOTel(ctx, "checkout-service", "production")
if err != nil {
log.Fatal(err)
}
<-ctx.Done()
stop()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := shutdown(shutdownCtx); err != nil {
log.Printf("otel shutdown: %v", err)
}
}

If context and shutdown are correct but nothing arrives, the exporter cannot deliver.

  • Endpoint scheme. For the gRPC exporter over TLS on port 443, OTEL_EXPORTER_OTLP_ENDPOINT must include the scheme: https://ingest.<region>.osuite.io:443. A bare host or http:// on 443 fails the handshake.
  • Header format. OTEL_EXPORTER_OTLP_HEADERS must be exactly x-osuite-ingest-token=<your-ingest-token>. A wrong header name or a rotated token is rejected at ingest.
  • Surface the errors. Register an error handler with otel.SetErrorHandler (shown above). OTLP export failures — auth, DNS, TLS, connection refused — are reported through it instead of failing silently.
  • Confirm spans are produced. To rule out the network, swap in the stdout exporter (go.opentelemetry.io/otel/exporters/stdout/stdouttrace) temporarily. If spans print, the SDK works and the problem is delivery, not instrumentation.
  • Match exporter to protocol. The bootstrap uses otlptracegrpc (gRPC). If you switch to otlptracehttp, the endpoint path and port change accordingly — do not mix a gRPC exporter with an HTTP endpoint.

No data in Osuite after a few minutes? Work through these checks.

  • Endpoint — confirm the exporter targets exactly ingest.<region>.osuite.io:443 for your region, over TLS.
  • Token — confirm the ingest header carries a valid <your-ingest-token> and has not been rotated.
  • Pipeline — confirm the signal you expect (traces, logs, or metrics) is wired into an active pipeline with the otlp/osuite exporter attached.
  • Export errors — check the application or Collector logs for OTLP export failures (auth, DNS, TLS, connection refused).
  • Network — confirm the host has outbound access to the ingest endpoint on port 443.
  • Timing — allow up to a minute for the first data to appear before assuming a failure.

Still stuck? Ask the Investigation Agent or contact support.