什么是 auto-instrumentation
当你 require("http") 或 import pg from "pg" 时,OTel 在模块加载那一刻替换掉它导出的函数——把原函数包一层,包装里加上 tracer.startSpan、attribute、setStatus、span.end,然后调原函数。对业务代码而言,pg 还是那个 pg——但它的每次 query() 都悄悄产出一个 span。
· Node.js:
require-in-the-middle hook + monkey patch· Python:
wrapt + 模块 import hook· Java:
ByteBuddy JVM Agent,在类加载时改字节码· Go:用
go get 包装,显式调用(Go 不能真正"无侵入")· .NET:
System.Diagnostics.DiagnosticSource + profiling API
Node.js:三行启用
pnpm add @opentelemetry/sdk-node \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/exporter-trace-otlp-http
// tracing.ts import { NodeSDK } from "@opentelemetry/sdk-node"; import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node"; import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; new NodeSDK({ traceExporter: new OTLPTraceExporter(), instrumentations: [getNodeAutoInstrumentations()], }).start();
// package.json { "scripts": { "start": "node --require ./tracing.ts app.ts" } }
--require 保证 tracing.ts 在任何业务模块之前加载——monkey patch 必须在目标模块被 require 之前完成,否则就晚了。
OTEL_* 环境变量
很多东西可以从代码里挪到环境变量:
OTEL_SERVICE_NAME=order-service OTEL_RESOURCE_ATTRIBUTES=service.version=1.2.0,deployment.environment=prod OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318 OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf OTEL_TRACES_SAMPLER=parentbased_traceidratio OTEL_TRACES_SAMPLER_ARG=0.1 OTEL_LOG_LEVEL=error OTEL_PROPAGATORS=tracecontext,baggage
K8s 里直接 env 注入,开发/测试/生产用不同值——代码完全复用。
Node auto-instrumentations 覆盖列表
http / https 模块,客户端服务端全覆盖覆盖 50+ 常用库。opentelemetry-js-contrib 仓库是完整清单。
Python:一条命令
pip install opentelemetry-distro opentelemetry-exporter-otlp opentelemetry-bootstrap -a install # 按当前项目装对应 instrumentation # 然后启动时加前缀 opentelemetry-instrument \ --traces_exporter otlp \ --metrics_exporter otlp \ --service_name order-service \ python app.py
它会:
- 侦测你的项目依赖(flask / django / fastapi / requests / sqlalchemy / psycopg / redis / celery ...)
- 自动 hook 对应模块
- 设置 Exporter、Sampler、Resource
完全无代码改动——这是 Python 最舒服的部分。
Java Agent:字节码级无侵入
# 下载 agent jar curl -L -O https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar # 启动时 -javaagent java -javaagent:./opentelemetry-javaagent.jar \ -Dotel.service.name=order-service \ -Dotel.exporter.otlp.endpoint=http://collector:4317 \ -jar myapp.jar
Java Agent 最强——用 ByteBuddy 在类加载时改字节码,能 hook 到绝大多数库(Spring、Hibernate、gRPC、Kafka、JDBC、Log4j 一整条工具链)。完全无代码改动、无重新编译。
Go:显式包装
// Go 没有 runtime monkey patch—— OTel 提供包装库 import ( "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/contrib/instrumentation/github.com/lib/pq/otelpq" ) // HTTP handler 包一下 http.Handle("/api", otelhttp.NewHandler(myHandler, "api")) // DB 连接用包装过的驱动 db := otelpq.OpenDB(connector)
需要改几行代码。但 Go 社区有个实验项目 opentelemetry-go-compile-instrumentation——编译期插桩,看齐 Java Agent 体验,2025 年达到 alpha。
.NET / Ruby / PHP
OpenTelemetry.Instrumentation.AspNetCore 等 NuGet 包,.NET 9 原生的 ActivitySource 几乎零成本桥到 OTel。opentelemetry-instrumentation-all gem,require 后调 OpenTelemetry::SDK.configure { |c| c.use_all }。opentelemetry + open-telemetry/auto-laravel 等包。相对新,覆盖度还在增长。OTel Operator(K8s 无感注入)
连 OTel SDK 都懒得装?OpenTelemetry Operator 让你在 K8s 里用注解自动注入:
apiVersion: v1 kind: Pod metadata: name: my-app annotations: instrumentation.opentelemetry.io/inject-nodejs: "true" instrumentation.opentelemetry.io/inject-python: "true" instrumentation.opentelemetry.io/inject-java: "true" spec: containers: - name: app image: myapp:1.2.0
Operator 会 init-container 把对应语言的 agent/SDK 注入到 Pod,设置环境变量——开发者完全无感。K8s 运维一次配置,所有 Pod 自动可观测。
自动 + 手动:混合最强
自动 instrumentation 负责 90% 的样板(HTTP/DB/缓存/消息)——节省人力。
手动 span 负责业务关键节点:
checkout、risk_check、send_email —— 让业务语义可见。两者在同一个 context 下,自动挂父子,Jaeger 上看就是"HTTP in → 业务 span → DB 查询"自然嵌套。
自动埋点的缺陷
- 颗粒度:HTTP 只能看到"一次调用",看不到中间函数——手动补
- 业务语义:它不知道
order.total、user.tier这种业务 attribute——手动补 - 性能开销:monkey patch 有常数级开销(Node 大约 3-5% CPU)——生产要测
- 库版本:instrumentation 落后于库的最新版,有时需要手动锁版本或写 custom hook
- 内部库:公司内部 RPC/DB 框架,官方不支持——需要自己写 instrumentation(参考 contrib 仓库的例子)
选择性启用 / 禁用
// 不想要 DNS 这种太细的 span?关掉 getNodeAutoInstrumentations({ "@opentelemetry/instrumentation-dns": { enabled: false }, "@opentelemetry/instrumentation-fs": { enabled: false }, "@opentelemetry/instrumentation-http": { ignoreIncomingRequestHook: (req) => req.url === "/health", }, });
健康检查、静态资源、DNS、fs 读取这些默认该关掉——span 量大但没价值。
性能观察
· 自动埋点 CPU 开销:Node ~3-5%,Python ~5-10%,Java ~2-5%(JIT 后更低),Go(编译插桩)~1-2%
· 内存:每个 span ~2-5 KB,有 batch 缓冲时短暂占用
· 网络:OTLP/gRPC 批量发,10k RPS 大约 1-2 MB/s
· 生产上线前务必压测:采样率调到目标值,对比开/关 OTel 的 p99
写自己的 instrumentation
公司自研 RPC、队列、DB——官方不支持怎么办?参考下面模板:
import { InstrumentationBase } from "@opentelemetry/instrumentation"; class MyRpcInstrumentation extends InstrumentationBase { init() { return [ new InstrumentationNodeModuleDefinition( "my-rpc", [">=1.0.0"], (exports) => { // 包装 exports.call const orig = exports.call; exports.call = (...args: any[]) => { return this.tracer.startActiveSpan( `rpc ${args[0]}`, (span) => { try { return orig(...args); } finally { span.end(); } } ); }; return exports; } ), ]; } }
比想象中简单——难的是 edge case 处理(错误传播、异步、回调、Promise 返回)。
本章小结
- Node:
--require ./tracing.js+getNodeAutoInstrumentations - Python:
opentelemetry-instrument python app.py - Java:
-javaagent:opentelemetry-javaagent.jar(最强) - Go:显式包装
otelhttp.NewHandler、包装驱动 - K8s:OTel Operator 按注解自动注入
- 自动 + 手动混合最强:样板零侵入、业务节点手写
- 关掉噪音(DNS / fs / health check),监控 CPU 开销