Unleash 日志级别语义规范:ADR Logging Levels 全解与源码级实践指南
【免费下载链接】unleashOpen-source feature management platform项目地址: https://gitcode.com/GitHub_Trending/un/unleash
本文档以 Unleash 开源特性管理平台(Open-source feature management platform)中的架构决策记录 ADR: Logging levels 为核心骨架,系统解读项目对 ERROR / WARN / INFO / DEBUG / TRACE 五级日志的语义约定、频率基准与可配置性,并结合仓库后端源码与既有实现,说明如何判定"该降级为 WARN"的日志场景,给出可落地的代码评审与开发规范。读完本文,你将掌握 Unleash 的日志分级准则、LOG_LEVEL环境变量的真实作用域,以及如何像项目维护者一样审慎地选择日志级别。
背景:日志级别为何值得一份 ADR
Unleash 把日志级别当作"携带语义信息的通道"(log levels carry semantic information),而非单纯的输出开关。这一点在 logging-levels.md 的 Background 一节被反复强调:
- ERROR 级别会触发 SRE 告警:当某部署环境每小时 ERROR 日志超过 1 条时,SRE 告警就会被触发。项目整体对"滥用 ERROR"控制得不错,但仍存在误报:告警触发后 SRE 登录检查部署,发现一切正常——说明这条日志本应是 WARN,而非 ERROR。
- 误报的成本是真实的:不恰当的 ERROR 会给值班人员(on-call)带来不必要的 mental load(心智负担),并让告警体系对真正需要立刻处理的问题失去敏感性。
- 本文档的目标:固化各级别携带的语义、明确哪些级别在排查运行日志时可以忽略,从而把"日志级别"从个人习惯变成团队共识。
这项决策属于 overarching(全局性)ADR,即它对后端、前端以及不属于这两类的所有代码一律适用。
决策:五级日志的语义、频率与可配置性矩阵
ADR 给出的核心结论是一张五级日志语义表,这也是整个规范最重要的内容,逐项说明如下:
| 日志级别 | 健康应用中的频率 | 标准可用性(Standard Availability) | 是否可配置 |
|---|---|---|---|
| ERROR | 0 | 所有环境 | 否 |
| WARN | 1–10 | 所有环境 | 否 |
| INFO | 10–100 | 默认部署配置设置LOG_LEVEL=info | 是 |
| DEBUG | 100–1000 | 仅本地开发 | 是(特定部署) |
| TRACE | 1000–10000 | 不提供 | 是(特定部署) |
解读这张表需要把握三个要点:
- ERROR 在健康应用中应当为 0 条。它留给"需要立即修复的异常行为"(exceptional behaviour that we need to fix immediately),且不可配置——任何环境都不允许通过配置把 ERROR 关掉,因为 ERROR 承载告警语义。
- WARN 的合理区间是每小时 1–10 条,同样不可配置。它用于"值得记录但无需立刻行动"的问题,例如一次可自愈的失败。
- INFO 是可配置的默认档:默认部署配置通过
LOG_LEVEL=info开启;DEBUG 与 TRACE 仅允许在本地开发或特定部署中开启,其中 TRACE 在标准可用性中不提供(NO)。
该表同时隐含了频率是选择级别的参考刻度:先估算该事件在健康系统中的发生频率,再对号入座选择级别,比"凭感觉"更客观。
变更内容:从 ERROR 降级到 WARN 的判断标准
ADR 明确要求改变既有习惯:
- 过去:对可自愈(self-healable)的问题记录 ERROR,例如"某次后台写入失败,但系统会重试或下次自动恢复"。
- 现在:这类情况应记录WARN;只有"需要立刻修复的异常行为"才允许 ERROR。
- 进一步收敛 WARN 基数:为了降低 WARN 的基数(cardinality),某些今天还在用 WARN 的普通消息,应该降级为 INFO。
换句话说,这构成了一个"逐级下移"的指导原则:判断一条日志该用什么级别,核心问题是"SRE / 值班人员对此能做什么"。如果什么都不做、系统会自动恢复,就降到 WARN;如果只是常规运行信息,就降到 INFO。
配套规范:ERROR 必须携带错误对象
需要说明的是,本 ADR 与另一份全局 ADR Error Logging stack traces 配套使用:一旦确认是 ERROR,就必须把错误对象作为第二个参数传给logger.error,以便日志带上完整堆栈(stacktrace),例如:
// 推荐写法:第二个参数传入错误对象,日志中携带堆栈 try { // ... } catch (e) { this.logger.error('Something went wrong', e); }而不是把错误内插进消息字符串(this.logger.error(\Something went wrong {$e}`)`),后者会丢失堆栈上下文,给排障增加难度。
实战案例一:Traffic Data Usage 保存失败(降级到 WARN 的教科书示例)
ADR 给出第一个例子来自 enterprise 仓库的 traffic-data 服务(traffic-data-usage-service.ts,该链接为外部仓库引用,供对照理解,当前开源仓库内无此文件)。核心逻辑是批量保存流量数据使用量:
之前(Previous)
await Promise.all(promises) .then(() => { this.logger.debug('Traffic data usage saved'); }) .catch((err) => { this.logger.error('Failed to save traffic data usage', err); });推荐(Recommended)
ADR 的分析是:保存失败时 SRE 没有任何可操作的空间(没有可供值班人员修复的配置、没有需要重启的服务),因此这是降级为 WARN 的绝佳候选:
await Promise.all(promises) .then(() => { this.logger.debug('Traffic data usage saved'); }) .catch((err) => { this.logger.warn('Failed to save traffic data usage', err); });注意推荐写法仍然保留了err作为第二参数——降级的是级别语义,不是排障信息量。
实战案例二:account-store 的 markSeenAt(当前仓库中的真实对应实现)
ADR 的第二个例子直接取自本仓库:account-store.ts 中的markSeenAt。该方法负责在个人访问令牌(PAT)被使用时更新其seen_at时间戳。ADR 引用时的原始形态如下:
async markSeenAt(secrets: string[]): Promise<void> { const now = new Date(); try { await this.db('personal_access_tokens') .whereIn('secret', secrets) .update({ seen_at: now }); } catch (err) { this.logger.error('Could not update lastSeen, error: ', err); } }ADR 给出的判断是:"无法更新 lastSeen"对值班人员来说同样无能为力(它不是部署配置问题、不是容量问题、也不会导致功能不可用),因此这也是降级为 WARN 的候选。
当前仓库中的演进印证
在本仓库的最新代码中,account-store.ts 的markSeenAt已演化为同时处理 v1(secret)与 v2(selector)两类令牌引用的批量更新:
async markSeenAt(tokens: AccountTokenReference[]): Promise<void> { if (tokens.length === 0) { return; } // v1 令牌按 secret 过滤,v2 令牌按 selector 过滤 const legacySecrets = tokens .filter((token) => token.version === 'v1') .map((token) => token.secret); const selectors = tokens .filter((token) => token.version === 'v2') .map((token) => token.selector); if (legacySecrets.length === 0 && selectors.length === 0) { return; } try { const query = this.db('personal_access_tokens'); if (legacySecrets.length > 0 && selectors.length > 0) { query.where((builder) => builder .whereIn('secret', legacySecrets) .orWhereIn('selector', selectors), ); } else if (legacySecrets.length > 0) { query.whereIn('secret', legacySecrets); } else if (selectors.length > 0) { query.whereIn('selector', selectors); } await query.update({ seen_at: now }); } catch (err) { this.logger.error('Could not update lastSeen, error: ', err); } }从源码结构看,该方法签名已升级为AccountTokenReference[],但catch分支仍以this.logger.error('Could not update lastSeen, error: ', err)记录,与 ADR 讨论的形态一致——这正是"候选项尚未完成迁移"的真实例子,也说明 ADR 是演进指南而非瞬时完成的重构。同一模式还出现在 api-token-store.ts,可作为同类降级审查的对照点。
源码级佐证:Unleash 的日志级别如何落地
为了把 ADR 的语义表落到实处,需要理解 Unleash 后端日志基础设施的真实行为。日志抽象定义在 logger.ts:
LogLevel枚举:debug、info、warn、error、fatal(fatal在 ADR 表中未单列,但基础设施层面存在);Logger接口为debug / info / warn / error / fatal各定义一个(message: any, ...args: any[]) => void签名,这正是本 ADR 中"把错误作为第二参数传入"得以成立的接口基础;getDefaultLogProvider(logLevel = LogLevel.error)基于 log4js 把日志输出到 console,默认兜底级别是error;validateLogProvider会在启动时校验注入的 logger 必须实现全部五个方法。
关键结论:ADR 矩阵中"可配置"一列的落地机制是LOG_LEVEL环境变量。在 create-config.ts 中:
const logLevel = options.logLevel || LogLevel[process.env.LOG_LEVEL ?? LogLevel.error]; const getLogger = options.getLogger || getDefaultLogProvider(logLevel); validateLogProvider(getLogger);即:运行时通过options.logLevel(对应 option.ts 中的logLevel?: LogLevel)或环境变量LOG_LEVEL决定日志输出阈值;两者都未设置时默认是error级别(这也与 ADR 表"ERROR 在所有环境都可见"的语义一致)。因此:
- 生产环境建议显式设置
LOG_LEVEL=info,与 ADR 表中"默认部署配置 sets LOG_LEVEL=info"对齐; - 本地开发可设
LOG_LEVEL=debug甚至LOG_LEVEL=trace观察细粒度流程; - 无论怎么配置,
error级别始终处于最低阈值之下,不会被任何合理配置屏蔽——这正是"ERROR 不可配置"的工程落地。
落地检查清单:如何判断一条日志该用什么级别
把 ADR 的语义、两个实战案例与源码基础设施结合,可以总结出一份可复用的评审清单:
- 判断是否可自愈 / 值班人员能否干预:如果系统会自动恢复、SRE 登录后无事可做,用 WARN(如 traffic-data 保存失败、lastSeen 更新失败);只有需要立即修复的异常才用 ERROR。
- 估算健康应用中的频率:对照矩阵——期望 0 条 → ERROR;每小时 1–10 条 → WARN;10–100 条 → INFO;更高 → DEBUG / TRACE。
- 克制 WARN 基数:如果某条 WARN 只是常规运行信息且频繁出现,应降级为 INFO。
- ERROR 必须携带错误对象:始终使用
logger.error('消息', err)第二参数形态,保留堆栈(配合 logging.md)。 - 验证配置与可见性:确认
LOG_LEVEL或options.logLevel设置正确,且 ERROR 不会因配置被屏蔽(参见 create-config.ts)。 - 审视现有候选:可在 account-store.ts 与 api-token-store.ts 等历史
logger.error调用点中逐一复核是否存在同类"应降级为 WARN"的遗留。
总结
Logging levels ADR 的核心主张是:日志级别是语义契约,不是个人风格。通过 ERROR/WARN/INFO/DEBUG/TRACE 五级语义表(含频率刻度与可配置性)、"值班人员能否干预"的判断标准、以及两个真实的降级案例,Unleash 将"告警只留给必须立刻处理的问题"落成了可执行的工程规范。配合 Logging errors ADR 的堆栈保留要求与LOG_LEVEL环境变量的基础设施(create-config.ts、logger.ts),任何为 Unleash 贡献代码的开发者都能写出既准确又低噪音的日志——让 SRE 的告警每一次都值得响应。
【免费下载链接】unleashOpen-source feature management platform项目地址: https://gitcode.com/GitHub_Trending/un/unleash
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考