第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 集成后的整体数据流:
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. 三种语言对比
| 维度 | Java | Go | Python |
|---|---|---|---|
| 初始化代码量 | 较多(需要配置 Provider/Exporter/Resource) | 中等 | 少 |
| Span 生命周期 | 手动 start / end | defer end + context 传递 | 上下文管理器自动结束 |
| 自动埋点支持 | Java Agent(零代码) | 通过中间件拦截(如 gin 插件) | opentelemetry-instrumentation 自动 |
| 常见坑 | 忘了加 BatchSpanProcessor | context 传递漏掉链接 | 环境变量 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。记住这个套路,换语言就是换语法细节。