155 lines
5.1 KiB
Go
155 lines
5.1 KiB
Go
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
|
||
}
|