Announcement

👇Official Account👇

图片

Welcome to join the group & private message

Article first/tail QR code

Skip to content

别再盲接 OTel:Go 可观察性接入的 8 个大坑

OpenTelemetry 很强大,但使用不当会带来性能问题。本文总结了 8 个常见坑点,帮你避开这些陷阱。

坑 1:没有正确传播 Context

❌ 错误做法

go
func handler(c *gin.Context) {
    // 丢失了上游的 trace context
    processOrder(c.Request.Context())
}

func processOrder(ctx context.Context) {
    // 创建新的 span,但没有关联父 span
    _, span := tracer.Start(context.Background(), "process_order")
    defer span.End()
}

✅ 正确做法

go
func handler(c *gin.Context) {
    // 使用请求中的 context
    processOrder(c.Request.Context())
}

func processOrder(ctx context.Context) {
    // 正确传递 context,自动关联父 span
    ctx, span := tracer.Start(ctx, "process_order")
    defer span.End()
    
    // 后续调用继续传递 ctx
    validateOrder(ctx, order)
}

坑 2:创建太多 Span

❌ 错误做法

go
func processOrder(ctx context.Context, order Order) error {
    for _, item := range order.Items {
        ctx, span := tracer.Start(ctx, "process_item")
        // 处理每个 item 都创建 span,数量太多
        span.End()
    }
}

✅ 正确做法

go
func processOrder(ctx context.Context, order Order) error {
    ctx, span := tracer.Start(ctx, "process_order")
    defer span.End()
    
    span.SetAttributes(attribute.Int("item_count", len(order.Items)))
    
    for _, item := range order.Items {
        // 批量处理,不创建过多 span
        processItem(item)
    }
}

坑 3:没有设置采样策略

❌ 错误做法

go
// 默认采样,高流量下数据量爆炸
provider := sdktrace.NewTracerProvider()

✅ 正确做法

go
// 使用概率采样
provider := sdktrace.NewTracerProvider(
    sdktrace.WithSampler(sdktrace.TraceIDRatioBased(0.1)), // 10% 采样
)

// 或者使用 ParentBased 采样
provider := sdktrace.NewTracerProvider(
    sdktrace.WithSampler(sdktrace.ParentBased(
        sdktrace.TraceIDRatioBased(0.1),
    )),
)

坑 4:Span 没有正确结束

❌ 错误做法

go
func processOrder(ctx context.Context) {
    _, span := tracer.Start(ctx, "process_order")
    
    if err := validateOrder(); err != nil {
        // 提前返回,span 没有结束
        return err
    }
    
    span.End() // 可能执行不到
}

✅ 正确做法

go
func processOrder(ctx context.Context) error {
    ctx, span := tracer.Start(ctx, "process_order")
    defer span.End() // 确保 span 总是结束
    
    if err := validateOrder(); err != nil {
        span.RecordError(err)
        span.SetStatus(codes.Error, err.Error())
        return err
    }
    
    return nil
}

坑 5:在 Span 中记录敏感信息

❌ 错误做法

go
span.SetAttributes(
    attribute.String("user.password", password),      // ❌ 敏感信息
    attribute.String("user.credit_card", cardNumber), // ❌ 敏感信息
    attribute.String("user.token", token),            // ❌ 敏感信息
)

✅ 正确做法

go
span.SetAttributes(
    attribute.String("user.id", userID),
    attribute.String("user.email", maskEmail(email)), // 脱敏
    // 不记录敏感信息
)

func maskEmail(email string) string {
    parts := strings.Split(email, "@")
    if len(parts) != 2 {
        return "***"
    }
    return parts[0][:1] + "***@" + parts[1]
}

坑 6:没有处理 BatchSpanProcessor 错误

❌ 错误做法

go
exp, _ := jaeger.New(jaeger.WithCollectorEndpoint(...))

// 忽略错误,可能导致数据丢失

✅ 正确做法

go
exp, err := jaeger.New(jaeger.WithCollectorEndpoint(...))
if err != nil {
    log.Fatal("failed to create exporter:", err)
}

// 使用 BatchSpanProcessor 并设置超时
tp := sdktrace.NewTracerProvider(
    sdktrace.WithBatcher(exp,
        sdktrace.WithBatchTimeout(5*time.Second),
        sdktrace.WithExportTimeout(30*time.Second),
    ),
)

坑 7:在循环中创建 Tracer

❌ 错误做法

go
for _, item := range items {
    tracer := otel.Tracer("my-service") // 每次都创建
    _, span := tracer.Start(ctx, "process")
    // ...
}

✅ 正确做法

go
// 全局创建一次
var tracer = otel.Tracer("my-service")

func processItems(ctx context.Context, items []Item) {
    for _, item := range items {
        ctx, span := tracer.Start(ctx, "process")
        // ...
        span.End()
    }
}

坑 8:没有优雅关闭 TracerProvider

❌ 错误做法

go
func main() {
    tp, _ := InitTracer()
    // 程序直接退出,可能导致数据丢失
}

✅ 正确做法

go
func main() {
    tp, err := InitTracer()
    if err != nil {
        log.Fatal(err)
    }
    
    // 确保优雅关闭
    defer func() {
        ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
        defer cancel()
        
        if err := tp.Shutdown(ctx); err != nil {
            log.Printf("failed to shutdown tracer: %v", err)
        }
    }()
    
    // 运行应用
    run()
}

总结

坑点解决方案
Context 传播始终传递 context
Span 过多控制粒度,批量处理
采样策略配置合理的采样率
Span 未结束使用 defer
敏感信息脱敏处理
错误处理检查 exporter 错误
Tracer 创建全局复用
优雅关闭实现 Shutdown

避开这些坑,让你的 OTel 接入更加顺畅。

上次更新于: