你有没有在追踪记录里见过这种场面:字段名一个不少,值全是 undefined。看着像一份每行都写着"待补充"的会议纪要,AI 出的。
这种空壳跨度很难查。整条记录缺失的时候你至少有个方向,知道去翻 SDK;字段全在值全空的时候,你的第一反应是怀疑自己——是不是我传参的时候就传了个寂寞?是不是我记忆里的配置根本不存在?
顺着这个方向查了两天的人,是我一个想象力很好的朋友。
说的是 Sentry 的 JavaScript SDK 给 Google GenAI 调用生成追踪跨度这件事。本来模型、生成配置、系统指令都会跟着 span 走,一切正常。但聊天调用下,配置丢了。issue 编号是 getsentry/sentry-javascript#20086,大家可以去围观一下现场。
根因说出来,我都想替历史的重构同学叹口气:Google GenAI SDK 的聊天是分两步的,先 chats.create 建一个本地聊天对象,这步不碰模型;真正发消息才走 sendMessage。上次重构把 chats.create 生成 span 的地方删了——毕竟这步又不调 API,span 删了看着不亏。但配置是挂在那一步上被捕获的,span 没了,配置跟着没。更绝的是,深度代理在重新代理聊天对象时还把调用参数给抖掉了,导致后面构造消息跨度的代码读配置,读了个寂寞。
所以那次重构删的不是一个"多余的 span",是顺手把唯一一份配置现场给端了。
配置这玩意最大的特点是:你以为它被记了两份,其实它只活在一个你觉得多余的地方。
我盯着这个 issue 看了一会儿,一时不知道是该笑那个重构,还是该共情那个重构。改动小到你根本不会在 code review 里多想一秒,但就这一删,埋了一路的雷。
修复本身倒是不刺激。改法就是逮住 chats.create 的参数,把模型和配置一条条挂到每个消息跨度上。合并逻辑还要严格按 @google/genai SDK 的规矩来——单条消息自己带了配置,就整体替换创建时的;没带,才回退到创建时的。这个"配置回退"的讲究程度,比我见过的大部分需求变更流程都严谨。
测试加了四个,修之前挂了仨,全挂在"属性为 undefined"上,死法整齐划一。修完四个全绿。整个测试套件从 38 个文件 335 个测试变成 39 个文件 339 个测试,专门为一个 bug 多养出来一个文件的信仰税。工程验收的快乐有时就是这么朴素:红的变绿的,没有别的。
作者还拿真实 Gemini API(gemini-2.5-flash)跑了一轮验证。修复之前,聊天跨度里 temperature、top_p、top_k、max_tokens、系统指令全不在场;修复之后全回来了。这比我读过的任何单测都让人安心——单测再全也是编的剧本,真实调用里跑出来的属性才是证词。
对了,作者在 PR 里交代了开发过程用了 Claude 辅助,设计、审查和验证是自己做的。所以整个故事的闭环是:一个用 AI 辅助的人,修了一个 AI 可观测性的 bug,让另一个 AI 的追踪跨度终于敢把配置写全。套娃套到这一层,我有点想发给 SRE 看看。
(友情提示:本次事故没导致任何线上服务瘫痪,它只是让追踪记录"看起来一切正常"——这大概是最 AI 风格的事故现场了。如果你也在 span 里扒拉过 temperature,评论区扣个同款 🫠)