fix(plugin-hono-server): 逃出 handler 的抛出不再被静默丢弃 (#5848) - #6052
Queued
qq9340100 wants to merge 1 commit into
Queued
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
|
This was referenced Aug 6, 2026
Contributor
📓 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:
|
qq9340100
marked this pull request as ready for review
August 6, 2026 20:14
qq9340100
added this pull request to the merge queue
Aug 6, 2026
Any commits made after this event will not be merged.
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 #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