ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

@effect/opentelemetry 日志修复实录:Effect LogLevel 与 OTel SeverityNumber 的规范映射

@effect/opentelemetry 日志修复实录:Effect LogLevel 与 OTel SeverityNumber 的规范映射 effect/opentelemetry 日志修复实录Effect LogLevel 与 OTel SeverityNumber 的规范映射【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3codeeffect/opentelemetry是 Effect 生态中把 Effect 日志、追踪与指标导出到 OpenTelemetry SDK 的集成包本仓库对应的源码位于 .repos/effect-smol/packages/opentelemetry。本文基于该仓库的变更记录 fix-otel-logger-severity-number.md讲解其中一项关键修复OtelLogger.make此前把 Effect 内部日志级别序号如Info20000直接当作 OpenTelemetry 日志的severityNumber字段上报导致该字段超出 OTel 日志数据模型规定的1~24范围被 Honeycomb、Datadog 等严格校验的后端归类为UNSPECIFIED。修复后二者按规范逐级映射并导出了可复用的辅助函数logLevelToSeverityNumber。读完本文你将理解 OTel SeverityNumber 规范、该 Bug 的根因与修复路径以及如何在自己的 Effect 应用中正确接入 OTel 日志。背景Effect 日志如何接入 OpenTelemetry Logs在了解修复之前先明确这条链路。OtelLogger.ts模块的职责是把 Effect 的日志事件log event转换为 OpenTelemetry 的日志记录log record其核心构件如下见 OtelLogger.tsOtelLoggerProvider一个Context.Service内部持有 OpenTelemetry 的LoggerProvider是日志导出的出口make工厂函数从OtelLoggerProvider与Clock.Clock构建出一个 Effect 的Logger任何 Effect 日志经它 emit 到 OTellayer安装层把make创建的 logger 合并mergeWithExisting默认为true进当前应用设为false则替换现有 logger 集合layerLoggerProvider作用域化的 provider 层接收一个或多个LogRecordProcessor配合当前Resource创建Otel.LoggerProvider并在层释放时先forceFlush再shutdown超时默认 3 秒。整个模块通过 index.ts 以OtelLogger命名空间导出。安装方式见 opentelemetry/README.mdnpm install effectrc effect/opentelemetryrc其中opentelemetry/*系列 SDK 包按需作为 peer dependency 提供。问题根因把内部序号当成了规范值变更记录明确指出修复前的问题OtelLogger.make使用LogLevel.getOrdinal(level)作为severityNumber上报。而 Effect 的getOrdinal返回的是内部排序序号——在 LogLevel.ts 中定义Trace对应10000、Info对应20000、Warn对应30000、Error对应40000、Fatal对应50000粒度是 10000 的倍数。问题在于OpenTelemetry 日志数据模型Logs Data Model规定的SeverityNumber合法范围是1 到 24。getOrdinal产出的 20000、40000 这类数值远远超出该区间属于无效值。对于 Honeycomb、Datadog 等会对该字段做合法性校验的后端这类值会被统一归并为UNSPECIFIED即 0使得日志的严重级别在传输与存储过程中被抹平无法按级别过滤与告警。修复方案按 OTel 规范逐级映射修复的核心是彻底替换取值来源不再使用 Effect 内部序号而是建立一张规范化的映射表将六个 Effect 日志级别逐一对应到 OTel 规范规定的 SeverityNumber。变更记录给出了完整映射Effect LogLevelOTel SeverityNumberTraceTRACE (1)DebugDEBUG (5)InfoINFO (9)WarnWARN (13)ErrorERROR (17)FatalFATAL (21)这张表遵循 OTel 规范中“每个级别对应间隔 4 的奇数数值”的设计TRACE1、DEBUG5、INFO9、WARN13、ERROR17、FATAL21预留了偶数位供将来细分级别使用例如 INFO 与 WARN 之间可插入 10~12 的中间值。因此映射后上报的日志在 Honeycomb、Datadog 等支持 OTel 日志语义的后端中可以正确展示级别、按级别过滤并触发对应的告警策略。源码级验证logLevelToSeverityNumber的实现在 OtelLogger.ts 中可以看到该辅助函数的具体实现export const logLevelToSeverityNumber (level: LogLevel.LogLevel): SeverityNumber { switch (level) { case Trace: return SeverityNumber.TRACE case Debug: return SeverityNumber.DEBUG case Info: return SeverityNumber.INFO case Warn: return SeverityNumber.WARN case Error: return SeverityNumber.ERROR case Fatal: return SeverityNumber.FATAL default: return SeverityNumber.UNSPECIFIED } }几个值得注意的实现细节函数通过穷举switch完成六个级别的显式映射default分支兜底返回SeverityNumber.UNSPECIFIED保证类型层面未预期的新级别不会抛出异常返回值类型直接使用opentelemetry/api-logs的SeverityNumber枚举语义清晰且与 OTel SDK 完全对齐该函数标注为category converting、since 4.0.0作为公开 API 从模块导出供下游复用到任意需要“Effect 级别 → OTel 级别”转换的场景。在make中emit 日志记录时正是用该函数填充severityNumber字段见 OtelLogger.tsotelLogger.emit({ body: message.length 1 ? message[0] : message, severityText: options.logLevel, severityNumber: logLevelToSeverityNumber(options.logLevel), timestamp: hrTime, observedTimestamp: hrTime, attributes })可以看到severityText仍保留 Effect 的原始级别字符串如Info与数值型的severityNumber互为补充符合 OTel 规范对二者关系的要求severityText是给人看的文本severityNumber是给系统做过滤和排序的数值。测试保障六级别映射被逐项断言该修复并非无凭据的改动仓库中的测试 OtelLogger.test.ts 通过InMemoryLogRecordExporter对映射做了端到端验证依次执行Effect.logTrace、logDebug、logInfo、logWarning、logError、logFatal随后把导出记录按severityText建索引逐项断言其severityNumber与SeverityNumber枚举值严格相等。值得注意该测试通过Layer.succeed(References.MinimumLogLevel, Trace)把最小日志级别降到Trace确保六条日志全部实际产生再对byText.Trace SeverityNumber.TRACE、byText.Error SeverityNumber.ERROR等断言做严格校验。这从测试层面固化了修复后的行为防止后续改动把映射回退成getOrdinal的实现。应用示例在 Effect 应用中启用 OTel 日志结合上述 API一个完整启用 OTel 日志的最小结构如下参考 OtelLogger.test.ts 中NodeSdk.layer的用法import * as NodeSdk from effect/opentelemetry/NodeSdk import { SimpleLogRecordProcessor, InMemoryLogRecordExporter } from opentelemetry/sdk-logs const LoggingLayer NodeSdk.layer(() ({ resource: { serviceName: my-service }, logRecordProcessor: [new SimpleLogRecordProcessor({ exporter: new InMemoryLogRecordExporter() })] })) // 在程序入口 Effect.provide(LoggingLayer) 后Effect.log 系列调用 // 会自动以规范化的 SeverityNumber 上报到 OTel在实际项目中把InMemoryLogRecordExporter替换为OtlpLogRecordExporter等生产级导出器即可把日志送往 Collector、Honeycomb 或 Datadog 等后端而借助logLevelToSeverityNumber这一公开导出自定义的日志适配器也可以在 OTel 生态外复用同一套规范的级别换算逻辑。关联变更与演进方向该修复属于effect/opentelemetry的patch级变更见 .changeset/pre/fix-otel-logger-severity-number.md 的 frontmatter不破坏既有 API 签名仅修正数值语义并新增导出。同目录下还并存着多条 OTel 日志相关的预发布变更例如 explicit-otel-service-identity.md、otel-resource-env-precedence.md、fix-otel-logger-clock-skew.md、fix-otel-logger-shutdown.md 与 preserve-otel-parent-context.md它们共同勾勒出effect/opentelemetry日志链路在服务标识、环境变量优先级、时钟对齐、优雅关闭与上下文保持等方面的持续完善方向。小结fix-otel-logger-severity-number这项变更以一张规范映射表解决了 Effect 日志接入 OpenTelemetry 时最容易被忽视的“数值语义”问题内部排序序号与行业标准数值是两个不同的概念前者服务于框架自身的比较逻辑后者必须严格遵循 OTel 日志数据模型。修复通过新增logLevelToSeverityNumber辅助函数、在make中替换取值来源、并配以逐级别断言的测试让 Effect 应用导出的日志在 Honeycomb、Datadog 等后端中能够被正确识别级别、过滤与告警——这也是任何把 Effect 日志接入 OTel 生态的应用都应当遵循的基线行为。【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进