
后端可观测性链路追踪【免费下载链接】tempoGrafana Tempo is a high volume, minimal dependency distributed tracing backend.项目地址https://gitcode.com/GitHub_Trending/tempo1/tempo点击查看免费下载导读本篇文章围绕 Tempo 仓库中随 vendored 引入的github.com/prometheus/otlptranslator库展开讲解如何将 OpenTelemetry 协议OTLP中的指标名、属性名与单位转换为符合 Prometheus/OpenMetrics 规范的命名格式。无论你是负责遥测数据落库的存储层开发者还是需要把 OTLP 指标接入 Prometheus 生态如 remote write、指标生成器的运维工程师读完后都能掌握 MetricNamer / LabelNamer / UnitNamer 三个核心组件的用法、四种翻译策略的取舍以及_total、_ratio、单位后缀等命名规则的底层实现逻辑。为什么需要 OTLP 到 Prometheus 的命名翻译OTLP 协议遵循 OpenTelemetry 语义约定指标名习惯使用点分隔的层级结构例如http.server.request.duration、requests.count属性名同样允许包含.、、$等符号而 Prometheus 的经典命名规范legacy scheme只允许[a-zA-Z0-9:_]出现在指标名中标签名则进一步限制为[a-zA-Z0-9_]。当这两套体系相遇时如果直接把 OTLP 名称原样写入 Prometheus 格式的时间序列轻则无法被 PromQL 正确引用重则产生序列碰撞。otlptranslator正是为了解决这一兼容问题而存在的 Go 库。它由 Prometheus 与 OpenTelemetry 两个社区共同维护源码文件头部保留了来自 prometheus/prometheus 与 opentelemetry-collector-contrib 的 provenance 标注遵循 OpenTelemetry 到 Prometheus 兼容性规范其核心职责可以概括为三件事指标名翻译把 OTLP 指标名转换为 Prometheus 兼容格式单位处理把 OTLP 单位采用 UCUM c/s 记法转换为 Prometheus 单位约定类型感知后缀根据指标类型追加_total、_ratio等语义后缀。在 Tempo 仓库中该库以间接依赖indirect形式引入记录于根目录 go.modgithub.com/prometheus/otlptranslator v1.0.0 // indirect其源码整体 vendored 在 vendor/github.com/prometheus/otlptranslator 目录下。安装与引入该库是标准 Go 模块通过go get即可引入go get github.com/prometheus/otlptranslator需要注意的是它本身是 Prometheus 与 OpenTelemetry 的内部库README 明确声明对外部使用者不提供稳定性保证without any stability guarantees for external usageAPI 可能随上游演进而调整。Tempo 采用 vendor 机制锁定版本因此在 vendor/github.com/prometheus/otlptranslator 下保留了完整的源码副本这也是我们本文所有源码引用的事实依据。快速上手三个 Namer包文档doc.go给出了整个库的结构概览全部功能收敛为三个辅助类型MetricNamer把 OTLP 指标名翻译为 Prometheus 指标名LabelNamer把 OTLP 属性名翻译为 Prometheus 标签名UnitNamer把 OTLP 单位翻译为 Prometheus 单位约定。MetricNamer 基本用法package main import ( fmt github.com/prometheus/otlptranslator ) func main() { // 使用传统 Prometheus 命名翻译追加后缀且不允许 UTF-8 名称 namer : otlptranslator.NewMetricNamer(myapp, otlptranslator.UnderscoreEscapingWithSuffixes) metric : otlptranslator.Metric{ Name: http.server.request.duration, Unit: s, Type: otlptranslator.MetricTypeHistogram, } // 注意Build 实际返回 (string, error) 两个值 name, err : namer.Build(metric) if err ! nil { panic(err) } fmt.Println(name) // myapp_http_server_request_duration_seconds // 翻译标签名 labelNamer : otlptranslator.LabelNamer{UTF8Allowed: false} label, err : labelNamer.Build(http.method) if err ! nil { panic(err) } fmt.Println(label) // http_method }上面这个例子完整覆盖了命名翻译的典型链路NewMetricNamer工厂函数接受命名空间与翻译策略Metric.Build会依次执行“字符转义 → 单位后缀 → 类型后缀 → 命名空间前缀”的规范化。需要提醒的是README 中的示例省略了Build的错误返回值而源码签名metric_namer.go实际是func (mn *MetricNamer) Build(metric Metric) (string, error)工程上务必处理该 error。四种翻译策略TranslationStrategyOption翻译策略是整个库的开关中枢定义于 strategy.go。NewMetricNamer正是通过策略的两个判定方法决定MetricNamer的最终配置ShouldEscape()是否需要转义非法字符为下划线ShouldAddSuffixes()是否追加单位与类型后缀。策略常量ShouldEscapeShouldAddSuffixes行为说明UnderscoreEscapingWithSuffixestruetrue默认推荐。指标名中非[a-zA-Z0-9_:]字符转义为_标签名中非[a-zA-Z0-9_]字符转义为_并按规则追加单位/类型后缀UnderscoreEscapingWithoutSuffixestruefalse与上者相同的字符转义但不追加任何后缀NoUTF8EscapingWithSuffixesfalsetrue接受指标/标签名原样要求合法 UTF-8仅按规则追加单位与类型后缀NoTranslationfalsefalse实验性。完全禁用翻译允许 OTLP 用户使用原生指标名源码中明确给出了各策略对应的具体常量值与注释strategy.go其中UnderscoreEscapingWithSuffixes是 OTLP→Prometheus 翻译的默认选项。关于 NoTranslation 的风险提示NoTranslation策略虽然在代码中以TranslationStrategyOption的形式存在但源码注释反复强调它是EXPERIMENTAL实验性且不应在生产系统使用主要风险包括在 YAML 形式的 PromQL 中使用原生指标名如 alerts、rules、dashboard、autoscaling 配置时用户体验受损可能引发序列碰撞series collisions最坏情况下静默产生畸形时间序列。例如同一个foo.bar指标名一个带单位seconds、一个带单位milliseconds二者会落成两条不同的序列。因此面向生产环境应优先选择UnderscoreEscapingWithSuffixes完整 Prometheus 风格兼容或NoTranslation仅用于 OTel 原生风格的原型验证。指标名转换类型感知的完整规则链MetricNamer的结构体定义metric_namer.go只有三个字段type MetricNamer struct { Namespace string // 可选命名空间前缀 WithMetricSuffixes bool // 是否追加类型/单位后缀 UTF8Allowed bool // 是否允许 UTF-8 原生名称 }合规模式UTF8Allowedfalse的完整流程当不允许 UTF-8 时Build走buildCompliantMetricName分支内部调用normalizeName其处理链条为Token 拆分以合法字符集[a-zA-Z0-9:]为界用strings.FieldsFunc把指标名切成 token。这一步顺带把连续多个下划线折叠为单个下划线这是 OTel→Prometheus 规范的一部分单位后缀根据单位表生成主单位与 per 单位后缀追加到 token 末尾并做去重与尾部下划线清理addUnitTokens_total后缀当指标类型为MetricTypeMonotonicCounter单调递增计数器即累计 Counter时追加_total若 token 中已有total则先移除再追加_ratio后缀当单位恰好为1且类型为 Gauge 时追加_ratio。源码注释解释了一个现实背景部分 OTel receiver 不规范地把计数类指标的单位写成1为避免误伤目前只为 Gauge 追加_ratio——理论上 Counter 也可以表达比率但从数学上不合理因此不做命名空间前缀若设置了 Namespace作为第一个 token 前置数字开头防御规范化结果若以数字开头则前置_。无后缀模式下的轻量处理当WithMetricSuffixesfalse时对应UnderscoreEscapingWithoutSuffixesbuildCompliantMetricName退化为轻量路径仅做字符转义、命名空间前缀与数字开头防御不涉及单位换算metric_namer.go。错误处理与边界防御buildCompliantMetricName通过defer做了两项兜底校验规范化结果为空时返回normalization for metric %q resulted in empty name规范化结果全部由下划线组成不含任何非下划线字符时返回normalization for metric %q resulted in invalid name %q。这意味着像...这类全部由非法字符组成的指标名会被拒绝而不是生成一个毫无意义的___。实战示例namer : otlptranslator.MetricNamer{WithMetricSuffixes: true, UTF8Allowed: false} // Counter 追加 _total counter : otlptranslator.Metric{ Name: requests.count, Unit: 1, Type: otlptranslator.MetricTypeMonotonicCounter, } name, _ : namer.Build(counter) // requests_count_total // Gauge 带单位换算 gauge : otlptranslator.Metric{ Name: memory.usage, Unit: By, Type: otlptranslator.MetricTypeGauge, } name, _ namer.Build(gauge) // memory_usage_bytes // 无量纲 Gauge 追加 _ratio ratio : otlptranslator.Metric{ Name: cpu.utilization, Unit: 1, Type: otlptranslator.MetricTypeGauge, } name, _ namer.Build(ratio) // cpu_utilization_ratioMetricType 的完整取值MetricType在 metric_type.go 中定义为整数常量覆盖 OpenTelemetry 数据模型中的全部类型其中 Sum 类型按时间性temporality拆分MetricTypeUnknown未知类型MetricTypeNonMonotonicCounter非单调递增计数器即 Delta 计数器MetricTypeMonotonicCounter单调递增计数器即累积 Counter唯一触发_total后缀的类型MetricTypeGauge仪表单位1时触发_ratio后缀MetricTypeHistogram直方图MetricTypeExponentialHistogram指数直方图MetricTypeSummary摘要单位转换unitMap 与 per 单位规则单位翻译由UnitNamer承担unit_namer.go其实现依托两张映射表。主单位表 unitMapOTLP 单位采用 UCUM c/s 记法见 OpenTelemetry 语义约定文档下表是源码中完整的映射metric_namer.go类别OTLP 单位Prometheus 单位时间d/h/min/s/ms/us/nsdays / hours / minutes / seconds / milliseconds / microseconds / nanoseconds字节By/KiBy/MiBy/GiBy/TiBy/KBy/MBy/GBy/TBybytes / kibibytes / mebibytes / gibibytes / tibibytes / kilobytes / megabytes / gigabytes / terabytesSIm/V/A/J/W/gmeters / volts / amperes / joules / watts / grams其他Cel/Hz/1/%celsius / hertz / 空无量纲/ percent值得注意的是字节单位的细粒度区分KiBy/MiBy/GiBy/TiBy映射为二进制前缀的kibibytes / mebibytes / gibibytes / tibibytes而KBy/MBy/GBy/TBy映射为十进制前缀的kilobytes / megabytes / gigabytes / terabytes这正是 Prometheus 最佳实践对单位严谨性的要求。per 单位表 perUnitMap对于带分母的单位如requests/sperUnitMap提供单数形式的 per 单位metric_namer.gos→second、m→minute、h→hour、d→day、w→week、mo→month、y→year并在前面拼接per_。UnitNamer 的拼接逻辑unitNamer : otlptranslator.UnitNamer{UTF8Allowed: false} unitNamer.Build(s) // seconds unitNamer.Build(By) // bytes unitNamer.Build(requests/s) // requests_per_second unitNamer.Build(1) // 无量纲其内部buildUnitSuffixes先按/拆分为主单位与 per 单位跳过包含{}的分量OTLP 中{}表示任意单位文本如requests本身cleanUpUnit负责把非法字符替换为下划线并折叠连续下划线、去除前导下划线unit_namer.go。这一逻辑同样服务于MetricNamer——normalizeName内部正是复用buildUnitSuffixes完成指标名的单位后缀。标签转换保留保留字、防御数字开头LabelNamerlabel_namer.go负责把 OTLP 属性名规范化为 Prometheus 标签名规则如下非法字符非[a-zA-Z0-9]替换为下划线以数字开头的标签名前置key_保留__双下划线包裹的保留标签原样如__name__、__metrics_path__这类 Prometheus 内部标签标签为空或规范化后全是下划线时报错。labelNamer : otlptranslator.LabelNamer{UTF8Allowed: false} labelNamer.Build(http.method) // http_method labelNamer.Build(123invalid) // key_123invalid labelNamer.Build(_private) // _private默认不特殊处理 labelNamer.Build(__reserved__) // __reserved__保留原样 labelNamer.Build(labelwith$symbols) // label_with_symbolsLabelNamer还有两个可选字段label_namer.goUnderscoreLabelSanitization为以单下划线开头非__的标签前置key该字段已标记Deprecated将在未来版本移除PreserveMultipleUnderscores在UTF8Allowedfalse时保留连续多个下划线。该选项不推荐开启因为它违反了 OTel→Prometheus 规范中“连续下划线折叠为单个”的要求仅用于兼容依赖旧行为的遗留系统。底层sanitizeLabelName与isReservedLabel的实现位于 strconv.go保留标签的判定条件是同时以__开头且以__结尾长度至少 4命中后先剥离双下划线再做字符净化最后重新包裹__...__从而确保内部非法字符同样被规范化。配套常量exemplar、scope 与 target_info除三个 Namer 外包内还定义了与 OTLP→Prometheus 兼容规范配套的常量constants.go在实现链路打通时经常用到常量值用途ExemplarTraceIDKeytrace_idPrometheus exemplar 中保存 Trace ID 的标签键ExemplarSpanIDKeyspan_idPrometheus exemplar 中保存 Span ID 的标签键ScopeNameLabelKeyotel_scope_name标识产生指标的 OpenTelemetry instrumentation scope 名称ScopeVersionLabelKeyotel_scope_version标识 instrumentation scope 版本TargetInfoMetricNametarget_info以指标形式保留资源属性resource attributes源自 OpenMetrics 的 target metadata 机制对 Tempo 这类以 trace 为核心的系统而言trace_id/span_id常量意味着当 OTLP 指标通过 exemplar 关联到具体 trace 时链路追踪 ID 可以以标准标签键形式落在 Prometheus 序列上形成 metrics 与 traces 的关联闭环。在 Tempo 中的定位与使用建议从仓库证据看Tempo 当前通过go.mod以间接依赖引入该库go.mod源码整体 vendored 在 vendor/github.com/prometheus/otlptranslator 目录。也就是说它是 Tempo 对外输出 OTLP 指标如 metrics-generator 生成的服务指标、exemplar 关联到 Prometheus 生态时负责名称与单位规范化的底层基础设施而不是 Tempo 自研的业务模块。结合源码给出三条实践建议生产环境选择UnderscoreEscapingWithSuffixes它同时覆盖字符转义、单位后缀与类型后缀产出完全符合 Prometheus 命名最佳实践的指标名NoTranslation仅适合实验场景认真处理Build的错误返回值空名称与全下划线名称都会被拒绝忽略 error 会把非法序列静默写入下游利用_ratio/_total后缀规则反推数据正确性如果你看到某个指标意外带上了_ratio通常意味着上游 receiver 把单位写成了1——这正是源码注释中点名的 OTel receiver 常见不规范行为排查指标命名异常时可优先检查单位字段。总结otlptranslator用三个 Namer 与一套策略常量把 OTLP 到 Prometheus 的命名兼容这一脏活收敛为可测试、可配置的 Go APIMetricNamer处理指标名的 token 化、单位换算与类型后缀LabelNamer处理标签字符净化与保留字保护UnitNamer提供单位到 Prometheus 约定的一一映射TranslationStrategyOption则让调用方在完整兼容与原生透传之间自由取舍。理解这些规则既能帮你写出正确的转换代码也能让你在排查 Prometheus 序列命名异常时迅速定位到根因。赞分享后端可观测性链路追踪【免费下载链接】tempoGrafana Tempo is a high volume, minimal dependency distributed tracing backend.项目地址https://gitcode.com/GitHub_Trending/tempo1/tempo点击查看免费下载相关推荐Loki 中的 OTLP 与 Prometheus 命名转换otlptranslator 库实战指南Loki 中的 OTLP 与 Prometheus 命名转换otlptranslator 库实战指南 导读 Grafana Loki 在接收 OpenTele可观测性日志分析后端微服务对象存储云原生深入解析 prometheus/otlptranslatorOTLP 指标名与标签名到 Prometheus 命名规范的转换指南深入解析 prometheus/otlptranslatorOTLP 指标名与标签名到 Prometheus 命名规范的转换指南 OTLPOpenTelem时序数据库数据库指标监控可观测性后端buildkit 依赖探秘OTLP 指标与 Prometheus 命名的 Go 翻译库 otlptranslatorbuildkit 依赖探秘OTLP 指标与 Prometheus 命名的 Go 翻译库 otlptranslator 导读 otlptranslator ht构建工具云原生后端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考