Quick start
Export is off by default. SetOTEL_ENABLED=true and point the exporter at
your collector:
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:
What is exported
HTTP server spans and metrics
Every request gets aSERVER 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:
gomodel.client.empty_responses counts buffered chat and Responses API calls
that returned 200 with no choices, no output, or no token usage. Those calls
look successful in the histograms above, so alert on this counter instead. Its error.type is
no_choices, no_output (a completed Responses API call with no output
items), or no_usage (content with zero token usage).
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, anduser_agent.originalare stripped from HTTP spans, and host-derived dimensions are excluded from HTTP metrics so a client cannot inflate metric cardinality through theHostheader.
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 minimaldocker-compose.yml that shows traces in Jaeger:
http://localhost:16686.