Skip to main content
GoModel exports OpenTelemetry traces and metrics for inbound HTTP requests (except operational endpoints, see below) and every outbound model provider call. Provider calls follow the GenAI semantic conventions, so a Jaeger, Grafana Tempo, Honeycomb, Datadog, or any other OTLP-compatible backend shows which model was called, through which provider, how long it took, and whether it failed. Prompts, responses, credentials, and error messages are never exported. For Prometheus scraping, see Prometheus Metrics; both can run at the same time.

Quick start

Export is off by default. Set OTEL_ENABLED=true and point the exporter at your collector:
GoModel logs opentelemetry enabled at startup together with the selected exporters. Exporters are asynchronous: the gateway starts even when the collector is down and retries in the background.
Without OTEL_EXPORTER_OTLP_ENDPOINT the SDK sends to http://localhost:4318 (http/protobuf) or localhost:4317 (grpc).

Configuration

OTEL_ENABLED is the only GoModel-specific switch. Everything else follows the standard OpenTelemetry environment variables, so GoModel is configured exactly like any other instrumented service. The common settings are also available under opentelemetry: in config.yaml; a variable set in the environment wins over the YAML value. A typical production setup:
Sampling only affects traces. Metrics are always complete.
Export headers such as authorization travel with every export request. Use an https:// endpoint (or a gRPC endpoint with TLS) whenever headers carry credentials; plaintext is acceptable only for a collector on the same host. GoModel logs a warning at startup when headers are configured and the effective endpoint is plaintext (http://, or gRPC with OTEL_EXPORTER_OTLP_INSECURE=true) and not a loopback address.

What is exported

HTTP server spans and metrics

Every request gets a SERVER span named after its route (for example POST /v1/chat/completions) with the standard http.request.method, http.route, http.response.status_code, and url.scheme attributes, plus the http.server.request.duration histogram and the request and response body size histograms. Trace context from the caller is honored, so a gateway span appears as a child of the calling application’s span whenever the client sends traceparent (or whichever headers OTEL_PROPAGATORS selects). /health, /health/ready, the Prometheus endpoint (METRICS_ENDPOINT), and /debug/pprof are excluded: they are polled, and would otherwise dominate the trace volume.

Provider call spans and GenAI metrics

Each logical call to a model provider is instrumented with: Metrics are histograms in seconds, carrying the same attributes: A buffered call produces a CLIENT span named <operation> <model>, such as chat gpt-5, nested under the HTTP server span. Retries and failovers to another provider are separate calls and therefore separate spans, so a request that failed over shows exactly which provider failed and which one answered. Streaming calls do not get a client span: the gateway can only observe when the stream was established, not when the model finished, and a span ending at the headers would misreport latency. The enclosing HTTP server span still covers the full stream lifetime as seen by the client. A stream that fails to establish, or ends before delivering its first chunk, gets a retrospective failure span, so errors are always traced.

Privacy

The exporter is designed so that telemetry can go to a third-party backend without leaking what flows through the gateway:
  • Request and response bodies, prompts, completions, and tool calls are never attached to spans or metrics.
  • Credentials and upstream error messages are never exported; failures carry only a status code or an error class.
  • client.address, network.peer.*, server.address, server.port, and user_agent.original are stripped from HTTP spans, and host-derived dimensions are excluded from HTTP metrics so a client cannot inflate metric cardinality through the Host header.

Reloading

gomodel --reload (SIGHUP) rebuilds the OpenTelemetry pipeline with the current environment, so exporter, sampling, and propagation changes apply without a restart. The previous pipeline is flushed before it is discarded.

Local collector example

A minimal docker-compose.yml that shows traces in Jaeger:
Send a request through the gateway and open http://localhost:16686.
Last modified on August 30, 2026