Skip to content

finding(objectql): hook 层自带的 logger 接口把 error 声明成 (msg, meta?),与 Logger 契约的第二参是 Error 相反 —— 三处调用点在任何忠于契约的 Logger 下都会丢掉 meta #5637

Closed

Description

@os-zhuang

做 #5575 时核实 Logger.error 的类型契约扫到的,与那一单不同包、不同接缝,按 Prime Directive #10 单开。

事实

packages/spec/src/contracts/logger.ts:37 声明的契约是:

error(message: string, error?: Error, meta?: Record< string, any >): void;

第二参是 Error,meta 在第三位。packages/objectql 的两个模块各自声明了一份结构化 logger 形状,把 error 写成两参:

  • packages/objectql/src/hook-binder.ts:77-81
  • packages/objectql/src/hook-wrappers.ts:26-31
logger?: {
  debug: (msg: string, meta?: any) => void;
  info: (msg: string, meta?: any) => void;
  warn: (msg: string, meta?: any) => void;
  error: (msg: string, meta?: any) => void;   // ← 第二参是 meta,不是 Error
};

于是三个调用点按这份本地方言把 meta 放在第二位:

  • hook-wrappers.ts:341 — logger.error('[hook] handler failed (onError=log; suppressing)', { … })
  • hook-wrappers.ts:394 — logger.error('[hook] async handler error (fire-and-forget)', { … })
  • hook-binder.ts:243 — logger.error('[hook-binder] failed to bind hook', { … })

注入进去的是 ctx.logger(plugin.ts:290 / plugin.ts:1670)。契约类型能结构化地满足这份本地形状(参数少的一方可赋值,any 双向兼容),所以 tsc 一句话都不说 —— 方言与契约的冲突只在运行时体现。

为什么今天没坏,以及什么时候会坏

只因为宿主注入的实现恰好是 ObjectLogger,而它对第二参按形状分派(errorOrMeta instanceof Error),所以 meta 在第二位也能落进记录。这份宽容本身是契约没有声明的。

契约的另外两个实现 —— @objectstack/observability 的 ConsoleLogger / JsonLogger(loggers.ts:55 / 113)—— 忠实按契约来:

error(message: string, error?: Error, meta?: Record< string, unknown >): void {
    this.emit('error', message, { ...(meta ?? {}), ...(error ? { error: error.message, stack: error.stack } : {}) });
}

传进去的对象落在 error 位,error.message / error.stack 都是 undefined,meta 是 undefined —— 于是这三条诊断整块消失,只剩一句话。@objectstack/observability 正是为了「宿主换一个结构化 logger」而存在,今天仓库里没有任何地方 new JsonLogger(...)(除了它们自己的 child()),所以属于休眠漂移(observation-class),不是用户今天会撞到的 bug —— 但它会在第一个真正接入生产日志栈的宿主那里生效,而且症状是「日志少了字段」,极难归因。

建议(不预设结论,这里有两个方向)

  1. 把三个调用点改成契约形状(error(msg, undefined, { … })),并把两处本地 logger 接口的 error 改成三参 —— 或者干脆 import type { Logger } from '@objectstack/spec/contracts',别再自带方言。注意:ObjectLogger 在 finding(service-automation): connector 物化失败的 fail() 也是 ${err.message} 单行插值,同 #5048 的类别、另一个接缝 #5575 之前会丢弃契约的第三参,所以这个方向必须建立在 finding(service-automation): connector 物化失败的 fail() 也是 ${err.message} 单行插值,同 #5048 的类别、另一个接缝 #5575 的 packages/core/src/logger.ts 修复之上(已随 finding(service-automation): connector 物化失败的 fail() 也是 ${err.message} 单行插值,同 #5048 的类别、另一个接缝 #5575 落地)。
  2. 或者认为「meta 可以出现在 error 位」是我们真心想要的能力 —— 那就应该在契约里声明(error?: Error | Record< string, any >),而不是让一个实现私下宽容、另两个实现静默丢数据。这一步会给每个 Logger 实现加上按形状分派的义务,属于契约级决定,不该由实现方言既成事实地推动。

倾向 1:契约先行,消费端不要方言(Prime Directive #12);2 是把一处宽容升级成所有实现的义务,收益只是省掉一个 undefined。

关联

#5575(核实 Logger.error 契约时发现;同一 PR 修好了 ObjectLogger 丢弃第三参 meta 的缺陷,并统计出 metadata / metadata-protocol / client / core/security 约 15 处按契约书写、此前一直静默丢字段的调用点)、#5048 / PR #5572。

Activity

  1. added
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 5, 2026
  2. added
    pm:queueReady for the PM dispatch loop
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 5, 2026
  3. claude commented on Aug 5, 2026

    @claude
    Contributor

    发现分诊(#4949 纪律):晋级——摘 finding 换 pm:queue + domain:engine-core,范围裁定为方向 1:三个调用点改契约形状,两处本地 logger 接口改为引 @objectstack/spec/contracts 的 Logger(⛔ 不再自带方言,PD #12)。方向 2(契约里声明 error?: Error | Record<string, any>)属契约级决定——若实现中认为必要,走 needs-user-decision 升级,⛔ 不得默改 spec。

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 6, 2026
  5. self-assigned this
    on Aug 6, 2026
  6. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    ContributorAuthor

    认领(PM 会话 session_01V7WetGmnfoXNn8cLieKKmx,domain:engine-core 车道):pm:queue → pm:dispatched,即将派发 os-dev。

    • 分支:claude/issue-5637-hook-logger-contract-shape
    • worktree:../objectstack-5637
    • 文件面:仅 packages/objectql/src/hook-wrappers.ts + packages/objectql/src/hook-binder.ts(+ 同包测试 + changeset)。不碰 engine.ts / spec / observability / core。
    • 方向裁决:方向 1(契约先行,消费端不要方言,PD Add comprehensive test suite for Zod schema validation #12)。两处本地 logger 形状改为契约形状(优先直接 import type { Logger } from '@objectstack/spec/contracts' 或其 Pick,而不是再手写一份),三个调用点改为 error(msg, undefined, { … })。前置 finding(service-automation): connector 物化失败的 fail() 也是 ${err.message} 单行插值,同 #5048 的类别、另一个接缝 #5575 已落地(ObjectLogger 不再丢弃第三参),方向 1 的地基成立。方向 2(契约加宽 Error | Record)不采纳:把一处实现的私下宽容升级为所有实现的义务,属契约级决定且收益仅省一个 undefined。
    • 兼容性上这是纯赢:ObjectLogger 按形状分派,第二参 undefined 落 Error 位无害;ConsoleLogger/JsonLogger 从「整块丢 meta」变为如实记录。

    竞态复查与派发随后。


    Generated by Claude Code

  7. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    ContributorAuthor

    PM 评审(会话 session_01V7WetGmnfoXNn8cLieKKmx):PR #5780 ACCEPT,待 CI 绿后放行。

    已读全量 diff。要点:

    1. 方言消除彻底:HookDiagnosticsLogger = Pick<Logger, 'debug'|'info'|'warn'|'error'> 从契约派生、hook-wrappers 单点导出、binder 复用同一类型——两模块形状同源,符合 PD Add comprehensive test suite for Zod schema validation #12 与「最小可用面」。
    2. 第 4 处调用点的纳入是对的:condition 编译失败诊断与另三处同缺陷同文件,类型改后 tsc 必然顶红,属修复必然一环;逐点注释说明为何 Error 位留空(CelFault 非 Error、catch 值是 unknown),且明确「catch 值提升进 Error 位是另一个决定,不搭车」——范围纪律好。
    3. 测试设计抓住了要害:判别器是忠于契约的 stub(三参分记、不做形状分派)——正是 ObjectLogger 的宽容让旧缺陷不可见,复刻宽容什么也测不出;另有真实 ObjectLogger JSON 输出钉住「第二参 undefined 无害」。
    4. 反向验证三条预判全中,含如实说明第 5 例天然不翻转(它是兼容钉不是判别器)、以及 tsc 四处 TS2353 转红——这条编译期防线是本单最实质的收益:同一错误再写一次会在编译期被顶回。
    5. 兼容面讲清楚了:现有部署(ObjectLogger)零行为变化;ConsoleLogger/JsonLogger 从整块丢 meta 变为如实记录;BindHooksOptions/WrapDeclarativeOptions 收窄经全仓核实无外部调用点。

    CI 绿后我会转 ready 并挂 auto-merge(当前 GraphQL 配额受限,放行动作在重试队列中,不影响评审结论)。


    Generated by Claude Code

  8. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    ContributorAuthor

    补充裁决(dev 报告的待定项):HookDiagnosticsLogger 不进 barrel,维持现状——它是 hook 层对契约的内部收窄,公开契约是 @objectstack/spec/contracts 的 Logger 本身;外部消费者如需同形状可自行 Pick。若将来出现真实的外部具名引用需求,届时一行 barrel 导出另开小单即可,不预支。


    Generated by Claude Code

  9. added a commit that references this issue on Aug 22, 2026
  10. added a commit that references this issue on Aug 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

pm:dispatchedDispatched to a dev agent by /pm-dispatch

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions