Collect gateway telemetry
The AI Gateway exports OpenTelemetry traces and metrics, and can serve its
metrics to Prometheus. Configure both under telemetry in your platform values.
Telemetry is off by default. This guide covers connecting a collector, counting
token usage, and adding request and identity context to spans so you can break
traffic down by project, team, or department.
Every signal carries the service name ai-gateway-v2, and every gateway metric
carries the label implementation="v2". Filter on that label when the legacy AI
Gateway reports into the same metric store.
For the legacy gateway, see legacy gateway telemetry.
Export traces and metrics to a collector
Set telemetry.otlp.endpoint to your collector's OpenTelemetry Protocol (OTLP)
HTTP receiver, then turn on each signal you want:
ai-gateway-v2:
telemetry:
otlp:
enabled: true
endpoint: 'otel-collector.observability.svc:4318'
tracing:
enabled: true
telemetry.otlp.enabled pushes metrics and telemetry.tracing.enabled pushes
traces, both to telemetry.otlp.endpoint. The gateway exports over OTLP/HTTP,
usually port 4318, and uses TLS unless you set telemetry.otlp.insecure: true
for a collector on a trusted private network.
Each model request produces an ai_gateway.chat_completion span carrying
attributes from the OpenTelemetry generative AI semantic conventions, such as
gen_ai.request.model and gen_ai.provider.name. When the provider's response
reports token usage, the span also carries gen_ai.usage.input_tokens and
gen_ai.usage.output_tokens, plus gen_ai.usage.cost when budgets are
configured. The gateway sets user.id to the caller's identity.
To share one collector across the platform, set
global.stacklok.telemetry.otlp.enabled and
global.stacklok.telemetry.otlp.endpoint instead. The gateway falls back to the
global endpoint when it has no local one.
Tracing samples every request by default. To cut span volume, set
telemetry.tracing.samplingRate to the fraction of root spans to keep, between
0.0 and 1.0. The default, 0, keeps every root span. The gateway always
keeps a request whose inbound traceparent is sampled, whatever the rate. A
cost platform fed by these spans sees only the sampled requests, so it no longer
counts all traffic.
Send traces to a separate destination
Some cost and usage platforms, such as CloudZero, ingest these spans directly at
a fixed URL. Set telemetry.otlp.tracesEndpoint to send traces there. The
gateway posts to the URL exactly as written, without appending /v1/traces, and
telemetry.otlp.endpoint keeps receiving metrics if you set it.
If the destination requires an API key, store the OTLP headers string under the
headers key of a Secret in the platform namespace:
kubectl create secret generic cloudzero-otlp-headers \
-n <NAMESPACE> \
--from-literal=headers='Authorization=<API_KEY>'
Then name the Secret in telemetry.otlp.headersSecretRef:
ai-gateway-v2:
telemetry:
tracing:
enabled: true
otlp:
tracesEndpoint: 'https://telemetry.cloudzero.com/v2/telemetry/<SINK>'
headersSecretRef:
name: cloudzero-otlp-headers
The headers require an https:// URL for tracesEndpoint. The gateway sends
the headers on every OTLP signal, so the chart refuses them while OTLP metrics
push is on, locally or through global.stacklok.telemetry.otlp. Scrape metrics
with Prometheus instead, or relay traces
through your own collector that holds the credential.
Scrape metrics with Prometheus
Set telemetry.prometheus.enabled to serve /metrics on a dedicated port,
9464 by default (change it with telemetry.metrics.port):
ai-gateway-v2:
telemetry:
prometheus:
enabled: true
The gateway pods carry prometheus.io/scrape annotations, so a Prometheus
server that discovers targets by annotation picks them up automatically. The
metrics listener is unauthenticated. When the chart's network policy is on, only
pods in the namespaces under networkPolicy.ingress.monitoringNamespaces
(default monitoring) can reach it.
Record token usage
The gateway records the stacklok.ai_gateway.token.usage counter for every
request whose provider reports usage, labeled with token_type (input or
output), provider, and model. Output tokens include reasoning tokens for
every provider. In Prometheus, the counter appears as
stacklok_ai_gateway_token_usage_total. For example, this query returns tokens
per second by model:
sum by (model, token_type) (rate(stacklok_ai_gateway_token_usage_total{implementation="v2"}[5m]))
To break usage down by a header the caller sends, map it under
telemetry.headerLabels with the closed set of values to keep:
ai-gateway-v2:
telemetry:
headerLabels:
- header: x-team
attribute: team
permitted:
- payments
- platform
A value outside permitted becomes other, which keeps the number of metric
series bounded however the header varies. Per-user and per-request identifiers
belong on spans instead.
Request rate, latency, and status codes come from the standard
http.server.request.duration histogram, labeled with http.route and
http.response.status_code. It covers every HTTP request, including ones
refused by authentication.
Add request headers to spans
For per-request context that has too many distinct values for a metric label,
such as a project or ticket ID, telemetry.spanAttributes copies a header the
caller sends onto that request's span:
ai-gateway-v2:
telemetry:
tracing:
enabled: true
spanAttributes:
- header: x-cz-context-project
attribute: cz.context.project
You can configure up to 20 entries, and tracing must be on. A request that omits the header gets no attribute, and values longer than 256 bytes are truncated. The value is whatever the client sent, so treat it as an attribution hint.
Header names are lowercase. Names the gateway already sets are reserved:
anything under gen_ai. (except gen_ai.client.app), stacklok., or
budget., plus the caller identity attributes such as user.id and
user.email. To keep secrets and personal data out of your traces, the chart
refuses credential and personal-data headers such as authorization, cookie,
x-api-key, x-user-email, and x-forwarded-for.
Attribute traffic to organizational groups
The gateway can look up each caller in the directory and attach the labels of
their derived groups, such as department or cost_center, to request spans
and audit records. You can then break activity down by department or cost center
without changing any client. You define these labels on derived groups in the
directory; see
Label derived groups.
Point the gateway at the directory's gRPC address, give it a credential to present, and list the labels to resolve:
ai-gateway-v2:
directory:
endpoint: '<ENTERPRISE_MANAGER_SERVICE>.<NAMESPACE>.svc:9091'
insecure: true
groupsResolutionIssuer: '<IDP_ISSUER_URL>'
tokenProjection:
enabled: true
groupLabels:
- department
- cost_center
Use the same directory connection values as in
Configure the AI Gateway.
The directory must allow the gateway's ServiceAccount to call it. The platform
chart adds system:serviceaccount:<NAMESPACE>:<RELEASE_NAME>-ai-gateway-v2 to
enterprise-manager.grpc.callerAuth.saSubjectAllowlist by default when the AI
Gateway is enabled. If you override that list, include this entry.
The directory accepts gRPC calls from AI Gateway pods in the Enterprise Manager's namespace, which is where the platform chart installs both.
For requests that use a virtual API key, the gateway resolves labels through the
key's owner. directory.groupsResolutionIssuer is the issuer the directory
knows those owners under. The chart refuses to render groupLabels without it.
How labels appear
Labels appear as attributes on request spans under their own names and under
subjects on audit records. They're kept off metrics because per-caller values
would multiply metric series. The gateway forwards each label to its governance
checks as a header, such as X-Department, and removes that header before the
request reaches the provider.
When a caller belongs to two derived groups with different values for the same label, the gateway omits that label and logs a warning naming it.
The gateway looks up every request, without caching. If the directory doesn't
answer, the gateway refuses the request with 503 and logs
directory group resolution failed; refusing request, so it never forwards an
incomplete identity.
Next steps
- Forward audit logs to send these records, group labels included, to your security information and event management (SIEM) system.
- Budgets and pricing to turn attributed usage into enforced spend limits.
Related information
- Collect telemetry for MCP workloads covers standing up the collector, Prometheus, and Grafana.
Troubleshooting
No traces or metrics reach the collector
Confirm the gateway has a destination for each signal. Metrics go to
telemetry.otlp.endpoint when telemetry.otlp.enabled is set. Traces go to
telemetry.otlp.tracesEndpoint when set, otherwise to
telemetry.otlp.endpoint, and only when telemetry.tracing.enabled is set. The
gateway speaks OTLP over HTTP, so point it at the collector's HTTP receiver,
usually port 4318.
With the chart's network policy on and networkPolicy.allowExternalEgress set
to false, the chart opens egress on the ports named in
telemetry.otlp.endpoint and telemetry.otlp.tracesEndpoint, to any
destination. Confirm that each port in those values matches the port the
collector listens on.
Spans carry no group labels
Confirm that tracing is on and that the label is listed in
directory.groupLabels. The gateway resolves only the labels you list. If one
label is missing while the others appear, check the caller's group memberships
in the console for two groups with conflicting values.