feat: add core.response.get_response_source() API for response origin classification#13224
Merged
nic-6443 merged 16 commits intoApr 16, 2026
Merged
Conversation
Add core.response.get_response_source(ctx) API that returns one of three values to identify where a response originated: - "apisix": response generated by APISIX Lua code (route not found, plugin rejection, upstream not configured, etc.) - "nginx": error generated by NGINX proxy module (connection refused, read timeout, DNS resolution failure, etc.) - "upstream": real HTTP response returned by the upstream service Implementation: - core.response.exit() now marks ctx._resp_source = "apisix" for 4xx/5xx - init.lua sets ctx._apisix_proxied = true after before_proxy phase, before proxy_pass dispatch - get_response_source() uses the last token of $upstream_header_time to distinguish NGINX errors (header_time = "-") from upstream responses (header_time has numeric value) Plugin updates: - opentelemetry: adds apisix.response_source span attribute, uses ngx.status for http.status_code (fixes nil status for gateway errors) - prometheus: adds response_source label to http_status counter - zipkin: adds apisix.response_source span tag, uses ngx.status
There was a problem hiding this comment.
Pull request overview
Adds a new core.response.get_response_source(ctx) API and propagates its classification into tracing (OpenTelemetry/Zipkin) and Prometheus metrics, enabling users to distinguish gateway-generated errors from upstream responses.
Changes:
- Introduces response-source classification (
apisix/nginx/upstream) incore.response, with_resp_sourceand_apisix_proxiedmarkers. - Updates OpenTelemetry and Zipkin spans to tag
apisix.response_sourceand to usengx.statusfor status reporting. - Extends Prometheus
http_statuscounter with aresponse_sourcelabel; adds a new core test file for the API.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
apisix/core/response.lua |
Implements get_response_source() and marks _resp_source on core.response.exit() for error codes. |
apisix/init.lua |
Marks requests as proxied (_apisix_proxied) after before_proxy and before NGINX proxy dispatch. |
apisix/plugins/opentelemetry.lua |
Adds apisix.response_source span attribute and switches status reporting to ngx.status. |
apisix/plugins/zipkin.lua |
Adds apisix.response_source span tag and switches status reporting to ngx.status. |
apisix/plugins/prometheus/exporter.lua |
Adds response_source label to the http_status counter and emits it in log phase. |
t/core/response-source.t |
Adds tests for get_response_source() and _resp_source behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Use gmatch(%S+) loop instead of str_match pattern to correctly handle spaces in comma-separated upstream_header_time (e.g. '- , -') - Guard ctx.var access with nil check for defensive safety - Improve integration tests to verify response_source via serverless-pre-function log phase plugin - Add tests for spaced separators and nil ctx.var edge cases
ctx.var may return upstream_header_time as a number (not string) for single numeric values. Apply tostring() before gmatch to avoid 'attempt to index local (a number value)' error.
…EST 17 - opentelemetry4-bugfix-pb-state.t TEST 3: add expected apisix.response_source attribute - response-source.t TEST 17: add diagnostic logging to debug connection refused case
The new response_source label in apisix_http_status metric needs to be accounted for in existing test assertions.
Add core.response.set_response_source(ctx, source) API for plugins that bypass NGINX proxy (e.g. ai-proxy) to explicitly mark whether the response came from upstream. The ai-proxy base now sets source to 'upstream' when the LLM provider responds, ensuring correct classification even for 429/5xx responses from upstream.
Switch from ngx.var (which broke unit tests using fake ctx) to pcall-wrapped ctx.var access. This handles both unit tests with mock objects and integration tests where resty.ngxvar may error in NGINX error paths.
Also adds diagnostic logging in TEST 17 to debug connection refused classification.
$upstream_header_time returns 0 (not "-") for connection refused, making it impossible to distinguish from fast upstream responses. $upstream_bytes_received is 0 when no data was received from upstream (connection refused, timeout) and > 0 when upstream sent a response, providing a reliable signal for response source classification.
nic-6443
requested review from
AlinsRan,
Baoyuantop,
membphis,
moonming and
shreemaan-abhishek
April 15, 2026 00:54
membphis
reviewed
Apr 15, 2026
membphis
requested changes
Apr 15, 2026
…es_received Switch back to $upstream_header_time but read via ngx.var directly instead of ctx.var. The lua-var-nginx-module FFI path clamps header_time -1 to 0 via ngx_max(ms, 0), making connection refused indistinguishable from fast upstream responses. ngx.var preserves the "-" sentinel. Changes: - Extract get_last_upstream_token() helper for parsing comma-separated NGINX upstream variable strings (unit-testable) - Use ngx_var.upstream_header_time in get_response_source() - Cache computed result in ctx._resp_source for subsequent calls - Replace mock-based unit tests with: - Parser helper unit tests (TEST 4-10) - Integration tests covering upstream 502, plugin rejection (TEST 18-19) - Add TEST 15 verifying set_response_source is not overridden by resp_exit
membphis
approved these changes
Apr 16, 2026
shreemaan-abhishek
approved these changes
Apr 16, 2026
AlinsRan
approved these changes
Apr 16, 2026
wistefan
pushed a commit
to wistefan/apisix
that referenced
this pull request
Jun 16, 2026
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.
Summary
Add
core.response.get_response_source(ctx)API to classify HTTP responses as:"apisix"— generated by APISIX Lua code (route not found, plugin rejection,core.response.exit())"nginx"— error from NGINX proxy module (connection refused, upstream timeout)"upstream"— real HTTP response from the upstream serviceAlso add
core.response.set_response_source(ctx, source)for plugins that bypass NGINX proxy (e.g. ai-proxy).Detection mechanism
Uses
ngx.var.upstream_header_time(notctx.var) to detect whether upstream sent response headers:"-"= no headers received (connection refused, timeout) →"nginx""upstream"Why ngx.var instead of ctx.var:
lua-var-nginx-module's FFI path clampsheader_timefrom-1to0viangx_max(ms, 0), losing the"-"sentinel that NGINX uses to distinguish "no response" from "fast response".ngx.varpreserves the raw string.Integration with observability plugins
apisix.response_sourcespan attribute; usengx.statusinstead ofupstream_statusfor span status coderesponse_sourcelabel tohttp_statusmetricapisix.response_sourcespan tag; usengx.statusfor status tagTests
19 test cases covering:
get_last_upstream_token) for single values, retries, spaces, colons, empty/nilresp_exitmarking,set_response_sourcepriority