一个业务计数器已经挂在 Micrometer MeterRegistry 上,又在同一处理函数直接调用 OpenTelemetry SDK 计数器;两个路径都叫 duplicate.orders。此时看到两条点,是 bridge 内部重试、reader 重复导出,还是业务代码真正记录了两次?答案要从记录入口和 instrumentation scope 入手,不能只看指标名。

本篇固定 Java SDK 1.31.0 源码 SHA c25c0a0ee0da01ab2f74ba83052d1c249ed57020、instrumentation bridge 1.31.0-alpha 源码 SHA 97d87f3f3b2bc61aa396d71a98387a1e857618af。测试 classpath 上引入 Micrometer Core 1.12.5 与桥接 JAR,具体 artifact/JAR SHA-256 在 writing-plans/opentelemetry-java/VERSIONS.md。本机完整 JDK 21.0.12 执行测试;没有挂 Agent,也没有建立第二套全局 SDK。

bridge 是 MeterRegistry,不是 exporter

固定 instrumentation SHA 的 OpenTelemetryMeterRegistry.java L33–52 返回桥接 registry,newCounter 在 L74–77 建立 OpenTelemetryCounter。OpenTelemetryMeterRegistryBuilder.java L86–102 从传入的 OpenTelemetry 对象获取 io.opentelemetry.micrometer-1.5 scope 的 Meter。这个 registry 把记录转成 SDK instrument,不负责连接 Collector。

OpenTelemetryCounter.java L29–49 以命名约定转换名称、以 base unit 建 double counter;increment 把 Micrometer tags 转为 Attributes,调用 SDK add;移除后不再记录。固定 SDK SHA 的 SdkMeter.java L83–90 创建直接 SDK long counter;InMemoryMetricReader.java L70–90 的 create() 默认 cumulative,而 createDelta() 是另一种显式选择。桥接上游 AbstractCounterTest.java L29–71 也检查单位、tag 与 add 后移除;这里是静态阅读上游测试,并未在本机跑其 Gradle 测试。

模型是 Micrometer counter → OpenTelemetryMeterRegistry → OTel double counter → reader;另一条是 业务代码 → OTel long/double counter → 同一个 reader。同名不代表同一条业务采集路径,scope 也参与标识。

固定样本:从两点到四点

本地自写 examples/opentelemetry-java/sdk-labs/src/test/java/org/example/otel/E03Test.java 使用 SDK 进程内 reader。向 bridge.orders 的 Micrometer counter 和 sdk.orders 的直接 SDK counter 分别记录 2,均附 route=checkout、单位 items。第一次 collect 精确得到两条 metric/两个点:bridge 为 double sum,direct 为 long sum,两个 temporality 都是 CUMULATIVE,值都为 2。

名称 记录入口 scope 单位 / temporality 点数与值
bridge.orders Micrometer registry io.opentelemetry.micrometer-1.5 items / CUMULATIVE 1 点,double 2
sdk.orders SDK 直调 manual items / CUMULATIVE 1 点,long 2
duplicate.orders 两个入口各执行一次 两个不同 scope items / CUMULATIVE 各 1 点,double 1 + 1

随后用两个代码入口记录同一个逻辑事件:

1
2
duplicateBridge.increment();
duplicateDirect.add(1, Attributes.of(AttributeKey.stringKey("route"), "checkout"));

第二次 collect 为四条 metric/四个点,其中 duplicate.orders 占两条:scope 各为 io.opentelemetry.micrometer-1.5 与 manual,每条一个 double sum 点,单位 items、值 1、CUMULATIVE。先前两点再次出现是 cumulative reader 的读取视图,不能把“第二次 collect 的四点”说成“只记录四个新事件”。重复的直接原因是registry.increment 与 direct.add 都运行了,不是桥接内部自动重复。完整命令、退出码和摘要见 examples/opentelemetry-java/evidence/E03/RUN.md。

边界与常见误解

  • 直接创建 OTel Counter 并不能证明 Micrometer bridge 正常工作;本次确实创建 OpenTelemetryMeterRegistry,从 Micrometer Counter 进入桥接。
  • 同名两点分别属于不同 scope;不能简单相加称为一个 SDK 点,也不能据此推断某个后端的去重策略。多个 registry、重复绑定或附加 Agent 可能带来其他重复路径,本实验没有覆盖。
  • items 是这里的显式 base unit;名称保持输入时的形态只适用于本实验默认 identity naming convention,不代表所有 Prometheus mode 命名规则。
  • create() 的 CUMULATIVE 是这里选定的进程内 reader 行为;没有运行 DELTA reader、导出 OTLP Metrics、Collector、存储查询或采样负载。

两道练习

  1. 修改 E03Test:只保留 duplicateBridge.increment(),删去同名 direct 的 add;重新运行并检查 duplicate.orders 只剩哪个 scope、几点。对照原结果,找出重复的必要第二条记录路径。
  2. 构造 InMemoryMetricReader.createDelta() 的对照:连续两次 collect 前各 increment 一次,断言各窗口的值和 temporality。不要把第二次 Delta 点与本篇第二次 Cumulative 读取混为一谈。

导航与参考资料