Repository navigation
feat: classify every error and name the build in logs and metrics - #13
Merged
Merged
Conversation
Every failed request now says who has to act (caller, provider, nexus, unclear), its error code, the provider's status and code, and, for internal errors, a fixed cause such as pii_filter_timeout. Requests rejected before any provider call log the account, model and provider, or the model the caller asked for. Messages and free text stay out of logs. nexus_gateway_errors_total counts failures by model, provider, phase, fault, code and upstream status, and nexus_gateway_build_info plus a version field on every log line tie each error to a release. Release images set the version at build time. Also fixes two metric bugs: every request was counted under the route "unmatched", and model names from callers that never resolved became metric labels. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Count requests the handlers reject before the generation service (missing key, unreadable or invalid body, confidential envelope checks) in nexus_gateway_errors_total. - A Nexus-side failure after the client left (PII analyzer, auth, routing, credit hold) is the client's cancellation, not a Nexus fault. - Errors may name their fault explicitly: an unregistered provider and a full confidential replay store are Nexus's, though they answer provider_unavailable. conflict, not_found, forbidden and already_exists are caller faults. - Name the cause of metering and router failures (unreachable, error status, bad response) and of Typesafe's missing key, which also stops telling clients the key is missing. - Log values that come from callers or providers only when they look like identifiers; anything else is logged as [redacted]. - Label metrics with the catalog's model ID, count fallbacks in nexus_gateway_fallbacks_total, and put the request ID on the PII filter warning. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Last week's production errors were hard to explain from the gateway's own output:
internal_errorresponses logged no cause. They turned out to be Presidio PII analyzer timeouts (1.5 s, fails closed), but that took correlating latencies by hand, because the dependency and cause metadata was never logged. A later automated diagnosis blamed metering instead, for the same reason.unsupported_model,invalid_field, …) logged no model or account, so "216 calls to a model ID that doesn't exist" could only be recovered from a Prometheus label.nexus_gateway_generations_total{outcome="error"}mixed caller mistakes (bad key, no balance, client cancel) with provider and Nexus failures, so an error rate could not be computed from it.This is the logging and metrics half of the gateway error classification work: every error should carry enough to point at its fix.
What changes
Classification.
domain.FaultOfsays who has to act on an error:caller,provider,nexus(our code, config or provider account) orunclear(provider_rejected_request: a bad request or a compatibility gap). It maps by error code; an error can name its fault explicitly where the code points elsewhere (an unregistered provider and a full confidential replay store answerprovider_unavailablebut are Nexus's). Unknown codes count asnexus.When the client has already left, a Nexus-side failure that follows (the PII analyzer, auth, routing or credit-hold call being canceled) is reported as
client_canceled, not as a Nexus fault.Logs.
request error/request failed,provider request failed,generation failedandprovider failed; trying fallbackcarryfault,error_code,gateway_statusand an allowlist of metadata. Values set by gateway code (dependency, cause, statuses) are logged as they are. Values that come from callers, providers or other services (upstream type and code, requested model, account, metering/router codes) are logged only when they look like identifiers, otherwise as[redacted]. Messages,reasonandupstream_errorare never logged.Errors that end a request before any provider call carry the account and catalog model, or the requested model ID when it didn't resolve. Internal errors and dependency failures set a fixed
cause:pii_filter_timeout,pii_filter_failedmetering_unreachable,metering_error_status,metering_bad_response,credit_hold_lostrouter_unreachable,router_error_status,router_bad_response,router_not_configured,router_empty_modelprovider_key_missing,provider_not_registeredreplay_store_full,untyped_errorThe
pii filter errorwarning now has the request ID. Typesafe's missing-key error now answers clients with the generic internal error, like the other adapters, instead of naming the missing key.Version. New
pkg/buildinfo(Version, Commit), set via-ldflags -Xinapps/gateway/Dockerfile. The release workflow passesVERSIONand the commit as build args. Every log line getsversion, andnexus_gateway_build_info{version,commit} 1is exported. Local builds reportdev.Metrics.
nexus_gateway_errors_total{endpoint,model,provider,phase,fault,error_code,upstream_status}:phaseisrequest(before any provider call, including what the handlers reject: missing key, unreadable or invalid body, confidential envelope checks),provider(provider failed before output) orstream(after output began).nexus_gateway_fallbacks_total{endpoint,model,provider,fallback_provider,fault,error_code}: primary failures that a fallback covered, which otherwise never show up as errors.nexus_gateway_http_requests_totallabelled every requestroute="unmatched".RequestIDhands the mux a copy of the request, sor.Patternnever reached the outer metrics middleware. The route now comes frommux.Handler(r).modellabel ongenerations_total, so any caller, even unauthenticated, could create unbounded series. It is nowunresolved, and resolved requests are labelled with the catalog's model ID.Not in this PR
Dockerfile.enclave) does not set the version yet. That build is reproducible and attested, so it deserves its own change.generations_totalby an unresolved model name;route="unmatched".Testing
go vet ./...,go test ./..., andgo test -race ./apps/gateway/... ./pkg/....client_canceled;-ldflags -X …Version=v9.9.9-testand ran it: every log line has"version":"v9.9.9-test"and/metricsexposesnexus_gateway_build_info{commit="abc123",version="v9.9.9-test"} 1.🤖 Generated with Claude Code