OpenTelemetry 自定义埋点

虽然自动埋点可以覆盖大部分场景,但有时需要添加业务特定的追踪信息。本文档介绍如何添加自定义埋点。

何时需要自定义埋点?

  • 业务逻辑追踪
  • 自定义指标
  • 业务事件记录
  • 性能分析
  • 错误追踪

添加自定义 Span

Go 示例

package main
import (
"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 trace
from opentelemetry.trace import Status, StatusCode
tracer = 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 }); // ERROR
throw 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 metrics
meter = 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 中传递 context
go 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);
// 在异步操作中传递 context
setImmediate(() => {
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 正确结束

// ✅ 正确:使用 defer
func Operation(ctx context.Context) {
ctx, span := tracer.Start(ctx, "operation")
defer span.End() // 确保总是调用
// 业务逻辑
}
// ❌ 错误:可能忘记调用 End
func Operation(ctx context.Context) {
ctx, span := tracer.Start(ctx, "operation")
if someCondition {
span.End() // 可能在某些路径不调用
}
}

下一步

参考资源

在Gitlab上编辑本文档