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环境变量的真实作用域以及如何像项目维护者一样审慎地选择日志级别。背景日志级别为何值得一份 ADRUnleash 把日志级别当作携带语义信息的通道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是否可配置ERROR0所有环境否WARN1–10所有环境否INFO10–100默认部署配置设置LOG_LEVELinfo是DEBUG100–1000仅本地开发是特定部署TRACE1000–10000不提供是特定部署解读这张表需要把握三个要点ERROR 在健康应用中应当为 0 条。它留给需要立即修复的异常行为exceptional behaviour that we need to fix immediately且不可配置——任何环境都不允许通过配置把 ERROR 关掉因为 ERROR 承载告警语义。WARN 的合理区间是每小时 1–10 条同样不可配置。它用于值得记录但无需立刻行动的问题例如一次可自愈的失败。INFO 是可配置的默认档默认部署配置通过LOG_LEVELinfo开启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该链接为外部仓库引用供对照理解当前开源仓库内无此文件。核心逻辑是批量保存流量数据使用量之前Previousawait Promise.all(promises) .then(() { this.logger.debug(Traffic data usage saved); }) .catch((err) { this.logger.error(Failed to save traffic data usage, err); });推荐RecommendedADR 的分析是保存失败时 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[]): Promisevoid { 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已演化为同时处理 v1secret与 v2selector两类令牌引用的批量更新async markSeenAt(tokens: AccountTokenReference[]): Promisevoid { 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.tsLogLevel枚举debug、info、warn、error、fatalfatal在 ADR 表中未单列但基础设施层面存在Logger接口为debug / info / warn / error / fatal各定义一个(message: any, ...args: any[]) void签名这正是本 ADR 中把错误作为第二参数传入得以成立的接口基础getDefaultLogProvider(logLevel LogLevel.error)基于 log4js 把日志输出到 console默认兜底级别是errorvalidateLogProvider会在启动时校验注入的 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_LEVELinfo与 ADR 表中默认部署配置 sets LOG_LEVELinfo对齐本地开发可设LOG_LEVELdebug甚至LOG_LEVELtrace观察细粒度流程无论怎么配置error级别始终处于最低阈值之下不会被任何合理配置屏蔽——这正是ERROR 不可配置的工程落地。落地检查清单如何判断一条日志该用什么级别把 ADR 的语义、两个实战案例与源码基础设施结合可以总结出一份可复用的评审清单判断是否可自愈 / 值班人员能否干预如果系统会自动恢复、SRE 登录后无事可做用 WARN如 traffic-data 保存失败、lastSeen 更新失败只有需要立即修复的异常才用 ERROR。估算健康应用中的频率对照矩阵——期望 0 条 → ERROR每小时 1–10 条 → WARN10–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),仅供参考