TT Lab
开始
学习 学习路径 课程

OTCA — OpenTelemetry 认证助理

库只认 API,SDK 由应用来装

在 TT Lab 中继续学习

一句话总结

OpenTelemetry 客户端按信号(signal)分成 API、SDK、语义约定(semantic conventions)和 Contrib 四类包。埋点代码只依赖 API,由应用所有者安装 SDK,组装出 provider、processor 和 exporter。本文依据规范概览和客户端设计原则,说明为什么需要这种分离(composability),SDK 管道是如何串起来的,以及无需修改代码的代理(agent)如何装入同样的 API 与 SDK。

为什么需要它

埋点正如文档所说,是“横切关注点(cross-cutting concern)”。观测代码会混进 Web 框架、数据库客户端、消息队列库里。然而,使用这些库的应用要把遥测发往哪个后端,或者根本不发送,库的作者无从得知。如果库拖入某个具体实现,连不想要它的应用也会变得臃肿。

设计原则文档因此提出三点要求:API 必须与实现明确分离;第三方库只能依赖 API;最终的应用开发者要能决定如何配置 SDK,甚至完全不使用。为此文档写明,API 与 SDK 必须作为独立的 artifact 提供(MUST)。

工作原理

API 的最小实现

API 包自身是完整的。没有 SDK,应用也必须能构建并运行,所以 API 里内置了最小实现(no-op)。文档强调这个最小实现的返回值必须有效:createSpan() 不能失败,必须返回非 null 的 Span;调用方不必关心此刻运行的是不是最小实现;并且性能开销应几乎为零。这就是满足“已埋点的库也能用在不使用 OpenTelemetry 的应用中”这一要求的方法,也让框架不必再分别发布“已埋点版”和“未埋点版”。

规则中针对埋点作者的一条很明确:“埋点作者不得直接引用任何种类的 SDK 包(MUST NOT),只能引用 API。”

SDK 内部:从 provider 到 exporter

安装 SDK 后,它会取代最小实现。SDK 又分为两部分:与协议无关的通用逻辑(批处理、附加进程信息等),以及与协议绑定的 exporter。exporter 只具备最基本的功能,便于厂商接入自己的协议。规范要求 SDK 提供的默认 exporter 有 OTLP(日志、指标、跟踪)、标准输出、内存(用于测试),指标还要加上 Prometheus,跟踪还要加上 Zipkin。厂商专用的 exporter 不放进客户端。

组装的骨架在每种信号中形状相同。

信号 Provider 创建的对象 记录单位 处理器 → exporter
跟踪 TracerProvider Tracer Span SpanProcessor → SpanExporter
指标 MeterProvider Meter → Instrument Measurement 聚合状态 → MetricReader/Exporter
日志 LoggerProvider Logger LogRecord LogRecordProcessor → LogRecordExporter

根据跟踪 SDK 规范,创建 TracerProvider 时要配置若干 SpanProcessor、IdGenerator、SpanLimits 和 Sampler。默认采样器是 ParentBased(root=AlwaysOn)。TracerProvider 的 Shutdown 会调用所有已注册处理器的 Shutdown,ForceFlush 也同样会传播。

处理器链

SpanProcessor 是跨度(span)生命周期的钩子:跨度开始时调用 OnStart,结束前一刻调用 OnEnding,结束后调用 OnEnd,此外还有 Shutdown 和 ForceFlush。它们按注册顺序调用,只有所有处理器的 OnEnding 都结束后,OnEnd 才会开始。内置处理器有两种。

Tracer ──► Span(끝) ──► [SpanProcessor 1] ──► [SpanProcessor 2: Batching] ──► SpanExporter(OTLP)
                              │ OnStart/OnEnding/OnEnd 훅

日志 SDK的结构也相同。在 LoggerProvider 中注册 LogRecordProcessor,由 Simple 或 Batching 处理器交给 LogRecordExporter(例如 OTLP)。规范说明,内置处理器负责“批处理和转换”。

代理(agent):不改代码,装入同样的东西

零代码埋点概念把 agent 的作用定义为“把 OpenTelemetry API 与 SDK 的能力添加到应用中”。具体方法因语言而异:字节码操作、猴子补丁(monkey patching)、eBPF。被埋点的是你所使用的库(请求与响应、数据库调用、消息队列),而不是你自己的代码;想给自己的代码埋点,需要基于代码的埋点。配置通过环境变量和各语言自己的方式完成,要启动只需要一个服务名称。

Java agent就是一个 opentelemetry-javaagent.jar。给 Java 8 及以上的 JVM 加上 -javaagent:path/to/opentelemetry-javaagent.jar,它会动态注入字节码,从许多库中获取遥测。配置可以通过 -Dotel.service.name=... 这样的系统属性、OTEL_SERVICE_NAME、OTEL_TRACES_EXPORTER 这样的环境变量、JAVA_TOOL_OPTIONS,或者用 otel.javaagent.configuration-file 指定的属性文件中的任意一种来完成。

Python 采用的是猴子补丁。用 pip install opentelemetry-distro opentelemetry-exporter-otlp 安装 API、SDK 和两个工具,opentelemetry-bootstrap -a install 会扫描 site-packages,为已安装的包选择并安装对应的埋点库(例如 flask 对应 opentelemetry-instrumentation-flask)。运行命令是 opentelemetry-instrument python myapp.py,通过 --traces_exporter console,otlp 这样的参数或 OTEL_* 环境变量来配置。文档明确指出,自动埋点要生效,必须安装 distro 包。

在现场相遇的样子

一个团队的内部 HTTP 客户端库直接依赖了 SDK。使用这个库的批处理任务明明没有地方可以发送遥测,OTLP exporter 却照样启动,退出时还要等待 Shutdown,导致结束得很晚。把库改为依赖 API 后,批处理任务中它变成 no-op,而在 Web 服务中则直接使用应用组装的 SDK。

另一个团队遇到 span 间歇性消失的问题。流量集中时,Batching processor 的队列(默认 2048)溢出,丢弃了 span。他们需要在调大队列,与调整 maxExportBatchSize、scheduledDelayMillis 以加快导出速度之间权衡。

接下来的文章要讲什么

后续文章将讲解七种指标 Instrument 与 temporality、日志记录的字段与桥接 API,以及 schema URL。之后的测验会确认 API 与 SDK 的分离原因、处理器链、Batching processor 的默认值,以及 agent 的工作方式。