Skip to content

fix(plugin-hono-server): 逃出 handler 的抛出不再被静默丢弃 (#5848) - #6052

Queued
qq9340100 wants to merge 1 commit into
mainfrom
claude/issue-5848-hono-adapter-error-log
Queued

fix(plugin-hono-server): 逃出 handler 的抛出不再被静默丢弃 (#5848)#6052
qq9340100 wants to merge 1 commit into
mainfrom
claude/issue-5848-hono-adapter-error-log

Conversation

@qq9340100

Copy link
Copy Markdown
Collaborator

Fixes #5848

前提复核(在 origin/main 739f496 上直读)

issue 正文引用的两处一字未改,前提成立:

  • adapter.ts runHandler() 的兜底 .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 记录。这是唯一的报告点,wrapnotFound 两个 caller 都经过它 —— 两者都把 failed: true 变成一个不说明原因的 500(No response from handler / Fallback handler failed),所以诊断不在这里发出就哪里都不存在。

non-enumerable 陷阱按 issue 点名的方式处理

Errormessage / 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) 一行删掉重跑:

Test Files  1 failed | 14 passed (15)
     Tests  9 failed | 170 passed (179)

红的正好是 9 条日志断言。响应形状钉住的那几条两个方向都绿 —— 这正是它们的作用:它们守的是本单的范围边界,不是修复本身。

命令与输出

pnpm --filter @objectstack/plugin-hono-server test
  Test Files  15 passed (15)
       Tests  179 passed (179)

pnpm --filter @objectstack/plugin-hono-server typecheck   # tsc --noEmit,无输出
eslint <4 个改动文件>                                      # 无输出

下游消费方(直接构造 HonoHttpServer 的)一并跑绿:

@objectstack/http-conformance   46 passed   # 跨适配器 IHttpServer 一致性
@objectstack/hono               73 passed
@objectstack/service-datasource 230 passed

家族门禁: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-server patch(可观察的日志新增,响应形状不变)。


Generated by Claude Code

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
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 6, 2026 3:45pm

Request Review

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/plugin-hono-server.

7 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/getting-started/your-first-project.mdx (via @objectstack/plugin-hono-server)
  • content/docs/permissions/authentication.mdx (via @objectstack/plugin-hono-server)
  • content/docs/plugins/index.mdx (via @objectstack/plugin-hono-server)
  • content/docs/plugins/packages.mdx (via @objectstack/plugin-hono-server)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/plugin-hono-server)
  • content/docs/releases/implementation-status.mdx (via @objectstack/plugin-hono-server)
  • content/docs/releases/v16.mdx (via @objectstack/plugin-hono-server)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@qq9340100
qq9340100 marked this pull request as ready for review August 6, 2026 20:14
@qq9340100
qq9340100 added this pull request to the merge queue Aug 6, 2026
Any commits made after this event will not be merged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

plugin-hono-server 适配器把逃出 handler 的抛出整个丢弃:裸 500 + 零日志(#4264 只按路由治了标)

2 participants