深入 OpenTelemetry 00 - 导读与第一条 Trace
一个订单查询调用了 span.end(),是否就意味着在可观测性平台能查到请求?不能。结束 Span 只是生成与交付流程中的一个本地边界。先用单进程 Java 程序拿到一条真实的 SpanData,再看这条证据究竟能证明什么。
一条请求的两个路径
订单查询是业务路径;遥测记录是另一条路径。本篇只运行单进程 order.lookup,库存服务、数据库和接收端还不存在。
1 | |
Tracer 与 Span 是业务代码调用的 API;SdkTracerProvider、SimpleSpanProcessor、exporter 是可替换的 SDK 配置。内存导出器不是网络接收器,业务成功也不是 export 成功。后续章节才在此订单场景上增加远端服务和其他信号。
从入口追到结束
冻结的上游 Java SDK 为 v1.31.0,commit c25c0a0ee0da01ab2f74ba83052d1c249ed57020。公开的 OpenTelemetry.getTracer 委托给 tracer provider;SdkTracerProvider.get 生成带 instrumentation scope 的 tracer。spanBuilder("order.lookup") 调用的是公开 API,内部则进入 SdkSpanBuilder.startSpan。这里取得父上下文、生成 ID、请求采样器做决定;不记录的分支在 第 207 行 返回不可记录的 Span 包装对象,而不是一条待导出的 SpanData。
对于本篇默认可记录的路径,Span.end() 在 SdkSpan.endInternal 设置结束时间、标记已结束,然后调用处理器的 onEnd;第二次 end 不重复交付。SimpleSpanProcessor 在结束调用所在的线程触发 exporter;InMemorySpanExporter.export 将数据追加到内存列表。它的 shutdown 分支 会清空列表,故测试必须在关闭 provider 前断言。上游对应检查可从 InMemorySpanExporterTest 复核。以上是冻结版本的静态调用链,尚未说明本机网络行为。
运行一次最小实验
教学工程在 examples/opentelemetry-java/。用 JDK 21 从该目录运行:
1 | |
第二条命令调用 FirstTrace.main:构造 provider 和 Simple processor,启动并结束 order.lookup,在 provider 关闭前检查内存导出器的列表。Java 程序实际输出为:
1 | |
Lab00Test 还逐项检查名称、有效 Trace ID/Span ID、结束时间,并先确认结束前列表为空。这里的 validIds=true 只是格式有效性,不能据此声称跨服务传递成功。程序没有打开监听端口,没有发送 OTLP,也没有执行后端查询。命令、工具链和原始测试输出保留在工程 evidence/00/。
可迁移的边界
程序里最多能证实“SDK 的结束回调到达了内存 exporter”。改成 Batch 处理器后 end() 甚至可能仅意味着排队;接收数还要由接收端另行验证。把“业务返回”“Span 已生成”“被导出”“被接收”“可查询”分成不同检查点,排障才不会从一个本地列表直接跳到后端结论。
常见误解。 API 依赖不会自动安装 SDK;没有 SDK 时相同 API 调用可能走 noop。另一个误解是以有效 ID 推断整个 Trace 完整:一个 Span 的 ID 不能证明任何兄弟 Span 或下游 Span 到达。
练习一。 在 FirstTrace 中删掉 span.end(),再运行 Lab00Test 的对应变体;预测并检查导出列表长度。把结论限定在 Simple processor + 内存 exporter 的组合。
练习二。 不改业务代码,将处理器换为 BatchSpanProcessor,思考为什么读取列表的时机会改变;记录需要新增哪种 flush/等待断言,不把可能的异步结果解释成网络丢包。
系列导航与资料
当前篇:00 导读与第一条 Trace。下一篇:01 API、SDK 与初始化(待写)。
参考资料:OpenTelemetry Java API、Java SDK、上文固定 SHA 的实现与测试(Apache-2.0);实验为独立教学代码,不是上游源码。规范与 SDK 版本索引见工程 writing-plans/opentelemetry-java/VERSIONS.md。
