Skip to content

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

Merged
qq9340100 merged 1 commit into
mainfrom
claude/issue-5848-hono-adapter-error-log
Aug 7, 2026
Merged

fix(plugin-hono-server): 逃出 handler 的抛出不再被静默丢弃 (#5848)#6052
qq9340100 merged 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.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 31130722252 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (1/3) — 失败步骤: Run this shard's tests

    �[41m�[1m FAIL �[22m�[49m src/__tests__/datasource-pool-support.test.ts�[2m > �[22m#5714 — the driver factory rejects a pool it cannot honour�[2m > �[22msqlite WITHOUT a pool still builds exactly as b
    

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 69 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Merged via the queue into main with commit dfa8bad Aug 7, 2026
48 of 80 checks passed
@qq9340100
qq9340100 deleted the claude/issue-5848-hono-adapter-error-log branch August 7, 2026 00:03
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