executing-plans
executing-plans
Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available
instrument-distributed-tracing使用 OpenTelemetry 为应用程序插桩分布式追踪,包括自动和手动插桩、 上下文传播、采样策略及与 Jaeger 或 Tempo 的集成。适用于调试分布式系统中的延迟问题、 理解跨微服务的请求流、将追踪与日志和指标关联进行根因分析、 测量端到端延迟,或将遗留追踪系统迁移到 OpenTelemetry。
Independent GitHub snapshot · Automated classification · Original documentation governs
实现 OpenTelemetry 分布式追踪,跨微服务追踪请求并识别性能瓶颈。
完整配置文件和模板请参阅 Extended Examples。
部署 Jaeger 或 Grafana Tempo 以接收和存储追踪数据。
选项 A:Jaeger all-in-one(开发/测试):
# docker-compose.yml
version: '3.8'
services:
jaeger:
image: jaegertracing/all-in-one:1.51
ports:
- "5775:5775/udp" # Zipkin compact thrift
- "6831:6831/udp" # Jaeger compact thrift
- "6832:6832/udp" # Jaeger binary thrift
- "5778:5778" # Serve configs
- "16686:16686" # Jaeger UI
- "14268:14268" # Jaeger HTTP thrift
- "14250:14250" # Jaeger GRPC
- "9411:9411" # Zipkin compatible endpoint
environment:
- COLLECTOR_ZIPKIN_HOST_PORT=:9411
- COLLECTOR_OTLP_ENABLED=true
restart: unless-stopped
选项 B:Grafana Tempo(生产环境,可扩展):
# docker-compose.yml
version: '3.8'
services:
tempo:
image: grafana/tempo:2.3.0
command: ["-config.file=/etc/tempo.yaml"]
volumes:
- ./tempo.yaml:/etc/tempo.yaml
- tempo-data:/tmp/tempo
ports:
- "3200:3200" # Tempo HTTP
- "4317:4317" # OTLP gRPC
- "4318:4318" # OTLP HTTP
- "9411:9411" # Zipkin
restart: unless-stopped
volumes:
tempo-data:
Tempo 配置(tempo.yaml):
server:
http_listen_port: 3200
distributor:
receivers:
jaeger:
# ... (see EXAMPLES.md for complete configuration)
生产环境使用 S3 存储:
storage:
trace:
backend: s3
s3:
bucket: tempo-traces
endpoint: s3.amazonaws.com
region: us-east-1
wal:
path: /tmp/tempo/wal
pool:
max_workers: 100
queue_depth: 10000
预期结果: 追踪后端可访问,准备好通过 OTLP 接收追踪,Jaeger UI 或 Grafana 初始显示"no traces"。
失败处理:
netstat -tulpn | grep -E '(4317|16686|3200)'docker logs jaeger 或 docker logs tempocurl http://localhost:4318/v1/traces -vtempo -config.file=/etc/tempo.yaml -verify-config 验证配置语法使用 OpenTelemetry 自动插桩处理常见框架,最小化代码变更。
Python with Flask:
pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a install
# app.py
from flask import Flask
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
# ... (see EXAMPLES.md for complete configuration)
Go with Gin framework:
go get go.opentelemetry.io/otel
go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc
go get go.opentelemetry.io/otel/sdk/trace
go get go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin
package main
import (
"context"
"github.com/gin-gonic/gin"
"go.opentelemetry.io/otel"
# ... (see EXAMPLES.md for complete configuration)
Node.js with Express:
npm install @opentelemetry/api \
@opentelemetry/sdk-node \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/exporter-trace-otlp-grpc
// tracing.js
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { Resource } = require('@opentelemetry/resources');
const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');
# ... (see EXAMPLES.md for complete configuration)
预期结果: 来自已插桩服务的追踪出现在 Jaeger UI 或 Grafana 中,HTTP 请求自动创建 span。
失败处理:
OTEL_EXPORTER_OTLP_ENDPOINT=http://tempo:4317OTEL_LOG_LEVEL=debug(Python)、OTEL_LOG_LEVEL=DEBUG(Node.js)为业务逻辑、数据库查询和外部调用创建自定义 span。
Python 手动 span:
from opentelemetry import trace
tracer = trace.get_tracer(__name__)
def process_order(order_id):
# Create a span for the entire operation
# ... (see EXAMPLES.md for complete configuration)
Go 手动 span:
import (
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
# ... (see EXAMPLES.md for complete configuration)
Span 属性最佳实践:
http.method、http.status_code、db.system、db.statementuser.id、order.id、product.categoryinstance.id、region、availability_zonespan.RecordError(err) 和 span.SetStatus(codes.Error, message)span.AddEvent("cache_miss")预期结果: 自定义 span 出现在追踪视图中,父子关系正确,属性在 span 详情中可见,错误被高亮显示。
失败处理:
defer span.End(),Python 中使用 with 块)确保追踪上下文跨服务边界和异步操作流动。
HTTP 头传播(W3C Trace Context):
# Client side (Python with requests)
import requests
from opentelemetry import trace
from opentelemetry.propagate import inject
tracer = trace.get_tracer(__name__)
# ... (see EXAMPLES.md for complete configuration)
// Server side (Go with Gin)
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
)
# ... (see EXAMPLES.md for complete configuration)
消息队列传播(Kafka):
# Producer
from opentelemetry.propagate import inject
from kafka import KafkaProducer
producer = KafkaProducer(bootstrap_servers=['kafka:9092'])
# ... (see EXAMPLES.md for complete configuration)
# Consumer
from opentelemetry.propagate import extract
def process_message(msg):
# Extract trace context from Kafka headers
headers = {k: v.decode('utf-8') for k, v in msg.headers}
ctx = extract(headers)
# Continue the trace
with tracer.start_as_current_span("process_order_event", context=ctx):
order_id = json.loads(msg.value)['order_id']
handle_order(order_id)
异步操作(Python asyncio):
import asyncio
from opentelemetry import trace, context
async def async_operation():
# Capture current context
token = context.attach(context.get_current())
try:
with tracer.start_as_current_span("async_database_query"):
await asyncio.sleep(0.1) # Simulated async work
return "result"
finally:
context.detach(token)
预期结果: 追踪跨越多个服务,追踪 ID 跨服务边界保持一致,父子关系得以保留。
失败处理:
otel.propagation.set_global_textmap(TraceContextTextMapPropagator())traceparent 头信息值实现采样以减少追踪量和成本,同时保持可见性。
采样策略:
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.sampling import (
ParentBased,
TraceIdRatioBased,
StaticSampler,
Decision
# ... (see EXAMPLES.md for complete configuration)
使用 Tempo 的尾部采样:
在 tempo.yaml 中配置:
overrides:
defaults:
metrics_generator:
processors: [service-graphs, span-metrics]
storage:
path: /tmp/tempo/generator/wal
remote_write:
- url: http://prometheus:9090/api/v1/write
send_exemplars: true
# Tail sampling (requires tempo-query)
ingestion_rate_limit_bytes: 5000000
ingestion_burst_size_bytes: 10000000
使用 Grafana Tempo 的 TraceQL 进行动态采样:
# Sample traces with errors
{ status = error }
# Sample slow traces (>1s)
{ duration > 1s }
# Sample specific services
{ resource.service.name = "checkout-service" }
预期结果: 追踪量减少到目标百分比,错误追踪始终被采样,采样决策在追踪元数据中可见。
失败处理:
ingestion_burst_size_bytes)otel_traces_dropped_total 指标将追踪链接到指标和日志,实现统一可观测性。
向日志添加追踪 ID(Python):
import logging
from opentelemetry import trace
# Custom log formatter with trace context
class TraceFormatter(logging.Formatter):
def format(self, record):
# ... (see EXAMPLES.md for complete configuration)
从追踪生成指标(Tempo):
# tempo.yaml
metrics_generator:
registry:
external_labels:
cluster: production
storage:
# ... (see EXAMPLES.md for complete configuration)
这将生成 Prometheus 指标:
traces_service_graph_request_total - 服务间请求计数traces_span_metrics_duration_seconds - span 持续时间直方图traces_spanmetrics_calls_total - span 调用计数从指标查询追踪(Grafana):
在 Grafana 中向 Prometheus 数据源添加 exemplar 支持:
datasources:
- name: Prometheus
type: prometheus
url: http://prometheus:9090
jsonData:
exemplarTraceIdDestinations:
- name: trace_id
datasourceName: Tempo
在 Grafana 仪表板中启用 exemplar:
{
"fieldConfig": {
"defaults": {
"custom": {
"showExemplars": true
}
}
}
}
预期结果: 点击指标 exemplar 打开追踪,日志显示追踪 ID,追踪链接到日志,实现跨信号的统一调试。
失败处理:
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) and on() exemplarcontext 传递给下游调用会断开追踪。始终显式传递上下文。defer span.End()(Go)或 with 块(Python)导致 span 保持打开并产生内存泄漏。span.RecordError() 会丢失宝贵的调试信息。始终在 span 中记录错误。ParentBased 采样器尊重上游采样。correlate-observability-signals - 通过追踪 ID 链接指标、日志和追踪进行统一调试setup-prometheus-monitoring - 使用 Tempo 指标生成器从追踪生成指标configure-log-aggregation - 向日志添加追踪 ID 以与分布式追踪关联build-grafana-dashboards - 在仪表板中可视化追踪衍生指标和 exemplar 链接