// Package otel 提供 OpenTelemetry 链路追踪功能 package otel import ( "context" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/trace" ) var tracer trace.Tracer // Config OpenTelemetry 配置 type Config struct { ServiceName string ServiceVersion string Environment string Endpoint string // Tempo OTLP HTTP endpoint, e.g., "tempo:4318" Enabled bool } // Init 初始化 OpenTelemetry // 返回 shutdown 函数,在程序退出时调用 func Init(cfg Config) (func(context.Context) error, error) { if !cfg.Enabled { // 如果未启用,返回空的 shutdown 函数 tracer = otel.Tracer(cfg.ServiceName) return func(ctx context.Context) error { return nil }, nil } ctx := context.Background() // 创建 OTLP HTTP exporter client := otlptracehttp.NewClient( otlptracehttp.WithEndpoint(cfg.Endpoint), otlptracehttp.WithInsecure(), // 内网使用,不需要 TLS ) exporter, err := otlptrace.New(ctx, client) if err != nil { return nil, err } // 创建 resource res, err := resource.Merge( resource.Default(), resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceName(cfg.ServiceName), semconv.ServiceVersion(cfg.ServiceVersion), attribute.String("environment", cfg.Environment), ), ) if err != nil { return nil, err } // 创建 TracerProvider tp := sdktrace.NewTracerProvider( sdktrace.WithBatcher(exporter, sdktrace.WithBatchTimeout(5*time.Second), ), sdktrace.WithResource(res), sdktrace.WithSampler(sdktrace.AlwaysSample()), // 生产环境可改为采样 ) // 设置全局 TracerProvider 和 Propagator otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{}, )) tracer = tp.Tracer(cfg.ServiceName) return tp.Shutdown, nil } // Tracer 获取全局 Tracer func Tracer() trace.Tracer { if tracer == nil { tracer = otel.Tracer("bindbox-game") } return tracer } // StartSpan 开始一个新的 span func StartSpan(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span) { return Tracer().Start(ctx, name, opts...) } // SpanFromContext 从 context 获取当前 span func SpanFromContext(ctx context.Context) trace.Span { return trace.SpanFromContext(ctx) } // AddEvent 为当前 span 添加事件 func AddEvent(ctx context.Context, name string, attrs ...attribute.KeyValue) { span := trace.SpanFromContext(ctx) span.AddEvent(name, trace.WithAttributes(attrs...)) } // SetError 设置 span 错误状态 func SetError(ctx context.Context, err error) { span := trace.SpanFromContext(ctx) span.RecordError(err) }