OpenTelemetry 自定义埋点
虽然自动埋点可以覆盖大部分场景,但有时需要添加业务特定的追踪信息。本文档介绍如何添加自定义埋点。
何时需要自定义埋点?
- 业务逻辑追踪
- 自定义指标
- 业务事件记录
- 性能分析
- 错误追踪
添加自定义 Span
Go 示例
package mainimport ("context""go.opentelemetry.io/otel""go.opentelemetry.io/otel/attribute""go.opentelemetry.io/otel/codes""go.opentelemetry.io/otel/trace")var tracer = otel.Tracer("my-service")func ProcessOrder(ctx context.Context, orderID string) error {ctx, span := tracer.Start(ctx, "process_order",trace.WithAttributes(attribute.String("order.id", orderID),attribute.String("operation", "process"),),trace.WithSpanKind(trace.SpanKindInternal),)defer span.End()// 添加业务事件span.AddEvent("order_validation_started")if err := validateOrder(ctx, orderID); err != nil {span.RecordError(err)span.SetStatus(codes.Error, err.Error())return err}span.AddEvent("order_validated",trace.WithAttributes(attribute.String("validation.result", "success"),),)// 业务逻辑if err := processPayment(ctx, orderID); err != nil {span.RecordError(err)span.SetStatus(codes.Error, "payment failed")return err}span.SetStatus(codes.Ok, "order processed successfully")return nil}
Python 示例
from opentelemetry import tracefrom opentelemetry.trace import Status, StatusCodetracer = trace.get_tracer("my-service")def process_order(order_id: str):with tracer.start_as_current_span("process_order",attributes={"order.id": order_id,"operation": "process",},) as span:# 添加业务事件span.add_event("order_validation_started")try:validate_order(order_id)span.add_event("order_validated", {"validation.result": "success",})process_payment(order_id)span.set_status(Status(StatusCode.OK))except Exception as e:span.record_exception(e)span.set_status(Status(StatusCode.ERROR, str(e)))raise
Java 示例
import io.opentelemetry.api.trace.Span;import io.opentelemetry.api.trace.Tracer;import io.opentelemetry.api.trace.StatusCode;import io.opentelemetry.context.Scope;public class OrderService {private static final Tracer tracer =GlobalOpenTelemetry.getTracer("my-service");public void processOrder(String orderId) {Span span = tracer.spanBuilder("process_order").setAttribute("order.id", orderId).setAttribute("operation", "process").startSpan();try (Scope scope = span.makeCurrent()) {span.addEvent("order_validation_started");validateOrder(orderId);span.addEvent("order_validated",Attributes.of(AttributeKey.stringKey("validation.result"), "success"));processPayment(orderId);span.setStatus(StatusCode.OK);} catch (Exception e) {span.recordException(e);span.setStatus(StatusCode.ERROR, e.getMessage());throw e;} finally {span.end();}}}
JavaScript 示例
const { trace } = require('@opentelemetry/api');const tracer = trace.getTracer('my-service');function processOrder(orderId) {const span = tracer.startSpan('process_order', {attributes: {'order.id': orderId,'operation': 'process',},});try {span.addEvent('order_validation_started');validateOrder(orderId);span.addEvent('order_validated', {'validation.result': 'success',});processPayment(orderId);span.setStatus({ code: 1 }); // OK} catch (error) {span.recordException(error);span.setStatus({ code: 2, message: error.message }); // ERRORthrow error;} finally {span.end();}}
.NET 示例
using System.Diagnostics;public class OrderService{private static readonly ActivitySource ActivitySource = new("my-service");public async Task ProcessOrderAsync(string orderId){using var activity = ActivitySource.StartActivity("process_order");activity?.SetTag("order.id", orderId);activity?.SetTag("operation", "process");try{activity?.AddEvent(new ActivityEvent("order_validation_started"));await ValidateOrderAsync(orderId);activity?.AddEvent(new ActivityEvent("order_validated",tags: new ActivityTagsCollection{{ "validation.result", "success" },}));await ProcessPaymentAsync(orderId);activity?.SetStatus(ActivityStatusCode.Ok);}catch (Exception ex){activity?.RecordException(ex);activity?.SetStatus(ActivityStatusCode.Error, ex.Message);throw;}}}
添加自定义指标
Counter(计数器)
Go 示例:
var (orderCounter metric.Int64Counter)func init() {meter := otel.Meter("my-service")orderCounter, _ = meter.Int64Counter("orders.processed",metric.WithDescription("Total number of orders processed"),)}func ProcessOrder(ctx context.Context, orderID string) {// 业务逻辑orderCounter.Add(ctx, 1,metric.WithAttributes(attribute.String("order.type", "standard"),attribute.String("order.status", "success"),),)}
Python 示例:
from opentelemetry import metricsmeter = metrics.get_meter("my-service")order_counter = meter.create_counter("orders.processed",description="Total number of orders processed",)def process_order(order_id: str):# 业务逻辑order_counter.add(1, {"order.type": "standard","order.status": "success",})
Histogram(直方图)
Go 示例:
var (orderProcessingDuration metric.Float64Histogram)func init() {meter := otel.Meter("my-service")orderProcessingDuration, _ = meter.Float64Histogram("order.processing.duration",metric.WithDescription("Order processing duration"),metric.WithUnit("ms"),)}func ProcessOrder(ctx context.Context, orderID string) {start := time.Now()defer func() {duration := time.Since(start).Milliseconds()orderProcessingDuration.Record(ctx, float64(duration),metric.WithAttributes(attribute.String("order.type", "standard"),),)}()// 业务逻辑}
业务事件追踪
记录关键业务事件
func ProcessOrder(ctx context.Context, orderID string) {ctx, span := tracer.Start(ctx, "process_order")defer span.End()// 订单创建span.AddEvent("order.created",trace.WithAttributes(attribute.String("order.id", orderID),attribute.String("order.amount", "100.00"),),)// 库存检查span.AddEvent("inventory.checked",trace.WithAttributes(attribute.Bool("inventory.available", true),),)// 支付处理span.AddEvent("payment.processed",trace.WithAttributes(attribute.String("payment.method", "credit_card"),attribute.String("payment.status", "success"),),)}
性能分析埋点
记录函数执行时间
func MeasureDuration(ctx context.Context, operation string, fn func() error) error {ctx, span := tracer.Start(ctx, operation)defer span.End()start := time.Now()err := fn()duration := time.Since(start)span.SetAttributes(attribute.Int64("duration_ms", duration.Milliseconds()),)if err != nil {span.RecordError(err)span.SetStatus(codes.Error, err.Error())} else {span.SetStatus(codes.Ok, "")}return err}// 使用err := MeasureDuration(ctx, "database_query", func() error {return db.Query(ctx, "SELECT * FROM users")})
错误追踪
记录详细错误信息
func ProcessOrder(ctx context.Context, orderID string) error {ctx, span := tracer.Start(ctx, "process_order")defer span.End()if err := validateOrder(ctx, orderID); err != nil {// 记录错误span.RecordError(err,trace.WithAttributes(attribute.String("error.type", "validation_error"),attribute.String("error.message", err.Error()),attribute.String("order.id", orderID),),)// 设置状态span.SetStatus(codes.Error, "order validation failed")return err}return nil}
上下文传播
在异步操作中传递上下文
Go 示例:
func ProcessOrderAsync(ctx context.Context, orderID string) {ctx, span := tracer.Start(ctx, "process_order")defer span.End()// 在 goroutine 中传递 contextgo func(ctx context.Context) {ctx, childSpan := tracer.Start(ctx, "async_operation")defer childSpan.End()// 业务逻辑}(ctx)}
JavaScript 示例:
function processOrderAsync(orderId) {const span = tracer.startSpan('process_order');const context = trace.setSpan(context.active(), span);// 在异步操作中传递 contextsetImmediate(() => {const childSpan = tracer.startSpan('async_operation', undefined, context);// 业务逻辑childSpan.end();});span.end();}
最佳实践
1. 使用语义约定
// ✅ 正确:使用语义约定span.SetAttributes(semconv.HTTPMethod("GET"),semconv.HTTPURL("https://api.example.com/users"),semconv.HTTPStatusCode(200),)// ❌ 错误:使用自定义键名span.SetAttributes(attribute.String("method", "GET"),attribute.String("url", "https://api.example.com/users"),)
2. 避免过度埋点
// ✅ 正确:只在关键路径埋点func ProcessOrder(ctx context.Context, orderID string) {ctx, span := tracer.Start(ctx, "process_order")defer span.End()// 业务逻辑}// ❌ 错误:对每个小函数都埋点func validateID(ctx context.Context, id string) {ctx, span := tracer.Start(ctx, "validate_id") // 太细粒度defer span.End()}
3. 记录有意义的属性
// ✅ 正确:记录有用的业务信息span.SetAttributes(attribute.String("order.id", orderID),attribute.String("order.type", "premium"),attribute.Float64("order.amount", 100.00),)// ❌ 错误:记录无意义的信息span.SetAttributes(attribute.String("temp_var", "value"),attribute.Int("counter", 12345),)
4. 确保 Span 正确结束
// ✅ 正确:使用 deferfunc Operation(ctx context.Context) {ctx, span := tracer.Start(ctx, "operation")defer span.End() // 确保总是调用// 业务逻辑}// ❌ 错误:可能忘记调用 Endfunc Operation(ctx context.Context) {ctx, span := tracer.Start(ctx, "operation")if someCondition {span.End() // 可能在某些路径不调用}}