Skip to content

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

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

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

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

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

契约的另外两个实现 —— @objectstack/observabilityConsoleLogger / 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,metaundefined —— 于是这三条诊断整块消失,只剩一句话。@objectstack/observability 正是为了「宿主换一个结构化 logger」而存在,今天仓库里没有任何地方 new JsonLogger(...)(除了它们自己的 child()),所以属于休眠漂移(observation-class),不是用户今天会撞到的 bug —— 但它会在第一个真正接入生产日志栈的宿主那里生效,而且症状是「日志少了字段」,极难归因。

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

  1. 把三个调用点改成契约形状(error(msg, undefined, { … })),并把两处本地 logger 接口的 error 改成三参 —— 或者干脆 import type { Logger } from '@objectstack/spec/contracts',别再自带方言。注意:ObjectLoggerfinding(service-automation): connector 物化失败的 fail() 也是 ${err.message} 单行插值,同 #5048 的类别、另一个接缝 #5575 之前会丢弃契约的第三参,所以这个方向必须建立在 finding(service-automation): connector 物化失败的 fail() 也是 ${err.message} 单行插值,同 #5048 的类别、另一个接缝 #5575packages/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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions