flush成功与接收成功之间
一句话总结
计量不是一次调用,而是一连串边界。必须分别确认:记录了异常与标记为错误、结束了 span 与已经导出、receiver 已接收与能从存储中查询,都是不同事实。
为什么需要它
有一个短时运行的订单验证任务。任务捕获错误并转换成响应,最后调用 force_flush,再把成功与否写入日志。日志显示 True,观测页面却没有该请求。如果直接断定“观测页面延迟”,可能漏掉 span 未结束或传输被拒绝的事实。
本实验的预实验使用真实 Python SDK 1.44.0 和官方 OTLP/HTTP exporter,让自制 receiver 分别返回 HTTP 200 与 400。exporter 结果分别为 SUCCESS 和 FAILURE,但 provider 的 force_flush 两次都返回 True。以下说明基于该观测,不能泛化为所有语言和版本都返回相同值。
工作原理
异常事件与错误状态彼此独立
Python 代码捕获并处理异常后,异常可能不会传播到 with 块外。此时不要猜测自动上下文管理会记录什么,而应分别检查当前 span 留下的事件和状态。本实验处理由 start_span 创建的 span 中捕获的异常,因此不会与自动上下文管理行为混淆。
from opentelemetry.trace import Status, StatusCode
try:
validate_order()
except ValueError as error:
span.record_exception(error)
span.set_status(Status(StatusCode.ERROR, "order validation failed"))
record_exception 留下供调查的事件;ERROR 决定如何分类这项工作。预实验中,只记录事件的 span 状态仍为 UNSET,只有另外设置状态的 span 才是 ERROR。若数据在事件搜索中可见,却未进入错误率或状态过滤结果,这一区分很有用。
并非所有被捕获异常都表示任务失败。若通过正常备用路径恢复,应按业务语义选择状态。本任务明确把订单验证失败归类为最终错误,因此要求 ERROR。记住 SDK 调用方法,与判断应把调用应用于哪种业务结果,是两种学习。
span.end 与 force_flush 的顺序
批处理器收集已经结束的 span 再导出。如果擅自完成并导出仍在进行的 span,实际工作时间或最后事件可能错误。因此,在 span 尚未结束时 flush,并不会自动结束它。
span.end()
flush_result = provider.force_flush(timeout_millis=3000)
预实验中,span 未结束时第一次 flush 返回 True,但 receiver 收到 0 个请求;结束 span 后再次 flush,才到达 1 个。第一个返回值并不证明开放工作已完成。如果不能区分“没有东西可导出,所以完成”和“发送了目标 span 后完成”,就会漏掉短任务丢数据的原因。
force_flush 也不是平时每个 span 都无条件调用的函数。一般服务应利用批处理,并在短时执行或进程可能终止的边界设计等待和关闭策略。本实验只是为了快速重现边界而直接 flush,并未验证批处理性能或高负载生产指南。
五种彼此不同的成功
| 观测点 | 能说明的事实 | 尚不能说明的事 |
|---|---|---|
| span.end 后的结束观测 | span 已结束 | 已传输 |
| exporter 调用 | 尝试了导出 | receiver 已接受 |
| exporter SUCCESS | 该 exporter 将其处理为成功 | 已持久存储、最终可查询 |
| receiver 接受记录 | 实验 receiver 接受了请求 | 已传给其他后端并存储 |
| 在目标存储中按 ID 查询 | 该存储中能看到资料 | 所有 span 均完整保留 |
HTTP 400 实验中,请求正文确实到达 receiver,因此只数 received span 会得到 1。但 receiver 拒绝请求,所以 accepted_spans 为 0,exporter 结果为 FAILURE。若把“正文到达”定义成成功,就会漏掉这个反例。实验中的 delivered 函数要求判断 receiver 是否接受,不能用作持久存储保证。
这种区分也适用于日志、指标和消息队列。先写明你观察的是函数返回、插入本地队列、网络发送、对端接受还是最终处理完成中的哪一点。系统之间的返回值契约不同,不能因为都使用“成功”一词,就随意扩大成功范围。
service.name 应放在哪里
要区分把 service.name 写成普通 span 属性,与设置 Resource 的 service.name。Resource 描述产生该信号的服务。实验会用两个不同服务名调用,所以把示例字符串硬编码进代码,只会在一个案例正确,另一个错误。
在收到的 protobuf 中,还要同时对照 service.name 与 trace ID,避免把服务名相同的其他请求误认为当前请求成功。这是把“页面出现了某些东西”缩小为“我刚发送的请求确实通过该边界”的练习。实际服务还要管理此类标识符的保留期和访问权限。
现场会遇到的情况
调查批处理任务在结束前丢失观测数据时,先问三个问题:是否结束了全部 span;在关闭边界 exporter 是否获得执行机会;此次尝试与接收端各自结果是什么。不要只延长等待时间,应比较哪个边界的数据发生变化。
另一个陷阱是测试结束清理。若在 finally 调用 provider.shutdown,遗漏的数据可能在此时迟到。把它计入学员代码的成功,会让漏写 flush 的实现也通过。本评分器会在学员函数返回后立即复制观测,再清理资源。清理是必需的,但不能代替答案。
下一实验要做什么
专用环境已预装 SDK 和 exporter,无需外部服务或 API key。八个步骤依次修正连接、采样、父级策略、异常、结束、接收判定、服务标识和综合报告。用 run 执行每个文件,可同时看到真实观测与失败条件。评分在代码副本上运行,不修改当前文件;前置步骤也不会覆盖已有的部分答案。
官方依据:Python 计量、OTLP exporter。