第4章 OpenTelemetry SDK 集成:在 Java/Go/Python 应用中埋点实战

说到可观测性,埋点就是“把话说清楚”。你想想看,系统出了问题,没有埋点就像闭着眼修车。我个人最怕那种“我猜是这里慢”的排查方式——十次有九次猜错。

这一章咱们直接动手。我会用 Java、Go、Python 三种语言,演示怎么把 OpenTelemetry SDK 集成进去。放心,代码不长,但每个坑我都踩过。

动手前提醒:确保你已经跑通了上一章的 Collector 本地部署。如果 Collector 没装好,后面数据全白费。

1. 通用准备:服务名与 Exporter

不管什么语言,三个事必须先定好:

  • Service Name —— 就是你这服务的标识,比如 order-service,别手抖写错
  • Exporter 地址 —— 默认 OTLP 走 gRPC localhost:4317,如果用 HTTP 就是 4318
  • 资源属性 —— 环境、版本号、实例 ID,方便后面过滤

我习惯把这些放到环境变量里,而不是硬编码。为什么?因为换环境不用改代码。下面这张图能帮你理清 SDK 集成后的整体数据流:

OpenTelemetry SDK 集成数据流 Java 应用 Go 应用 Python 应用 OTel Java SDK OTel Go SDK OTel Python SDK OTLP Exporter(gRPC / HTTP) 端口 4317 / 4318 OpenTelemetry Collector 注意 Exporter 配置 必须与 Collector 端口一致

2. Java 应用:Spring Boot 集成

Java 生态里,我推荐用 OpenTelemetry Java Agent 做零代码注入。但如果你想自己控制埋点位置,可以用 SDK 手动埋。这里我讲手动方式,因为更灵活。

核心思路:创建 OpenTelemetry 实例 → 初始化 Tracer → 用 Tracer 创建 Span → 设置属性/事件 → 结束 Span。

先加依赖(Maven):

<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-sdk</artifactId>
    <version>1.36.0</version>
</dependency>
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-exporter-otlp</artifactId>
    <version>1.36.0</version>
</dependency>

然后初始化 SDK:

// 创建 SdkTracerProvider,配置 OTLP exporter
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
    .addSpanProcessor(BatchSpanProcessor.builder(
        OtlpGrpcSpanExporter.builder()
            .setEndpoint("http://localhost:4317")
            .build())
        .build())
    .setResource(Resource.getDefault()
        .toBuilder()
        .put("service.name", "order-service")
        .put("deployment.environment", "production")
        .build())
    .build();

OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
    .setTracerProvider(tracerProvider)
    .buildAndRegisterGlobal();

实际埋点就两行:

Tracer tracer = openTelemetry.getTracer("order-service");
Span span = tracer.spanBuilder("getOrder").startSpan();
// 执行业务逻辑...
span.setAttribute("order.id", orderId);
span.addEvent("cache.hit");
span.end();
曾经踩过的坑:我刚开始忘了配置 BatchSpanProcessor,直接用了 SimpleSpanProcessor,结果每次同步导出拖慢了接口 20 毫秒。一定要用批量处理器。

3. Go 应用:gRPC 服务埋点

Go 的集成比 Java 更轻量。我用的是 go.opentelemetry.io/otel 官方包。安装:

go get go.opentelemetry.io/otel \
       go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc \
       go.opentelemetry.io/otel/sdk

初始化代码类似:

ctx := context.Background()
exporter, _ := otlptracegrpc.New(ctx,
    otlptracegrpc.WithInsecure(),
    otlptracegrpc.WithEndpoint("localhost:4317"))
tp := sdktrace.NewTracerProvider(
    sdktrace.WithBatcher(exporter),
    sdktrace.WithResource(resource.NewWithAttributes(
        semconv.SchemaURL,
        semconv.ServiceNameKey.String("order-service"),
    )))
otel.SetTracerProvider(tp)

埋点示例(在 handler 里):

tracer := otel.Tracer("order-handler")
ctx, span := tracer.Start(r.Context(), "getOrder")
defer span.End()

// 设置属性
span.SetAttributes(
    attribute.String("order.id", orderID),
    attribute.Int("item.count", len(items)),
)
我的习惯:我会把 tracer.Start 的 context 一直传下去,这样内部调用的子 span 会自动继承父 span。你想想看,如果每个函数自己新建 span,trace 就断开了。

4. Python 应用:Flask 快速集成

Python 最简单。用 opentelemetry-distro 一行命令就能自动检测 Flask、Django。但手动集成也不复杂:

pip install opentelemetry-sdk opentelemetry-exporter-otlp-proto-grpc

初始化:

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter

provider = TracerProvider()
processor = BatchSpanProcessor(OTLPSpanExporter(endpoint="localhost:4317", insecure=True))
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)

tracer = trace.get_tracer("order-service")

在路由里埋点:

@app.route("/order/<order_id>")
def get_order(order_id):
    with tracer.start_as_current_span("getOrder") as span:
        span.set_attribute("order.id", order_id)
        # 业务逻辑...
        return {"id": order_id, "status": "ok"}

Python 的 with 上下文管理器自动处理 span 的结束,这个设计真的很省心。

5. 三种语言对比

维度JavaGoPython
初始化代码量较多(需要配置 Provider/Exporter/Resource)中等少
Span 生命周期手动 start / enddefer end + context 传递上下文管理器自动结束
自动埋点支持Java Agent(零代码)通过中间件拦截(如 gin 插件)opentelemetry-instrumentation 自动
常见坑忘了加 BatchSpanProcessorcontext 传递漏掉链接环境变量 OTEL_EXPORTER_OTLP_ENDPOINT 拼写错误

6. 避坑指南

这些年我踩过的坑,列出来帮你省时间:

  • Exporter 地址写错 —— 我见过有人把 localhost:4317 写成 localhost:4318 然后 data 全丢。先 curl 测试 Collector 是否存活。
  • Resource 不统一 —— 同一服务不同实例的 service.name 不一致,后面在 Grafana 里全串了。用环境变量统一注入。
  • Span 未正确结束 —— Java 里忘记调 span.end() 导致内存泄漏,幸好压测时发现了。用 try-finally 包裹。
  • 忘记设置全局 TracerProvider —— Go 里必须调 otel.SetTracerProvider(tp),否则第三方库的自动埋点拿不到你的 Exporter。

总结一下:三种语言的 SDK 集成大同小异,核心步骤就是:建 Provider → 配 Exporter → 设 Resource → 拿 Tracer → 埋 Span。记住这个套路,换语言就是换语法细节。