手把手教程:容器里的 Java/Python 服务如何用 OpenTelemetry 自动埋点,把 Trace、指标、日志接入 OBSERVE,并补上业务手工埋点与 trace_id 关联,附常见踩坑清单。
目标很具体:一个跑在容器里的 Java 或 Python 服务,十分钟内把 Trace、指标、日志接进 OBSERVE,并能在页面上看到完整调用链。前置条件只有一个:OBSERVE 已部署,且你能拿到 OTLP 上报地址和访问 token。本文假设你的服务已经跑在容器里、有办法改启动命令或环境变量,其余步骤零业务代码改动。
Java 服务不用改业务代码,加一个 -javaagent 参数即可:
java -javaagent:opentelemetry-javaagent.jar -Dotel.exporter.otlp.endpoint=http://observe-host:4317 -Dotel.resource.attributes=service.name=order-api,deployment.environment=prod -Dotel.metrics.exporter=otlp -jar order-api.jar
Python 用自动插桩,先装依赖再启动:
pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a install
opentelemetry-instrument --traces_exporter otlp --metrics_exporter otlp --exporter_otlp_endpoint http://observe-host:4317 python app.py
关键点:上报端点统一走 OTLP(gRPC 4317 或 HTTP 4318),OBSERVE 侧同时接收 Trace 和 Metrics,不需要维护两套 exporter 配置。service.name 一定要显式设置,否则默认取进程名或 jar 名,检索时对不上。生产环境建议再带上 deployment.environment,方便按环境过滤。
自动埋点覆盖框架和库,但业务语义(下单、支付、退款)需要手动 Span:
from opentelemetry import trace
tracer = trace.get_tracer("order-api")
with tracer.start_as_current_span("create_order") as span:
span.set_attribute("order.amount", 128.0)
span.set_attribute("user.id", uid)
# ...业务逻辑
给 Span 打上业务属性,排查时才能按金额、用户 ID 过滤到具体请求。Java 侧同理,用 Span.current().setAttribute(...) 即可。一个建议:把业务属性的 key 统一成团队约定(如 user.id、order.amount),不要张三写 userId、李四写 user_id,否则检索时漏数据。
把 trace_id 注入日志格式,让每行日志都能和调用链对上:
import logging
from opentelemetry import trace
class TraceIdFilter(logging.Filter):
def filter(self, record):
ctx = trace.get_current_span().get_span_context()
record.trace_id = format(ctx.trace_id, '032x')
return True
logging.basicConfig(format='%(asctime)s [trace_id=%(trace_id)s] %(message)s')
如果日志已经用 Filebeat 之类的采集器收进 OBSERVE,只要日志里带 trace_id= 字段,平台就会自动识别并关联到对应链路,不用再配映射。
Trace 全量采样成本高,默认建议先用「头部采样 + 保留错误和慢请求」的策略:
这样既控制住了存储和采集开销,又保证出问题时关键样本都在。OBSERVE 侧还支持尾采样,按错误率和慢请求比例在 Agent 端二次过滤。
除了 service.name,把部署相关的元数据也补齐,排查时能少问一句「这是哪个环境哪台机器」:
service.version:版本号,回滚排查时能对号入座。host.name / container.id:定位到具体实例。deployment.environment:区分 prod / staging。这些属性会随每条 Span 上报,检索时可直接过滤。补齐之后,一个「哪个版本的 order-api 在报错」的问题,一条查询就能回答,不用去翻发布记录。属性命名同样建议团队约定,避免各自为政。
当服务多了以后,直接让每个进程连 OBSERVE 不太好管理,建议在中间加一层 Collector 做批量、脱敏和路由:
receivers:
otlp:
protocols: { grpc: { endpoint: 0.0.0.0:4317 }, http: { endpoint: 0.0.0.0:4318 } }
processors:
batch: {}
attributes:
actions:
- key: user.phone
action: delete
exporters:
otlp:
endpoint: observe-host:4317
headers: { authorization: "Bearer ${TOKEN}" }
service:
pipelines:
traces: { receivers: [otlp], processors: [attributes, batch], exporters: [otlp] }
metrics: { receivers: [otlp], processors: [batch], exporters: [otlp] }
Collector 三个好处:统一加 token、集中做字段脱敏、批量压缩降低网络开销。Kubernetes 环境建议以 DaemonSet 部署。
接完后发几条真实请求,到 OBSERVE 的 Trace 页按服务名检索,确认能看到完整调用链;再切到指标页确认 http.server.duration 等指标有值。三个高频坑:一是 exporter 端点拼错(漏路径或写错端口);二是网络不通,先 telnet observe-host 4317 确认;三是采样率过低看不到低频请求,排查时先临时改成 always_on。另外记得确认 OTLP 上报带上了 token,否则会被网关静默拒绝。还有两个高频问题:一是 Java Agent 版本和业务 JDK 不匹配时会静默不埋点,先看启动日志里有没有 OpenTelemetry 相关输出;二是 HTTP/1.1 上报在大流量下会排队积压,生产环境尽量用 gRPC(4317)。如果 Trace 页能看到数据但指标页是空的,多半是 metrics exporter 没开启,检查 otel.metrics.exporter 配置。
接入完成只是第一步。真正让可观测性持续起作用的,是不断给关键链路补埋点、补齐业务属性,并按团队规范统一命名。建议把接入步骤写成脚本固化进 CI/CD,新服务启动时自动带上 Agent,避免每个服务都手工配一遍。