Gateway OTEL Lifecycle
The LLM gateway emits a lifecycle trace and matching OTEL log events for LLM and
relay proxy requests. The contract is intentionally request-centric: the root span
is the full gateway request, child spans represent lifecycle stages, and lifecycle
log events carry event.sequence so log backends can reconstruct in-request order
even when async plugin work is involved.
Instrumentation name for lifecycle traces and logs:
guardian.gateway.lifecycleSpan Shape
Section titled “Span Shape”gateway.request gateway.request.prepare gateway.plugin.invocation # request.start, when matching plugins run gateway.verification gateway.proxy gateway.plugin.invocation # proxy.pre, when matching plugins run gateway.upstream gateway.plugin.invocation # proxy.post, when matching plugins runRelay uses the same verify → hooks → forward path, but does not emit
gateway.proxy / gateway.upstream child spans in the current release (C07).
Request lifecycle + structured error logs are sufficient for operators to filter
relay traffic.
gateway.request gateway.request.prepare gateway.plugin.invocation # request.start, when matching plugins run gateway.verification gateway.plugin.invocation # proxy.pre / proxy.post when hooks match /v1/relayThe root gateway.request span includes:
viper.state_cidwhen the inbound request providesX-Viper-State-CIDgateway.traffic_kind=llm|relayhttp.route= provider template (/v1/anthropic_api/*, …) or/v1/relay- for relay after destination resolve:
relay.destination_host(hostname only) - for LLM only:
llm.provider,llm.operation(omitted on relay — not emitted empty)
request.end plugins are asynchronous. Their queued work carries W3C trace
propagation and a reserved event.sequence value into the worker so worker plugin
logs continue after the synchronous queue/request-end events. Worker plugin
invocation spans are emitted as gateway.plugin.invocation spans with
hook.name=request.end.
Lifecycle Logs
Section titled “Lifecycle Logs”Every lifecycle log has correlation_id. Logs also include event.name as a
durable attribute because some local Collector/file-exporter versions omit the
OTEL log record’s top-level event name field. Logs emitted on the synchronous
request path also carry a monotonic event.sequence for that request. Async
request.end plugin worker logs continue from the reserved sequence captured when
the queue item was written.
gateway.request.start attrs (shared): correlation_id, viper.state_cid, http.method, gateway.uri, gateway.traffic_kind attrs (LLM): llm.provider, llm.operation attrs (relay): (no llm.* attrs)
gateway.request.prepare.start attrs: correlation_idgateway.request.prepare.end attrs: correlation_id, llm.request_model, llm.stream_requested, gateway.route_supported, upstream.uri (llm.* prepare fields only when an LLM body was parsed)
gateway.verification.start attrs: correlation_id, viper.state_cidgateway.verification.end attrs: correlation_id, agent.id, signer.key_id, verification.outcome, verification.decision_code
gateway.proxy.start # LLM only attrs: correlation_id, agent.id, llm.provider, llm.operation, upstream.uri
gateway.plugin.invocation.start attrs: correlation_id, hook.name, plugin.id, plugin.mode, plugin.runtime, configuration.version, configuration.layer, plugin.order_indexgateway.plugin.invocation.end attrs: correlation_id, hook.name, plugin.id, plugin.outcome, plugin.decision_code, plugin.status_code, plugin.error_code, plugin.latency_msgateway.plugin.invocation.queued attrs: correlation_id, hook.name=request.end, configuration.version
gateway.upstream.start # LLM only attrs: correlation_id, llm.provider, llm.operation, upstream.urigateway.upstream.end # LLM only attrs: correlation_id, http.status_code, llm.response_model, llm.stream_observed
gateway.proxy.end # LLM only attrs: correlation_id, gateway.route_type, policy.decision_code, http.status_code
gateway.request.end attrs (shared): correlation_id, agent.id, gateway.route_type, policy.decision_code, http.status_code, gateway.duration_ms, gateway.traffic_kind attrs (relay): relay.destination_host (when destination was resolved)Error And Deny Semantics
Section titled “Error And Deny Semantics”Verification denials use verification.outcome=denied. Internal verification
failures use verification.outcome=error.
Plugin denials are policy outcomes, not span errors. gateway.plugin.invocation
spans are marked with OTEL error status only for plugin execution or context
failures, such as timeouts, runtime errors, and invalid context writes.
Proxy and request completion severities follow the response outcome:
INFOfor successful proxy responses.WARNfor local denials and other 4xx outcomes.ERRORfor upstream failures and 5xx outcomes.
Relay upstream transport failures and SSRF-floor destination_blocked denials
emit gateway.proxy.upstream.error from guardian.gateway.proxy with
gateway.traffic_kind=relay, gateway.route_type, policy.decision_code, and
relay.destination_host when known. Full destination URLs are not logged on this
path (path/query may carry secrets). Keep filtering on existing event name;
discriminate traffic kind via attrs.
Payload Policy
Section titled “Payload Policy”Lifecycle OTEL logs do not capture request bodies, response bodies, or raw headers. Payload inspection remains in the audit subsystem and plugin previews, where retention and redaction are controlled separately.