Uh oh!
There was an error while loading. Please reload this page.
fix(plugin-hono-server): 逃出 handler 的抛出不再被静默丢弃 (#5848) - #6052
Conversation
runHandler() 的兜底 .catch 此前把 rejection 显式丢弃(参数名就是 `_err`), wrap() 随后回一个不带原因的 500。净效果:任何逃出 handler 的抛出,在以本 适配器为 transport 的 host 上都是裸 500 + 零日志 —— 连 stack 都没有。 #4264 诊断的正是这段代码,但它的修法是给三条 datasource 路由各加 catch, 接缝本身没动;check-route-envelope.mjs 结构上看不到这一类(它审计响应写点, 未捕获的抛出根本不写响应)。 现在该接缝按 Logger 契约打一条 error 记录:Error 走契约的 error 形参槽而不是 结构化 meta(message/stack 是 non-enumerable,进 meta 会序列化成 {},比没有 日志更糟 —— 它会报告成功);跨 realm 的 Error 按 name/message/stack 重建; 非 Error 抛出被描述进 message。meta 只带 method + path,不带请求体。 未接线时默认用 createLogger() 而非静默 —— 直接内嵌 HonoHttpServer 的 serverless 入口正是本问题的生产现场。HonoServerPlugin.init() 用 ctx.logger 替换默认值。新增 HonoHttpServer.setLogger(),不改 IHttpServer 契约。 响应形状一字未改(兜底 body 仍是 {"error":"No response from handler"} + 500), 并加测试钉住 —— 收成声明信封属另一项未裁决的契约决策,不随本次改动附带。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wbxm29qPKnLf44AbSxizqW
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 7 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31130722252 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#5848
前提复核(在
origin/main739f496上直读)issue 正文引用的两处一字未改,前提成立:
adapter.tsrunHandler()的兜底.catch((_err) => { … })—— 参数名就是下划线前缀的显式丢弃,无任何日志;wrap()的return response ?? c.json({ error: 'No response from handler' }, 500);净效果就是 issue 描述的那样:任何逃出 handler 的抛出,在以本适配器为 transport 的 host 上都表现为一个不带原因的裸 500,而且任何地方都没有日志 —— 连 stack 都没有。
「不是 #4264 的重复」也已复核成立:#4264 诊断的正是这段代码,但它的修法是给三条 datasource 路由各加 catch,接缝本身没动。
scripts/check-route-envelope.mjs结构上看不到这一类 —— 它审计响应写点,而未捕获的抛出根本不写响应。改了什么
runHandler()的.catch现在把 rejection 交给新的reportHandlerFailure(),按Logger契约打一条error记录。这是唯一的报告点,wrap与notFound两个 caller 都经过它 —— 两者都把failed: true变成一个不说明原因的 500(No response from handler/Fallback handler failed),所以诊断不在这里发出就哪里都不存在。non-enumerable 陷阱按 issue 点名的方式处理
Error的message/stack是 non-enumerable,直接塞进结构化 meta 会序列化成{}—— 那比没有日志更糟,因为它会报告成功。处理方式:Error走契约的error(message, error, meta)形参槽,不进 meta。仓内三个实现(ObjectLogger/ConsoleLogger/JsonLogger)都按名字把这两个字段提出来,所以适配器只需保证进入该槽的确实是Error;toLoggableError():真Error原样透传(原始 stack 保留);跨 realm 的Error(vm/ worker,instanceof不成立,恰是裸 spread 会压成{}的那个形状)按name/message/stack重建;throw 'boom'/throw { code: 1 }这类被描述进 message 并标注为非Error抛出,以免合成的 stack 被误读成抛出方的。日志级别 =
error按 AGENTS.md「Degradation log levels」逐条判过:第三种合法答案「失败已交给 CALLER」在这里不成立 —— caller 拿到的 500 body 里没有 cause、没有 code、没有 message,所以日志是唯一记录。这也不是「每个畸形请求打一条」的校验路径:抛出逃逸是服务端缺陷,一次一条正是正确音量。该 seam 不在
check:durability-log-level的词表内(它守的是handler(req, res)),gate 已跑绿。上下文只到 method + path
请求体不入日志 —— 那是凭据与 PII 最可能待的地方,而
message+stack已足以定位代码位置。有测试断言 body 里的值完全不出现在记录中。默认就有日志出口,不是静默
未接线时用
createLogger()而非NoopLogger。这不是便利选择:本 issue 的生产现场(cloud 控制面)跑在裸适配器上 —— 没有 plugin,也就没有ctx.logger—— 静默默认会对它原样复现该 bug,而它此前只能在自己每条路由里 try/catch 补(cloud#1144),那正是 #4264 描述、却没有消除的税。HonoServerPlugin.init()会用ctx.logger替换默认值;要静默须显式传NoopLogger。新增
HonoHttpServer.setLogger(logger)—— 纯新增的类方法,不改IHttpServer契约(其它适配器不受影响)。适配器在 plugin 构造函数里创建、那时还没有ctx,所以注入点只能是init()。⛔ 明确不做的那一半
兜底响应 body 一字未动,仍是
{"error":"No response from handler"}+ 500。把它收成声明信封会改变线上响应形状,属另一项尚未裁决的契约决策,分诊评论已明确不入本单 —— 这里加了逐字节钉住的测试,免得它日后作为 rider 漂进来。测试
新增
handler-throw-logging.test.ts(15 例,全部走真实 Hono app 的app.fetch,不用 mock):async rejection / 同步 throw / 级别是error/ meta 只有 method+path 且不含 body / 字符串抛出 / 跨 realm error-like / 循环引用无法 JSON 化的抛出 / fallback(notFound)seam 同样报告 / 响应字节不变 / 只是「什么都没写」的 handler 保持静默 / happy path 零噪音 / 404 与 405 零噪音 / 裸适配器默认 logger 仍然输出(stderr,含 stack)/ logger 自身抛出不会把 500 变成更糟的东西。hono-plugin.test.ts增一例钉住init()把ctx.logger交给了适配器。反向验证(方向为预测的「红」)
把
this.reportHandlerFailure(c, err)一行删掉重跑:红的正好是 9 条日志断言。响应形状钉住的那几条两个方向都绿 —— 这正是它们的作用:它们守的是本单的范围边界,不是修复本身。
命令与输出
下游消费方(直接构造
HonoHttpServer的)一并跑绿:家族门禁:
check:route-envelope✓ /check:error-code-casing✓ /check:durability-log-level✓ /check:engine-double-contract✓ /check:nul-bytes✓(另按规程对 5 个改动文件做了超出 gate 的控制字符自扫,clean)。顺带的实证:
fallback-seam.test.ts:214那个既有的抛出用例,此前完全静默,现在在测试输出里直接打出带完整 stack 的诊断行 —— 修复效果在一条不是我写的测试上可见。Changeset
.changeset/hono-adapter-handler-throw-logging.md——@objectstack/plugin-hono-serverpatch(可观察的日志新增,响应形状不变)。Generated by Claude Code