Files
zt-closet-backend/pkg/tracing/provider.go
T
2026-09-11 01:41:52 +08:00

155 lines
5.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package tracing
import (
"context"
"errors"
"fmt"
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/pkg/setting"
"go.opentelemetry.io/contrib/instrumentation/runtime"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
logglobal "go.opentelemetry.io/otel/log/global"
metricnoop "go.opentelemetry.io/otel/metric/noop"
"go.opentelemetry.io/otel/propagation"
sdklog "go.opentelemetry.io/otel/sdk/log"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/noop"
)
// defaultServiceName 缺省服务名。
const defaultServiceName = "zt-closet-backend"
// noopShutdown 安全的空关闭函数。
func noopShutdown(context.Context) error { return nil }
// NewOTelProvider 初始化 OpenTelemetry 三大信号(Traces/Metrics/Logs)并通过 OTLP gRPC 导出。
//
// 返回:
// - trace.Tracer:全局 Tracer(供 global.Tracer 使用);
// - func(ctx) error:聚合的优雅关闭函数,进程退出时调用以 flush 三个 Provider;
// - error:初始化错误。
//
// 行为约定:
// - cfg.Enabled=false:返回 no-op Tracer 与空 Shutdown,不建立任何导出器(业务层 Tracer.Start 仍安全)。
// - cfg.Insecure=true:OTLP gRPC 使用非 TLS(内网采集器)。
// - 导出器采用惰性连接,采集器不可用时不阻断启动,仅后台重试。
func NewOTelProvider(cfg *setting.OtelSettingS) (trace.Tracer, func(context.Context) error, error) {
name := defaultServiceName
if cfg != nil && cfg.ServiceName != "" {
name = cfg.ServiceName
}
// 未启用:降级为 no-op。
if cfg == nil || !cfg.Enabled {
return noop.NewTracerProvider().Tracer(name), noopShutdown, nil
}
ctx := context.Background()
res, err := resource.New(ctx, resource.WithAttributes(
attribute.String("service.name", name),
attribute.String("service.version", valueOr(cfg.ServiceVersion, "unknown")),
attribute.String("deployment.environment", valueOr(cfg.Environment, "unknown")),
))
if err != nil {
return noop.NewTracerProvider().Tracer(name), noopShutdown, fmt.Errorf("otel build resource: %w", err)
}
endpoint := cfg.ExporterEndpoint
// ===== Traces =====
traceOpts := []otlptracegrpc.Option{otlptracegrpc.WithEndpoint(endpoint)}
if cfg.Insecure {
traceOpts = append(traceOpts, otlptracegrpc.WithInsecure())
}
traceExp, err := otlptracegrpc.New(ctx, traceOpts...)
if err != nil {
return noop.NewTracerProvider().Tracer(name), noopShutdown, fmt.Errorf("otel trace exporter: %w", err)
}
ratio := cfg.SamplerRatio
if ratio <= 0 {
ratio = 1.0
}
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(traceExp),
sdktrace.WithResource(res),
sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased(ratio))),
)
otel.SetTracerProvider(tp)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{},
))
// ===== Metrics =====
metricOpts := []otlpmetricgrpc.Option{otlpmetricgrpc.WithEndpoint(endpoint)}
if cfg.Insecure {
metricOpts = append(metricOpts, otlpmetricgrpc.WithInsecure())
}
metricExp, err := otlpmetricgrpc.New(ctx, metricOpts...)
if err != nil {
// 已建 trace,尽力关闭并恢复全局默认 provider,避免留下已关闭的 provider。
_ = tp.Shutdown(ctx)
otel.SetTracerProvider(noop.NewTracerProvider())
return noop.NewTracerProvider().Tracer(name), noopShutdown, fmt.Errorf("otel metric exporter: %w", err)
}
mp := sdkmetric.NewMeterProvider(
sdkmetric.WithResource(res),
sdkmetric.WithReader(sdkmetric.NewPeriodicReader(metricExp)),
)
otel.SetMeterProvider(mp)
// Go runtime 指标采集(GC、goroutine、内存等)。
if rerr := runtime.Start(runtime.WithMeterProvider(mp)); rerr != nil {
// 非致命,仅记录。
fmt.Printf("otel runtime metrics start failed: %v\n", rerr)
}
// ===== Logs =====
logOpts := []otlploggrpc.Option{otlploggrpc.WithEndpoint(endpoint)}
if cfg.Insecure {
logOpts = append(logOpts, otlploggrpc.WithInsecure())
}
logExp, err := otlploggrpc.New(ctx, logOpts...)
if err != nil {
_ = tp.Shutdown(ctx)
_ = mp.Shutdown(ctx)
otel.SetTracerProvider(noop.NewTracerProvider())
otel.SetMeterProvider(metricnoop.NewMeterProvider())
return noop.NewTracerProvider().Tracer(name), noopShutdown, fmt.Errorf("otel log exporter: %w", err)
}
lp := sdklog.NewLoggerProvider(
sdklog.WithResource(res),
sdklog.WithProcessor(sdklog.NewBatchProcessor(logExp)),
)
logglobal.SetLoggerProvider(lp)
// 聚合优雅关闭:依次 flush/关闭三个 Provider。
shutdown := func(ctx context.Context) error {
return errors.Join(
tp.Shutdown(ctx),
mp.Shutdown(ctx),
lp.Shutdown(ctx),
)
}
return tp.Tracer(name), shutdown, nil
}
// valueOr 返回 v,v 为空时返回 def。
func valueOr(v, def string) string {
if v == "" {
return def
}
return v
}