fix(service-automation): wait 节点五条日志的外来 cause 移出 message,改走 meta (#5737) - #5911
Merged
Conversation
`builtin/wait-node.ts` 有五处把我们不控制文本的失败原因(数据源驱动、job 服务、 `engine.resume()` 的错误信封)拼进日志 message。`ObjectLogger.write()` 一次调用 只加一个记录头,message 里的换行会把一条记录切成多个物理行,后几行无级别无时间戳 —— `pretty` / `text`(`os dev` / `os serve` 默认)下文件 sink 当独立记录存, `grep ERROR` 只捞到不含事实的那一行。cloud#971 同形。 五处统一改为:message 单行自足,cause 交给 logger 的结构化参数位 —— 按 `Logger` 契约选位,`warn(message, meta?)` 第二参、`error(message, error?, meta?)` 第三参 (第二参留空,否则每条记录带栈)。与 #5048 / #5575 / #5636 / #5661 同一套修法。 其中 `:98` 单独判断:那里的 cause 是 `AutomationResult.error` 这个字符串信封字段而 非抛出值,`describeThrownForLog` 的鸭子类型无处可读、且对 `undefined` 会渲染出字面 量 "undefined",故直接给 `{ error: … }` —— 字段名仍是 helper 自己的 `error`。 三处 #4632 耐久性诊断级别不变,`pnpm check:durability-log-level` 24 个接缝仍全绿。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015a5qkLzpGXhLL2F5gvJ7dD
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 5 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
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.
Fixes #5737
packages/services/service-automation/src/builtin/wait-node.ts里有五处把我们不控制文本的失败原因(数据源驱动、job 服务、engine.resume()的错误信封)直接拼进日志 message。本 PR 把五处统一改成:message 单行自足,cause 走 meta。与 #5048 / PR #5572、#5575 / PR #5639、#5636 / PR #5662、#5661 完全同一套修法,零新词汇。前提复核(rule 6):成立,且行号表没有过期
派单里的机制假设 1 预判「五处行号表已过期(今日 58ffcab 触过该文件)」。实测证伪:在
origin/main7b005b4 上,五处仍然精确落在 issue 表列出的行号上 —— 58ffcab(#5760)改的是objectql,并未移动本文件的这些行。我按要求自己枚举了该文件所有「外来 cause 进 message」的位点(不限
Cause:拼写),全集就是这五处::98errorengine.resume()的信封字段result.errorCause: ${...}:216warnjob.schedule抛出)(${...})括号形态,非Cause::323errorCause: ${...}:346errorengine.resume()抛出Cause: ${...}:387errorjob.schedule抛出)Cause: ${...}考察后排除的插值(留在 message 里,并在代码注释里写明理由):
:362/:384的wakeAt是本文件自己写入、又在两个分支之前用typeof === 'string'加非 NaN 的Date.parse守过的期限值;jobName/runId/node.id是平台自己铸造或作者声明的标识符。都不是外来 cause。:98单独判断的结论(机制假设 2)以 helper 实际签名为准:
describeThrownForLog(err: unknown)靠鸭子类型读抛出对象的.issues/.message。而:98的 cause 是AutomationResult.error,契约里就是error?: string(packages/spec/src/contracts/automation-service.ts)—— 一个引擎已经拼好的字符串,不是抛出值。传给 helper 有两个后果:字符串上它无处可读;undefined时它会把字面量"undefined"渲染进记录,顶掉原本的?? 'store unavailable'默认值。所以这一处按 issue 的 fallback 走
{ error: result.error ?? 'store unavailable' }直接给 meta,字段名仍是 helper 自己的ThrownCauseMeta.error—— 五条记录的非校验类 cause 仍只有一个键。已加用例钉住空信封那一支返回'store unavailable'而不是"undefined"。顺带记一笔可达性:这个信封字符串本身就是引擎把驱动的
message插值进去构造的(resumeInternal的STORE_UNAVAILABLE返回),所以多行驱动错误是经两跳到达这条记录的。危害机制
ObjectLogger.write()一次调用只加一个「时间戳 + 级别」记录头,所以 message 里的换行会把一条记录变成多个物理行,后几行既无级别也无时间戳。在pretty/text格式(os dev/os serve的默认)下,文件 sink 当成独立记录存,采集器读成无主碎片,grep ERROR只捞到不含任何事实的那一行 —— 而:323/:346/:387这三条是 #4632 亲自定为error的耐久性诊断,存在理由就是给人读的(文本自己写着 every wait/approval paused before this restart will hang indefinitely)。cloud#971 即此形态。参数位按
Logger契约选,不凭记忆packages/spec/src/contracts/logger.ts:warn(message, meta?)第二参,error(message, error?, meta?)第三参(第二参传undefined,否则每条记录都带整个栈,#5575)。两个级别各有一个 spy 用例专门钉参数位。反向验证(方向在跑之前就已预判)
预判:普通的红。把拼接式渲染放回去,新钉子必须变红 —— meta 里的
error字段消失、message 重新含换行、pretty下 stderr 从 1 个物理行变回 3 个。没有反转,因为这些断言钉的是「只有修复才会产生的 meta 字段」的存在。实测(
git checkout --回退wait-node.ts、测试文件不动):15 个用例转红,失败信息正好点名机制 ——随后
git apply复原,全绿。测试
新增
packages/services/service-automation/src/builtin/wait-node-log-cause.test.ts(13 例)。照 #5662 / #5661 的先例,凡问题是「按行切分的下游会看到什么」就读真ObjectLogger的真字节,spy 只出现在参数位本身是待验事实的地方:error上;:323额外加一例pretty格式(实测就是在这个格式下取的);:98加空信封回落一例;error/warn各一例 spy 钉参数位;改动到的既有测试(fixture 分诊,逐条判定而非批量重拼):
wait-node-rearm-log-level.test.ts—— [convention] best-effort 降级导致"看起来正常、实则不持久"时不应记 warn——把 #4460 的点状修复定成规则 #4632 的级别钉子一条不动;它的 capturing logger 原本只收 msg,现在按契约分参数位收,三处 cause 断言从 msg 改读 meta,并加断了 msg 不再含 cause;wait-node.test.ts——:98那条connection refused断言同样改读 meta 并加了反向断言;plugin-startup-log-cause.test.ts—— 只简化failProbe的注释:它原来写着本文件那条记录「still interpolates(out of this issue's scope, filed separately)」,现在不成立了;fixture 本身保留,因为那里的toHaveLength(1)是按接缝计数的,窄化的理由从「躲开一条被搅烂的记录」变成「把捕获限制在它自己点名的那个接缝」。命令与结果(全程持容器级验证锁、
--max-old-space-size=4096、--maxWorkers=2):验证门(从
.github/workflows/lint.yml逐个枚举后跑,非凭记忆挑):packages/spec未触碰,无 spec 产物重生成。一个字节纪律的自我记录
新测试文件第一稿里,ANSI 引导符以裸控制字节的形式落进了文件(写的是转义序列,编辑工具在我正写到控制字符时把它实体化了)—— 正是 AGENTS.md 与
scripts/check-nul-bytes.mjs点名的那类事故(#4890 同形)。已改回小写 u 转义序列拼写,check:nul-bytes与grep -naP自查均干净,并在该函数的 docblock 里留了记录。与 #5660 的关系(派单必答项)
完全无影响 —— 且 #5660 已于 2026-08-06T04:25:47Z 关闭(completed),issue 正文「仍开:#5660」这一句已过期。
理由:#5660 是
engine.ts的registerDegradedConnector(连接器降级路径,warn,cause 是reason: string参数),与本单是不同文件、不同方法、不同契约;两者唯一的共同点是复用同一个describeThrownForLog,而本 PR 没有改动那个 helper(只是新增调用点)。#5660 正文里真正待定的那件事是「registerDegradedConnector签名要不要加cause?: unknown」,本 PR 既没有改那个签名,也没有为它设下任何先例 —— 我的:98那处虽然同样绕开了 helper,但绕开的理由是「信封字段本就是string,不是抛出值」,与 #5660 选项 A/B 的取舍(要不要把抛出值一路带到报告点)不同源。Generated by Claude Code