ErrorReporter 里 defect 为什么也是 Info?

数据库连接炸了,用户拿到 500,你的日志里这条错误却是 info 级别,淹没在日常日志里。

原因是 ErrorReporter 没有「defect 默认更严重」这回事:错误对象有合法的 [ErrorReporter.severity] 就用它,否则一律是 "Info"。 想让 defect 以更高级别出现,要在上报器回调里自己判断。

ErrorReporter 是 Effect 4 里把错误上报集中到环境里的模块(整体见 Effect 里怎么统一上报错误?),它在 4.0.5 的 JSDoc 里是 unstable。defect 指 Effect.die 这类非预期错误,与可预期的失败的区别见 Effect 里的 failure 和 defect 怎么区分?。下面看这个默认值有多彻底,以及怎么修。

默认 severity 到底有多彻底?

我跑过:Effect.die(new Error("bug"))、Effect.die(new Boom())、Effect.fail(new Error("x")) 的 severity 全是 Info。

更糟的是,如果失败值或 defect 是字符串之类的非对象,它既不能被标注,severity 也恒为 Info、attributes 恒为空。标注是挂在错误对象上的属性,非对象没有地方挂。

后果很直接:把回调里的 severity 原样转成日志级别,真正的服务端故障会以 info 级别出现。

怎么让 defect 升级成 Error?

最小修复放在上报器里,用 cause 自己判断。这是我的取舍,不是官方推荐写法:

import { Cause, Effect, ErrorReporter } from "effect"

const lines: Array<string> = []

// 默认 severity 全是 Info;用 cause 里是否有 defect 自行升级
const reporter = ErrorReporter.make(({ error, severity, cause }) => {
  const effective = severity === "Info" && Cause.hasDies(cause) ? "Error" : severity
  lines.push(`${effective} ${error.message}`)
})

const program = Effect.die(new Error("db exploded")).pipe(
  Effect.withErrorReporting,
  Effect.exit,
  Effect.provide(ErrorReporter.layer([reporter]))
)

await Effect.runPromise(program)
console.log(lines)

输出 [ 'Error db exploded' ]。

这个办法有什么局限?

回调拿到的是整条 cause,混合 cause 里的预期失败也会被一起升级,因为你无法从回调参数里分辨某个 error 对应 cause 里的哪一条。

如果需要精确,就给自己的预期错误类显式标 severity,让 defect 才走默认值,再按「有标注就信标注,没标注才看 cause」判断。给错误类加标注的写法,见主文里的最小示例。

为什么不直接信默认值?

因为默认值不区分严重程度,也不会因为你没标就提醒你。上报器回调里自己决定 severity 与 defect 的映射,比事后在监控面板里找「为什么 500 是 info」成本低得多。

版本是 effect 4.0.5。默认 severity 全是 Info(对 Error 的 die、自定义类的 die、Effect.fail),以及上面升级写法的输出,都在这个版本上跑过。非对象值不能被标注、severity 恒为 Info 这一点,没有单独写代码重跑;混合 cause 被一起升级是从回调参数得出的推论,没有单独运行验证。