深入 OpenTelemetry 16:Histogram 的桶不是精确分位数
有六个延迟,能求出准确 P95 吗? 固定延迟 [0, 10, 11, 20, 50, 70] 毫秒,count=6、sum=161。若选择显式边界 [10,20,50],采集得到四个桶 [2,2,1,1];仅凭这四个数字,不能还原每条请求的延迟,更无法精确还原 P95。桶累计的是落在哪个范围的次数,而不是原始样本列表。 值 桶(显式边界) 次数 0、10 ≤10 2 11、20 (10,20] 2 50 (20,50] 1 70 >50 1 两种桶的数据对象和源码路径 以下为对 Java SDK 完整 SHA c25c0a0ee0da01ab2f74ba83052d1c249ed57020 的静态阅读。Aggregation.explicitBucketHistogram 可以设置显式边界;默认 histogram 使用该类聚合,默认桶不是本次自定义的 [10,20,50]。DoubleExplicitBucketHistogramAggregator.Handle 维护 count/sum/桶;ExplicitBucketHistogra...
深入 OpenTelemetry 17:Exemplar 怎样携带一次 Trace 的线索
从一个延迟桶能找到对应的请求吗? Histogram 的聚合点只保留 count、sum 和桶分布,正常情况下不能从一个桶反推出特定请求。Exemplar 是保留在某些观测上的样本:记录时间、原始测量值及关联的 SpanContext。它提供可能的跳转线索,不是每条延迟都必有一条 Trace,也不等于系统已经保存了该 Trace。 固定事件只有三条:/orders 在无 Span 时记录 10,在 sampled Span 中记录 30;/stock 无 Span 时记录 50。两个 route 属性集合分别形成一个点。期望前者聚合值为 count=2、sum=40,同时保留一个值为 30 且匹配 sampled Span 的 exemplar;后者 count=1,无 exemplar。无 Span 的观测仍然增加聚合计数,不会因 exemplar filter 被丢弃。 指标属性 测量值 / 记录上下文 聚合点 Exemplar route=/orders 10/无 Span、30/sampled Span count=2、sum=40 值 30,Span ID...
深入 OpenTelemetry 18:Logs SDK 从 emit 到批量导出
调用 emit 后,日志已经写出去了吗? logRecordBuilder().emit() 只把一条记录交给本地 processor;它既不保证已被导出,也不负责把 Logback 的普通日志自动接进来。为了分开观察“产生记录”和“处理队列”,实验设置了极长的批处理调度间隔,发出一条日志,再显式 flush。固定的事件时间是 epoch 90 秒,观察时间由测试时钟提供,为 100 秒。 阶段 本篇对象 可以判定的事实 不代表 API 调用 Logger / LogRecordBuilder body、severity、事件时间被设置 已导出 processor 队列、工作线程 已排队,可能因队列满被丢弃 已接收 exporter InMemoryLogRecordExporter 内存列表里可读到 LogRecordData Collector 或后端已入库 从 SDK 源码看对象何时定型 以下为 opentelemetry-java v1.31.0 固定 SHA c25c0a0ee0da01ab2f74ba83052d1c249ed57020 的...
深入 OpenTelemetry 19:Logback 的 MDC 和 Logs 桥接各做了什么
日志里出现 trace_id,就等于送进 Logs SDK 吗? 业务日志的文本带了 Trace ID,只能证明某条输出可供人关联。要将其变为 OTel LogRecordData,Logback 还需另一个 appender 将事件映射到 Logs API。本篇用同一 logger 的三个阶段核对:普通 ListAppender → MDC 装饰器包住 ListAppender → 额外安装 Logs 桥接 appender。每个阶段只发一条日志,禁用向 root logger 传播,避免实验误把重复传播当成桥接行为。 阶段 ListAppender 新增事件 trace_id 在输出事件 MDC 中 内存 Logs exporter 新增记录 普通日志、无 Span 1 无 0 仅 MDC 装饰器、有效 Span 1 有 0 MDC 与 OTel appender、有效 Span 1 有 1 两条源码路径不要混成一条 instrumentation 仓库固定 tag v1.31.0,完整 SHA 97d87f3f3b2bc61aa396d71a98...
深入 OpenTelemetry 20:三信号如何关联又不泄漏字段
同一次请求,哪些字段应跨越三条信号? 一次订单请求带有路由模板 /orders/{id}、方法 GET,也可能接触认证头和 SQL 绑定值。关联日志与指标需要的是 Trace ID 和 Span ID,不必让令牌或原始参数进入属性,更不该把每个订单 ID 当成 histogram 维度。本篇把范围限定为一个受控线程、一个请求 Scope、一个 Logback appender 和进程内三种数据读取;测试既证明安全的记录方式,也构造一个 View 过滤后仍泄漏 exemplar 的反例。 位置 允许字段 控制措施 验证对象 Span route=/orders/{id}、http.request.method=GET 业务侧只赋允许键;SDK 上限设为 2 内存 SpanData Logs 固定消息、logback.mdc.route Logback 桥接只捕获 route;SDK 上限设为 1 内存 LogRecordData Metrics histogram 聚合 route 模板 测量输入只传允许字段,View 再保留 route reader 的点及...
深入 OpenTelemetry 21:premain 如何把 Agent 接进 JVM
同一份应用,为何有无 -javaagent 的 Span 不同? 一个只调用 GlobalOpenTelemetry.get() 的程序,不装 Agent 时可以正常运行,却只得到 no-op Span。增加 Agent 后,不改业务代码,Span 变为 recording。问题不在“额外创建一个全局 SDK”:究竟是谁在应用 main 前安装它,又怎样隔离 Agent 自己的类?本文先看启动链,再用同一程序、两个独立 JVM核对应用侧可观察结果。 时点 所在边界 工作 premain JVM Instrumentation 找到发行 JAR,追加 bootstrap 搜索路径 初始化 bootstrap → AgentClassLoader 从内部 inst 加载 tooling 并启动 配置 Agent tooling 自动配置 SDK 为全局值、安装字节码 transformer main 应用类加载器 通过 API 读取已装好的全局实现 固定源码中的启动、默认值和失败分支 本篇冻结 SDK tag v1.31.0 的完整 SHA c25c0...
深入 OpenTelemetry 22:哪些 HTTP 方法会进入 Advice
同样是 URLConnection,为什么只观察到 HTTP Span? URL.openConnection() 可以返回 HTTP 连接,也可以返回 file 连接;方法名或接口像,并不意味着都被增强。为排除“Agent 只要启动就给每次 I/O 建 Span”的误解,本篇用 JDK 21 自带的两个实现作对照:先看模块怎样筛类型和方法,再观察只有 HTTP 请求形成一个 CLIENT Span。 步骤 决策位置 当前测试实例 模块装载 InstrumentationModule 名称 http-url-connection 类型匹配 TypeInstrumentation.typeMatcher() HTTP 子类通过;file 子类不通过 方法匹配 transform → Byte Buddy Advice connect / getInputStream 等;不等于所有方法 运行入口 Advice → Instrumenter 读取连接状态、Span 生命周期;在本地端点核对输出 固定源码:模块、类型、方法与运行时门槛 SDK SHA c...
深入 OpenTelemetry 23:Instrumenter 怎样开始、结束并避免重复 Span
一次 HTTP 请求会被几个方法观察? HttpURLConnection 的 connect()、getInputStream() 与 getResponseCode() 可在同一次请求中先后调用。如果每个 Advice 都无条件新建、结束 Span,就可能留下重复记录或未结束的 Span。另一个反例是 getInputStream() 因 500 抛出 IOException:是否自动记录异常事件,不能仅看业务侧是否捕获异常。本篇把“是否启动”“结束时看到什么”与“为什么不重复”拆开,分别用源码和三个独立 JVM 检验。 层次 开始路径 结束/去重路径 Instrumenter shouldStart → start,提取名称和起点属性 end 更新终点属性、status 和结束时间 HTTP Advice 读取当前 Context、同连接的状态 CallDepth、VirtualField 保存的状态和 finished 标志 输出 Scope 临时成为 current 客户端 200/500/重复方法各得到一个 CLIENT Span 静态源码...
深入 OpenTelemetry 24:Servlet 入口怎样接到 JDBC 调用
一次请求在入口 Servlet 里查询数据库,再请求另一个 HTTP 端点。挂上 Agent 后看到几个 Span,如何判断它们真的是同一条链,而不是恰好同一批送出的独立事件?只检查 Span 数量不够:还要核 traceId、parentSpanId、SpanKind 和 SQL 属性,并且区分成功请求与错误请求。 本文实验采用完整 JDK 21.0.12、Java Agent 1.31.0、嵌入式 Tomcat 10.1.24 和 H2 2.2.224。Agent 的发行 JAR SHA-256 为 e866de5fa4e4c2c7d076072798fb9ca65b679d9c6be7792b37d4c94e2bc686f7;Tomcat、H2 发行物的 SHA-256 和取得方法见工程的 writing-plans/opentelemetry-java/VERSIONS.md。应用没有初始化另一套全局 SDK。子进程由 Agent 负责采集,JUnit 进程只负责启动请求并解码测试 HTTP 端点收到的 OTLP Trace。 入口到数据库的状态模型 1234外部请求 G...
深入 OpenTelemetry 25:为自己的库编写 Java Agent 扩展
库没有现成的自动插桩时,业务代码可以显式创建 Span;如果不便改业务代码,也可以让 Agent 扩展匹配库的方法。困难不在调用 spanBuilder,而在确定扩展何时被找到、增强了哪个版本、辅助类由哪个加载器提供,以及异常或禁用时是否留下重复或未结束的 Span。 本文固定 JDK 21.0.12、Java Agent 1.31.0。发行 Agent 的 SHA-256 是 e866de5fa4e4c2c7d076072798fb9ca65b679d9c6be7792b37d4c94e2bc686f7。扩展的编译 API 是 io.opentelemetry.javaagent:opentelemetry-javaagent-extension-api:1.31.0-alpha(SHA-256 afc55fe952fa10fbad2836993bacc2a01cf02e1d50d53a6db9595fb97b2a37aa),Byte Buddy 编译依赖版本与 SHA-256 见 writing-plans/opentelemetry-java/VERSIONS.md;扩展 ...

