Skip to content

追踪系统

可观测性对任何 LLM 网关都至关重要。GodeX 的追踪系统捕获整个请求生命周期中的每个请求、token 使用量事件、流式事件和错误,将它们持久化到 SQLite 用于离线分析。系统专为生产吞吐量设计:AsyncTraceRecorder 在队列中批量处理事件,并定期或在批量大小阈值达到时刷新,使热路径免于磁盘 I/O。当追踪被禁用时,NoopTraceRecorder 以零开销替代真实的记录器。

追踪系统通过 TraceRecordingContext 附加到 ResponsesContext,因此任何有权访问上下文的代码都可以发出追踪记录,而无需了解存储后端。

概览

组件文件用途
TraceRecorderrecorder.ts:5-8核心接口(recordclose
AsyncTraceRecorderrecorder.ts:30-110基于队列的批量记录器
NoopTraceRecorderrecorder.ts:25-28追踪禁用时的零操作记录器
SQLiteTraceStoresqlite.ts:69-297具有四张表的 SQLite 存储
TraceRecordEventtypes.ts:70-74所有记录类型的联合类型
mapTraceRecordToRowrow-mapper.ts:16-98将事件转换为存储行
summarizePayloadpayload.ts:10-35SHA-256 哈希、字节数、可选 JSON 捕获
TraceRecordingContextcontext.ts:4-12附加到每个请求的上下文
createTraceServicestrace-services.ts:15-34基于配置的工厂函数

架构概览

TraceRecorder 接口

TraceRecorder 接口(recorder.ts:5-8)设计上保持最小化:

方法描述
record(event)将追踪事件入队等待持久化
close()刷新剩余事件并释放资源

AsyncTraceRecorder

生产环境记录器(recorder.ts:30-110)使用内存队列,具有两个刷新触发器:

配置选项

选项类型描述
maxQueueSizenumber队列中的最大事件数;溢出时丢弃
batchSizenumber每次刷新的事件数量
flushIntervalMsnumber自动刷新的定时器间隔
storeTraceStoreWriter存储后端(通常是 SQLiteTraceStore
loggerTraceRecorderLogger用于记录丢弃和错误警告
capturePayloadboolean是否存储完整的 JSON 负载
payloadMaxBytesnumber存储负载的字节限制

SQLiteTraceStore

SQLite 存储(sqlite.ts:69-297)在构造时自动迁移四张表:

数据库 Schema

批量插入包装在事务中(sqlite.ts:90-95)以确保原子性。在 request_idresponse_idevent_namecode 上创建索引以优化常见查询模式。

追踪记录类型

TraceRecordEvent 联合类型(types.ts:70-74)有四种变体:

类型接口关键字段
requestTraceRequestRecordEventstreamrequested_prompt_cache_keypayload
usageTraceUsageRecordEventusage(input_tokens、output_tokens、total_tokens、cached_tokens、reasoning_tokens、cache_hit_ratio)
eventTraceEventRecordEventevent_namesequencepayload
errorTraceErrorRecordEventevent_nameerror_typedomaincodemessagestatuspayload

所有变体共享 TraceRecordBasetypes.ts:19-25),包含 request_idresponse_idprovidermodelcreated_at

事件名称

TraceEventRecordEventevent_name 限制为(types.ts:50-54):

事件名称记录时机
provider.request.prepared最终 provider 请求已经完成 patch,即将进入 provider client;该事件不存储请求体
provider.response.body从上游接收的同步响应体
upstream.stream.event.raw来自上游的原始 SSE 数据块
upstream.stream.event.transformed桥接层转换后的事件

Provider 请求 payload 存储在 trace_requests,不会在 trace_events 中重复保存。把 request 行和 provider.request.prepared 事件关联起来,就能同时看到最终 patched 请求摘要以及进入 provider client 前的生命周期点。prepared 事件在 provider patchRequest hook 执行之后、request()stream() 调用 provider client 的 HTTP 操作之前记录;它不表示网络发送一定已经成功。

负载捕获

summarizePayloadpayload.ts:10-35)控制存储多少数据:

模式capturePayloadpayload_jsonpayload_hash
仅摘要falsenull完整 JSON 的 SHA-256 十六进制值
完整捕获true完整 JSON 字符串(不超过 payloadMaxBytesSHA-256 十六进制值
截断捕获true截断的 JSON 字符串SHA-256 十六进制值

payload_bytes 字段始终记录原始字节长度,无论是否截断(payload.ts:23-24)。哈希使用 Bun.CryptoHasher("sha256") 计算(payload.ts:6-8)。

行映射

mapTraceRecordToRowrow-mapper.ts:16-98)根据 event.kind 分派:

类型目标表负载处理
requesttrace_requests通过 summarizePayload 摘要
usagetrace_usage直接从 TraceUsageSnapshot 提取字段
eventtrace_events通过 summarizePayload 摘要
errortrace_errors通过 summarizePayload 摘要

如果任何事件的序列化失败,映射器返回 null 并记录警告,而不是导致刷新崩溃(row-mapper.ts:91-97)。

记录辅助函数

四个辅助函数附加到 TraceRecordingContext,提供便捷的记录方式:

recordTraceRequest

request-recorder.ts:4-22 在 provider hooks 执行之后记录最终 patched provider 请求,包括是否为流式请求、可选的 prompt_cache_key,以及 provider 请求体的可选 payload 摘要。配套的 provider.request.prepared trace event 只标记生命周期点,不再次携带请求体。

recordTraceUsage

usage-recorder.ts:6-22 通过 traceUsageFromResponseUsageusage.ts:4-23)将 ResponseUsage 转换为 TraceUsageSnapshot,同时在两者都可用时计算 cache_hit_ratiocached_tokens / input_tokens

recordTraceEvent

event-recorder.ts:5-25 记录一个命名事件,带有可选的负载和用于流内排序的序列号。

recordTraceError

error-recorder.ts:5-27GodeXError 或通用错误中提取错误元数据(类型、领域、错误码、消息、状态码),并以完整的错误上下文作为负载进行记录。

服务装配

createTraceServicestrace-services.ts:15-34)读取 TraceConfig 并创建由 SQLiteTraceStore 支持的 AsyncTraceRecorder(当 config.enabled 为 true 时)或 NoopTraceRecorder(当为 false 时):

交叉引用

  • 会话存储 -- 会话存储系统使用类似的 SQLite 持久化模式
  • ProviderSpec 契约 -- 追踪记录中的 provider 和 model 字段来自解析后的 spec

参考文献