Chapter 06

零侵入的魔法

业务代码一行不改,HTTP/DB/Redis/Kafka 自动产出 span——这是 OTel 最能打动决策者的能力。原理是 monkey patching(JS/Python)或 bytecode rewriting(Java)。用得好,一下午接入一整个微服务集群。

什么是 auto-instrumentation

当你 require("http")import pg from "pg" 时,OTel 在模块加载那一刻替换掉它导出的函数——把原函数包一层,包装里加上 tracer.startSpan、attribute、setStatusspan.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
http / https 模块,客户端服务端全覆盖
DB 驱动
pg / mysql2 / mariadb / mongodb / ioredis / redis / cassandra-driver / mssql
消息队列
kafkajs / amqplib / sqs / sns
ORM
knex / sequelize / typeorm / prisma / mongoose
Web 框架
Express / Koa / Fastify / Hapi / Nest / Hono(第三方)
RPC
grpc-js / tRPC(第三方)
云 SDK
aws-sdk v2/v3 / @azure/core
日志
pino / winston / bunyan — 自动注入 trace_id

覆盖 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

它会:

完全无代码改动——这是 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

.NET
OpenTelemetry.Instrumentation.AspNetCore 等 NuGet 包,.NET 9 原生的 ActivitySource 几乎零成本桥到 OTel。
Ruby
opentelemetry-instrumentation-all gem,require 后调 OpenTelemetry::SDK.configure { |c| c.use_all }
PHP
用 PECL 扩展 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 负责业务关键节点:checkoutrisk_checksend_email —— 让业务语义可见。
两者在同一个 context 下,自动挂父子,Jaeger 上看就是"HTTP in → 业务 span → DB 查询"自然嵌套。

自动埋点的缺陷

选择性启用 / 禁用

// 不想要 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 返回)。

本章小结