// Package otelx 是 OpenTelemetry 链路追踪的统一启动器与门面。 // // 各服务 main 启动时调一次 Init(ctx, "服务名"),即: // - 注册全局 W3C TraceContext 传播器(即便禁用导出,trace_id 仍能跨服务串起来); // - 把 span 经 OTLP/HTTP 批量导出到 OTEL_EXPORTER_OTLP_ENDPOINT(默认 localhost:4318,即 docker 里的 Jaeger)。 // // 业务代码用 otelx.Tracer().Start(ctx, "span 名") 开 span;跨 NATS 的传播由 bus 层自动完成。 // 设 OTEL_SDK_DISABLED=true 可整体关闭导出(仍保留传播器,零依赖运行)。 package otelx import ( "context" "log" "os" "strings" "go.opentelemetry.io/otel" "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.26.0" "go.opentelemetry.io/otel/trace" ) // scopeName 是本项目所有 span 的 instrumentation scope(Jaeger 里按此归组)。 const scopeName = "github.com/sundynix/sundynix-agentix" const defaultEndpoint = "localhost:4318" // Jaeger all-in-one 的 OTLP/HTTP 入口 // Init 初始化全局链路追踪。返回 shutdown,进程退出前调用以 flush 残留 span。 // 导出器构建失败 / Jaeger 不在线都不阻断启动(批处理异步丢弃),保证可观测性是「增益」而非「依赖」。 func Init(ctx context.Context, serviceName string) (func(context.Context) error, error) { // 传播器始终注册:哪怕本服务不导出,也要把上游 traceparent 透传给下游。 otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{}, )) noop := func(context.Context) error { return nil } if strings.EqualFold(os.Getenv("OTEL_SDK_DISABLED"), "true") { log.Printf("[otel] 导出已禁用 (OTEL_SDK_DISABLED=true),service=%s", serviceName) return noop, nil } endpoint := os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT") if endpoint == "" { endpoint = defaultEndpoint } // 容忍带 scheme 的写法(http://host:port),otlptracehttp.WithEndpoint 只要 host:port。 endpoint = strings.TrimPrefix(strings.TrimPrefix(endpoint, "http://"), "https://") endpoint = strings.TrimSuffix(endpoint, "/") exp, err := otlptracehttp.New(ctx, otlptracehttp.WithEndpoint(endpoint), otlptracehttp.WithInsecure(), // 本地 Jaeger 无 TLS ) if err != nil { log.Printf("[otel] 导出器构建失败(降级为不导出): %v", err) return noop, nil } res, _ := resource.Merge(resource.Default(), resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceName(serviceName), )) tp := sdktrace.NewTracerProvider( sdktrace.WithBatcher(exp), sdktrace.WithResource(res), ) otel.SetTracerProvider(tp) log.Printf("[otel] 链路追踪已启用 service=%s → otlp http://%s", serviceName, endpoint) return tp.Shutdown, nil } // Tracer 返回统一 scope 的 tracer,供业务开 span。 func Tracer() trace.Tracer { return otel.Tracer(scopeName) }