115 lines
3.0 KiB
Go

// 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)
}