trace

package
v0.2.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 19, 2026 License: MIT Imports: 17 Imported by: 0

README

Telemetry 链路追踪系统(Phase 2)

📋 概述

基于 OpenTelemetry 标准的链路追踪系统,使用 OTLP 协议发送到后端(Jaeger/Zipkin/自定义)。

✨ 功能特性

  • OpenTelemetry 标准:完全遵循 OTEL 规范
  • OTLP 导出器:gRPC 协议发送链路数据
  • 自动 Span 创建:多种 Span 类型支持
  • 代码位置追踪:自动提取调用位置和函数名
  • 灵活采样:可配置采样率
  • 批量发送:高性能批量导出

⚙️ 配置

配置结构
type TraceConfig struct {
    Enabled  bool    // 是否启用
    Endpoint string  // OTLP endpoint (gRPC)
    Sampler  float64 // 采样率 0.0-1.0
    Batcher  string  // 导出器类型
}
配置示例
# api/etc/api.yaml
Telemetry:
  ServiceName: idrm-api
  ServiceVersion: 1.0.0
  Environment: dev
  
  Trace:
    Enabled: true
    Endpoint: localhost:4317  # Jaeger OTLP gRPC
    Sampler: 1.0              # 100% 采样
    Batcher: otlp
常用后端配置

Jaeger:

Endpoint: localhost:4317  # OTLP gRPC (推荐)

Zipkin (需要使用 HTTP 导出器):

Endpoint: http://localhost:9411/api/v2/spans

自定义 OTLP Collector:

Endpoint: collector.example.com:4317

🚀 使用方法

1. 初始化
import (
    "idrm/pkg/telemetry/trace"
)

func main() {
    // 初始化链路追踪
    err := trace.Init(
        config.Telemetry.Trace,
        config.Telemetry.ServiceName,
        config.Telemetry.ServiceVersion,
        config.Telemetry.Environment,
    )
    if err != nil {
        panic(err)
    }
    defer trace.Close(context.Background())
    
    // 业务代码...
}
2. 基础使用
创建 Span
import (
    "idrm/pkg/telemetry/trace"
    "go.opentelemetry.io/otel/attribute"
)

func processRequest(ctx context.Context) error {
    // 方式一:普通 Span
    ctx, span := trace.Start(ctx, "processRequest")
    defer span.End()
    
    // 添加属性
    span.SetAttributes(
        attribute.String("user_id", "123"),
        attribute.Int("count", 10),
    )
    
    // 业务逻辑...
    return nil
}
自动提取调用信息
func handleRequest(ctx context.Context) error {
    // 自动提取函数名、文件路径、行号
    ctx, span := trace.StartInternal(ctx)
    defer span.End()
    
    // Span 名称自动设置为: logic.handleRequest
    // 自动添加属性: code.filepath, code.lineno, code.function
    
    // 业务逻辑...
    return nil
}
3. 不同类型的 Span
Server Span(HTTP/RPC 请求)
func handleHTTPRequest(ctx context.Context, req *http.Request) {
    ctx, span := trace.StartServer(ctx, req.URL.Path,
        attribute.String("http.method", req.Method),
        attribute.String("http.url", req.URL.String()),
    )
    defer span.End()
    
    // 处理请求...
}
Client Span(调用外部服务)
func callExternalAPI(ctx context.Context, url string) error {
    ctx, span := trace.StartClient(ctx, "CallExternalAPI",
        attribute.String("http.url", url),
    )
    defer span.End()
    
    // 发送 HTTP 请求...
    return nil
}
Consumer/Producer Span(消息队列)
// 生产者
func publishMessage(ctx context.Context, topic string, msg []byte) error {
    ctx, span := trace.StartProducer(ctx, "PublishMessage",
        attribute.String("messaging.destination", topic),
        attribute.Int("messaging.message_size", len(msg)),
    )
    defer span.End()
    
    // 发送消息...
    return nil
}

// 消费者
func consumeMessage(ctx context.Context, msg Message) error {
    ctx, span := trace.StartConsumer(ctx, "ConsumeMessage",
        attribute.String("messaging.destination", msg.Topic),
    )
    defer span.End()
    
    // 处理消息...
    return nil
}
4. 错误处理
使用 End 辅助方法
func processData(ctx context.Context) error {
    ctx, span := trace.StartInternal(ctx)
    defer func() {
        // 自动记录错误状态
        trace.End(span, err)
    }()
    
    var err error
    // 业务逻辑...
    if err != nil {
        return err
    }
    
    return nil
}
手动记录错误
func processData(ctx context.Context) error {
    ctx, span := trace.StartInternal(ctx)
    defer span.End()
    
    err := doSomething()
    if err != nil {
        // 记录错误
        trace.SetError(span, err)
        return err
    }
    
    return nil
}
5. 添加事件和属性
func processOrder(ctx context.Context, order Order) error {
    ctx, span := trace.StartInternal(ctx)
    defer span.End()
    
    // 添加属性
    trace.SetAttributes(span,
        attribute.String("order.id", order.ID),
        attribute.Float64("order.amount", order.Amount),
    )
    
    // 添加事件
    trace.AddEvent(span, "OrderValidated",
        attribute.String("status", "valid"),
    )
    
    // 业务逻辑...
    
    trace.AddEvent(span, "OrderProcessed")
    
    return nil
}
6. 获取 TraceID 和 SpanID
func logRequest(ctx context.Context) {
    traceID := trace.GetTraceID(ctx)
    spanID := trace.GetSpanID(ctx)
    
    logx.WithContext(ctx).Infow("处理请求",
        logx.Field("trace_id", traceID),
        logx.Field("span_id", spanID))
}

📝 完整示例

Logic 层使用
// api/internal/logic/category/createcategorylogic.go
package category

import (
    "context"
    
    "idrm/api/internal/svc"
    "idrm/api/internal/types"
    "idrm/pkg/telemetry/trace"
    
    "github.com/zeromicro/go-zero/core/logx"
    "go.opentelemetry.io/otel/attribute"
)

type CreateCategoryLogic struct {
    logx.Logger
    ctx    context.Context
    svcCtx *svc.ServiceContext
}

func (l *CreateCategoryLogic) CreateCategory(req *types.CreateCategoryReq) (*types.CategoryResp, error) {
    // 1. 创建 Span(自动提取函数信息)
    ctx, span := trace.StartInternal(l.ctx)
    defer span.End()
    
    // 2. 添加业务属性
    span.SetAttributes(
        attribute.String("category.name", req.Name),
        attribute.String("category.code", req.Code),
    )
    
    // 3. 记录日志(自动包含 trace_id)
    logx.WithContext(ctx).Infow("创建类别",
        logx.Field("name", req.Name))
    
    // 4. 验证(子 Span)
    if err := l.validateRequest(ctx, req); err != nil {
        trace.SetError(span, err)
        return nil, err
    }
    
    // 5. 数据库操作
    category, err := l.svcCtx.CategoryModel.Insert(ctx, data)
    if err != nil {
        trace.SetError(span, err)
        return nil, err
    }
    
    // 6. 添加事件
    trace.AddEvent(span, "CategoryCreated",
        attribute.Int64("category.id", category.Id))
    
    return &types.CategoryResp{...}, nil
}

func (l *CreateCategoryLogic) validateRequest(ctx context.Context, req *types.CreateCategoryReq) error {
    // 创建子 Span
    ctx, span := trace.Start(ctx, "ValidateRequest")
    defer span.End()
    
    // 验证逻辑...
    return nil
}

🔍 Span 层级示例

processRequest                           [Server Span]
├── validateRequest                      [Internal Span]
├── CategoryModel.Insert                 [Internal Span]
│   └── SQL Query                        [自动创建]
└── callExternalAPI                      [Client Span]
    └── HTTP Request                     [自动创建]

🎯 最佳实践

1. Span 命名
// ✅ 好的命名:清晰、具体
trace.Start(ctx, "CreateCategory")
trace.Start(ctx, "ValidateUserInput")
trace.Start(ctx, "QueryDatabase")

// ❌ 不好的命名:模糊、通用
trace.Start(ctx, "process")
trace.Start(ctx, "handle")
2. 属性使用
// ✅ 使用有意义的属性
span.SetAttributes(
    attribute.String("user.id", userID),
    attribute.String("order.id", orderID),
    attribute.Int("item.count", count),
)

// ❌ 避免敏感信息
span.SetAttributes(
    attribute.String("password", pwd),        // ❌
    attribute.String("credit_card", card),    // ❌
)
3. 错误处理
// ✅ 始终记录错误
if err != nil {
    trace.SetError(span, err)
    return err
}

// ✅ 使用 defer + End
ctx, span := trace.StartInternal(ctx)
defer trace.End(span, err)
4. 控制 Span 粒度
// ✅ 合理粒度:重要操作创建 Span
func processOrder(ctx context.Context) {
    ctx, span := trace.StartInternal(ctx)
    defer span.End()
    
    // 子操作
    validateOrder(ctx)  // 创建子 Span
    saveOrder(ctx)      // 创建子 Span
}

// ❌ 过细粒度:每个小函数都创建
func add(ctx context.Context, a, b int) int {
    ctx, span := trace.Start(ctx, "add")  // 不必要
    defer span.End()
    return a + b
}

🔧 与 go-zero 集成

go-zero 的 HTTP 服务会自动创建 Server Span,无需手动添加。

// HTTP Handler 层不需要手动创建 Span
// go-zero 自动创建

// Logic 层创建内部 Span
func (l *Logic) Handle(req *Req) (*Resp, error) {
    ctx, span := trace.StartInternal(l.ctx)
    defer span.End()
    
    // 业务逻辑...
}

📊 查看链路数据

使用 Jaeger
  1. 启动 Jaeger:
docker run -d --name jaeger \
  -p 4317:4317 \
  -p 16686:16686 \
  jaegertracing/all-in-one:latest
  1. 访问 UI:http://localhost:16686

  2. 查询链路:选择服务名 idrm-api

❓ 常见问题

Q: 如何关闭链路追踪?

A: 设置 Trace.Enabled: false

Q: 采样率如何设置?

A: Sampler: 1.0 表示 100%,0.5 表示 50%。生产环境建议 0.1-0.5。

Q: Span 太多会影响性能吗?

A: 使用批量发送,影响很小。但避免在循环中创建过多 Span。

Q: 如何在日志中关联 TraceID?

A: 使用 logx.WithContext(ctx) 会自动提取 TraceID。

🆘 故障排除

无法连接到 OTLP endpoint:

  • 检查 endpoint 配置是否正确
  • 确认后端服务正在运行
  • 检查网络连通性

Span 未显示:

  • 检查采样率设置
  • 确认 TraceProvider 已正确初始化
  • 查看后端服务日志

📚 参考资料

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AddEvent

func AddEvent(span trace.Span, name string, attrs ...attribute.KeyValue)

AddEvent 添加事件

func Close

func Close(ctx context.Context) error

Close 关闭链路追踪

func End

func End(span trace.Span, err error)

End 结束 Span 并记录错误(如果有)

func GetSpan

func GetSpan(ctx context.Context) trace.Span

GetSpan 从 Context 获取当前 Span

func GetSpanID

func GetSpanID(ctx context.Context) string

GetSpanID 从 Context 获取 SpanID

func GetTraceID

func GetTraceID(ctx context.Context) string

GetTraceID 从 Context 获取 TraceID

func Init

func Init(config TraceConfig, serviceName, version, environment string) error

Init 初始化链路追踪

func RecordError

func RecordError(ctx context.Context, err error, attrs ...attribute.KeyValue)

RecordError 记录错误到 Span

func SetAttributes

func SetAttributes(span trace.Span, attrs ...attribute.KeyValue)

SetAttributes 设置属性

func SetError

func SetError(span trace.Span, err error)

SetError 设置错误状态

func Start

func Start(ctx context.Context, spanName string, opts ...trace.SpanStartOption) (context.Context, trace.Span)

Start 开始一个 Span

func StartClient

func StartClient(ctx context.Context, spanName string, attrs ...attribute.KeyValue) (context.Context, trace.Span)

StartClient 开始客户端 Span(用于 HTTP/RPC 调用)

func StartConsumer

func StartConsumer(ctx context.Context, spanName string, attrs ...attribute.KeyValue) (context.Context, trace.Span)

StartConsumer 开始消费者 Span(用于消息队列消费)

func StartInternal

func StartInternal(ctx context.Context) (context.Context, trace.Span)

StartInternal 开始内部方法追踪(自动提取调用信息) 使用 runtime.Caller 自动获取调用位置和函数名

func StartProducer

func StartProducer(ctx context.Context, spanName string, attrs ...attribute.KeyValue) (context.Context, trace.Span)

StartProducer 开始生产者 Span(用于消息队列生产)

func StartServer

func StartServer(ctx context.Context, spanName string, attrs ...attribute.KeyValue) (context.Context, trace.Span)

StartServer 开始服务端 Span(用于 HTTP/RPC 服务)

func Tracer

func Tracer() trace.Tracer

Tracer 获取全局 Tracer

func WithAttributes

func WithAttributes(kv ...interface{}) []attribute.KeyValue

WithAttributes 便捷方法:创建属性

Types

type TraceConfig

type TraceConfig struct {
	Enabled  bool
	Endpoint string
	Sampler  float64
	Batcher  string
}

TraceConfig 链路追踪配置

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL