指标与日志
AISIX AI 网关会公开聚合指标、逐请求日志、响应头和可导出的用量事件。这些信号共同反映服务健康状况,并将调用方可见的响应关联到产生该响应的模型、路由和策略结果。
选择遥测来源
请先选择与运维问题匹配的来源;需要深入调查问题时,再按请求、模型或服务提供方关联各类信号。
| 运维问题 | 首选来源 | 提供的信息 |
|---|---|---|
| 各网关实例的流量是否健康? | Prometheus 指标 | 请求速率、延迟分布、Token 和成本计数、策略结果、路由健康、缓存行为和导出器投递健康。 |
| 单个请求发生了什么? | 访问日志 | 结构化请求字段,包括状态、延迟、模型、服务提供方、请求 ID,以及可用时的路由结果。 |
| 调用应用可以观察到什么? | 响应头 | 受支持路由上的请求关联、缓存结果、重试时机和选中目标提示。 |
| 请求记录可以存储到哪里、如何分析或用于核算? | 用量事件 | 通过可观测性导出器投递的逐次尝试结果和消耗记录。 |
抓取 Prometheus 指标
Prometheus 指标是观察流量趋势、延迟、Token 计数、成本计数、限流结果、缓存行为和导出器投递健康状态的最佳起点。
AISIX 默认通过专用指标监听器在 /metrics 提供 Prometheus 指标。你可以通过启动期可观测性设置修改路径或禁用该端点。
该端点设计上不做认证。请保持专用指标监听器私有。
在启动配置中配置 Prometheus 暴露:
observability:
metrics:
prometheus:
enabled: true
path: "/metrics"
专用监听器默认绑定到 0.0.0.0:9090。如果 Prometheus 应从其他接口或端口抓取指标,请设置不同的监听地址。
抓取默认指标端点:
curl -sS "http://127.0.0.1:9090/metrics"
AISIX 会在每次抓取时发布配置状态。其他指标族会在首次观测时注册,因此流量指标可能不会在启动后立即出现。请先发送一次模型请求,再检查 aisix_requests_total 和 aisix_tokens_consumed_total 等序列。
AISIX 使用 aisix_ 前缀输出原生指标名称。使用直方图序列计算跨网关实例的延迟百分位数,并使用请求计数器分析成功率和路由情况。精确指标名称、标签范围和 PromQL 示例请参见指标参考。
区分请求计数与尝试计数
请求和上游尝试是两种不同的计量单位,把两者混为一谈是数字看起来自相矛盾的最常见原因。一次客户端请求可能发起多次上游调用:对同一目标的重试,或在模型组内故障转移到下一个目标。AISIX 会分别统计这两种单位,且统计位置不同。
| 单位 | 统计位置 | 一个样本或一行代表什么 |
|---|---|---|
| 请求 | aisix_proxy_requests_total、aisix_llm_requests_total | 一次客户端请求,状态是调用方实际收到的状态码。 |
| 尝试 | aisix_deployment_requests_total 及部署指标族 | 对某一个目标模型的一次上游调用。 |
| 尝试 | aisix_usage_events_emitted_total | 一条发出的用量事件,AISIX 对每次尝试各发一条。 |
| 尝试 | 用量事件记录,以及基于其构建的用量日志 | 一次尝试,通过 request_id 与同一请求的其他尝试关联,并按 attempt_index 排序。 |
需要牢记的推论是:被故障转移救回的失败尝试在请求计数器中是不可见的。 调用方收到的是 200,因此请求计数器记录 status="200",仅此而已。第一个目标返回的 502 存在于部署计数器和用量日志中,作为它自己的一次尝试。
因此,当一个模型组中只有一个目标损坏、其余健康时,网关的用量日志完全可能显示数万条 5xx 记录,而 sum(increase(aisix_proxy_requests_total{status=~"5.."}[24h])) 只返回几百。日志统计的是失败的尝试,指标统计的是看到失败的调用方。两者都没有错,也不应该趋于一致。
请按各查询真正回答的问题选用:
# 最终以服务端错误结束的请求——调用方实际经历的情况。
sum(increase(aisix_proxy_requests_total{status=~"5.."}[24h]))
# 发生过故障转移、但最终仍以服务端错误结束的请求。
sum(increase(aisix_proxy_requests_total{status=~"5..", is_fallback="true"}[24h]))
# 失败的上游尝试,按目标聚合——包含后续被故障转移救回的那些。
# 这是 5xx 用量日志行数在尝试层面的对应视图,范围限于通过模型组下发的端点。
sum(increase(aisix_deployment_failure_responses_total[24h])) by (model)
# 成功救回请求的故障转移,按模型组和实际到达的目标聚合。
sum(increase(aisix_routing_successful_fallbacks_total[24h])) by (model, fallback_model)
# AISIX 打算发出的用量事件,以及因队列已满或关闭而丢失的事件。
sum(increase(aisix_usage_events_emitted_total{status_code="5xx"}[24h]))
sum(increase(aisix_usage_event_drops_total[24h]))
在断定两个来源互相矛盾之前,请先确认它们覆盖的是同一批数据:
- 抓取覆盖范围。 请求计数器不带环境标签,因此除非服务该环境的每个网关实例都被抓取,否则来自用量记录的单环境数字无法与全网关范围的 PromQL 结果相比较。用
sum by (job, instance) (...)可以看出哪些实例贡献了数据,再与实际在运行的实例对照。 - 时间窗口覆盖范围。
increase(...[24h])只统计该区间内实际存在的样本。如果实例在窗口中途重启过,或 Prometheus 的保留期短于该窗口,结果覆盖的时间就短于用量日志筛选的时间。把原始计数器按同一区间画成曲线,就能同时看出这两类缺口。 - 投递。 用量记录通过导出器进入存储,队列过载时会丢弃事件而不是阻塞请求处理。
aisix_usage_event_drops_total界定了这部分损失:记录数可能少于发出计数,但不会多于它。 - 从未离开网关的尝试。 deployment 系列统计的是上游调用,因此没有产生上游调用的尝试会被有意排除在外:被目标自身限流拒绝的尝试,以及在请求仍在组装阶段就被拒绝的尝试——例如凭证不可用、缺少
model_name、api_base缺失或格式非法。这些尝试仍会出现在用量日志和aisix_usage_events_emitted_total中。因此这只是 deployment 尝试数低于用量日志尝试数的原因之一,而不是全部:上文提到的统计范围是另一个原因(deployment 系列只覆盖经模型组下发的端点,而用量事件还包含直连模型的尝试),本清单中的抓取、窗口和投递缺口同样会影响差额。请逐项隔离验证,不要把整个差额都当作网关内拒绝。不过这类排除有一个明确特征:配置错误的目标只会表现为失败的请求,而完全不产生 deployment 失败——服务提供方从未被请求过,因此没有任何数据归因到它。
收集访问日志
访问日志描述单个代理请求。AISIX 会通过进程 logger 将其写入标准错误流,因此它们会出现在运行时收集的容器或进程日志中。
在启动配置中配置进程日志:
observability:
log_level: "info"
access_log: true
进程环境可以通过 RUST_LOG 环境变量覆盖已配置的日志级别。
每个代理请求会写出一条访问日志条目,无论请求成功、失败或提前终止。条目会包含 method、path、status、latency、provider、model、API Key ID、request ID、Token 计数和路由结果等字段(如果相关值可用)。
条目的写出时机因响应类型而异,这也决定了它能携带哪些字段。非流式请求在请求结束时写出,因此条目包含网关已解析出的全部内容。流式请求在响应打开时写出,此时流还没有被消费——因此条目既没有 Token 计数,也没有服务提供方响应 ID,因为这两者都还不存在。流式请求的这些数值由下文的用量事件承载。
如果网关在写出条目时已经拿到该值,条目还会带上 provider_request_id:服务提供方自己返回的响应对象 ID,例如 OpenAI 的 chat.completion.id、Anthropic 的消息 id 或 Responses API 的 resp_…。服务提供方的控制台和技术支持渠道正是按这个 ID 检索一次调用的。只要没有可记录的 ID,该字段就会被省略而不是留空,因此可以按字段是否存在进行过滤。除上面的流式情况外,还包括:请求在下发之前就被拒绝(例如护栏拦截)、响应由缓存命中返回,以及服务提供方响应本身就不带 ID 的端点(如 Embedding、音频和图像生成)。
对于 ID 无法进入访问日志条目的调用,网关会单独输出一条 provider call completed 日志,其中包含 request_id、attempt_index、attempt_kind 和 provider_request_id。每一次返回了 ID 的服务提供方调用写一条——因此流式响应会产生一条,而在流中途发生故障转移的请求会按其发起的服务提供方调用各产生一条。可通过 request_id 将它们与访问日志条目关联,并通过 attempt_index 区分重试或故障转移请求中的不同调用。
如果调用方在网关发送响应头之前断开连接,也会生成一条日志,状态码为 499、error_kind="client_disconnected",且不包含模型或 Token 字段。网关会在处理请求期间解析这些字段,因此请求如此提前终止时尚不可用。此类请求也会计入 aisix_proxy_client_cancelled_requests_total。如果调用方在响应传输中途断开,则不会以这种方式记录,因为该请求已经有正常状态码和用量事件。
因超过 proxy.request_body_limit_bytes 而被拒绝的请求会以 aisix::body_limit 为日志目标生成第二条日志,包含 declared_content_length、configured_limit_bytes、drained_bytes 和 drain_outcome。可通过 request_id 将其与访问日志条目关联。
drain_outcome 表示网关如何结束对被拒绝请求体的读取,并决定调用方最终看到什么。completed 表示调用方发送了声明的全部内容,因此可以读取 413 响应。cap_reached、timeout 和 client_read_error 表示读取提前停止,因此调用方通常会看到连接关闭。completed 以 info 级别记录;另外三种结果以 warn 级别记录,并按每种结果每秒最多一条进行限流。因此,请使用 aisix_proxy_request_body_limit_rejections_total 查看总量。
当前版本中 access_log 字段为保留字段。代理处理器仍会输出结构化访问日志,但没有单独的访问日志格式或 sink 设置。当日志需要离开网关主机时,请使用运行时日志流水线收集标准错误流。
将响应与遥测关联
响应头提供调用方可见的关联和路由提示。在受支持路径上,它们可以标识请求、缓存结果、重试时机或选中的目标。
使用请求 ID 和其它受支持的响应头,将调用方可见的响应关联到访问日志或导出的记录。每条代理路由适用的响应头范围请参见响应头与错误码。
一次调用会带着两个 ID,二者不能相互替代:
| ID | 由谁分配 | 用途 |
|---|---|---|
request_id | 由网关分配,每个请求一个;调用方自带 ID 且被 AISIX 采用时则由调用方决定(见下文)。通过 x-aisix-request-id 响应头返回。 | 在网关访问日志、用量事件和控制台的日志页面中定位该请求。它对服务提供方没有意义。 |
provider_request_id | 由服务提供方在响应体中给出(前提是它会返回)。按 attempt 记录在该次尝试的用量事件上,以及上文所述的日志条目中。 | 在服务提供方自己的控制台中定位同一次调用,或提供给服务提供方技术支持。 |
调用方报障时通常手上只有 request_id——网关在每个响应上都会返回它。先用它查到该请求,再从实际提供响应的那次尝试中读取 provider_request_id,然后拿这个 ID 去找服务提供方。如果调用方保留了完整响应体,也可能直接从中读到服务提供方的 ID,但仅限于会返回该 ID 的端点。
复用自己的请求 ID
如果你的服务已经为该业务调用生成了请求 ID,直接把它发给 AISIX,AISIX 就会采用这个 ID,而不再自行生成。该 ID 随即成为这次请求在各处的身份标识:x-aisix-request-id 响应头、访问日志和这次请求产生的每一条用量事件上的 request_id,以及服务提供方收到的 x-aisix-request-id。
这样一来,你排查一次请求所用的各条线索,都以自己应用日志里已有的 ID 为键——网关访问日志、导出的用量事件,以及 AISIX Cloud 的请求日志都是如此,无需再维护第二套映射关系。
通过 x-aisix-request-id 发送:
# AISIX_PROXY 为网关源站地址;请省略末尾斜杠和端点路径。
export AISIX_PROXY="YOUR_AISIX_GATEWAY_ORIGIN"
curl "$AISIX_PROXY/v1/chat/completions" \
-H "Authorization: Bearer $AISIX_API_KEY" \
-H "Content-Type: application/json" \
-H "x-aisix-request-id: req_abc123-orders-svc" \
-d '{"model": "gpt-4o-mini", "messages": [{"role": "user", "content": "Hello"}]}' -i
响应会回显同一个值:
HTTP/1.1 200 OK
x-aisix-request-id: req_abc123-orders-svc
发生重试或故障转移的请求会为每次尝试各产生一条用量事件,它们全部携带你的 ID,因此按该 ID 过滤得到的是完整的调用链,而不只是最终成功的那次尝试。
可接受的取值
ID 长度为 1–256 字节且仅包含可见 ASCII 字符(! 到 ~)时按原值使用:不能含空格、控制字符或非 ASCII 字符。UUID、ULID、req_abc123 这类带前缀的 ID,以及 nginx $request_id 中的十六进制 ID 都符合要求。
超出该范围的取值会被忽略,AISIX 转而生成一个 UUID,与完全不发送 ID 时的行为一致。请求本身绝不会因为关联 ID 而被拒绝。
AISIX 不要求 ID 唯一。为两次不同的请求发送相同的 ID,会让它们在所有以该 ID 为键的线索中无法区分,因此请为每次请求生成新的 ID。
选择接受的请求头
默认情况下 AISIX 只读取自己的 x-aisix-request-id。通过 proxy.request_id.accept_headers 修改:
proxy:
request_id:
accept_headers: ["x-aisix-request-id", "x-request-id"]
请求头按列出的顺序依次查找,第一个可接受的取值胜出,因此该列表同时也是优先级顺序。仅使用环境变量的部署通过 AISIX_PROXY__REQUEST_ID__ACCEPT_HEADERS 以逗号分隔设置该列表。
默认不接受 x-request-id 是有意为之。反向代理、Ingress 控制器和负载均衡器会为它们转发的每个请求都打上该请求头,因此在 AISIX 位于它们之后的部署中启用它,意味着关联 ID 来自你的基础设施而非发起调用的服务。当 AISIX 是第一跳,或者你确实希望按代理分配的 ID 来追踪时,再把它加进来。
设置 accept_headers: [] 可完全忽略调用方提供的 ID,始终自行生成。
如果某个名称不是合法的 HTTP 请求头名称,网关会启动失败,而不是静默跳过。
导出用量事件
用量事件是受支持代理路径输出的逐次尝试记录。发生重试或故障转移的请求会生成多条具有相同 request_id 的事件,并按 attempt_index 排序。因此统计这些记录得到的是尝试数而非请求数——在拿记录条数与请求指标作比较之前,请先阅读区分请求计数与尝试计数。每条事件都会包含请求结果、消耗详情、调用方请求的模型别名,以及网关能够观测到时处理该次尝试的解析模型。由响应缓存返回的响应会把命中层记录在 cache_hit_layer(exact 或 semantic),语义命中还会把匹配相似度记录在 cache_similarity。
延迟字段会区分服务提供方耗时和调用方可见耗时:
| 字段 | 范围 |
|---|---|
upstream_latency_ms | 一次上游尝试所花费的时间,不包括请求解析、安全护栏、路由、重试延迟和先前尝试。 |
upstream_ttft_ms | 从一次上游尝试开始到首个流式帧的时间,无论帧类型为何——包括 response.created、仅含角色的 chat 增量等元数据起始帧,与调用方侧代理的统计口径一致。非流式请求、错误和缓存命中时省略或为零。 |
downstream_latency_ms | 调用方等待请求的总时间,包括网关处理、重试及重试延迟和所有输出暂存;仅出现在最后一次尝试中。 |
如果调用方在流式响应传输中途断开,仍会生成用量事件,因为上游已经执行工作并可能计费。该事件的状态码为 499 而不是 200,Token 计数仅覆盖断开前已到达的内容。统计流式成功请求时请使用 status_code = 200,以排除被放弃的响应。
用量事件通过 sink 消费,而不是从本地端点读取。配置可观测性导出器,将它们发送到 OTLP/HTTP、对象存储、阿里云 SLS 或 Datadog。
下一步
配置可观测性导出器,把用量事件发送到外部收集器、日志目标、对象存储或数据仓库工作流。构建 Prometheus 控制面板或告警时,请使用指标参考。