first commit

This commit is contained in:
2026-09-11 01:47:20 +08:00
commit 303207d3cc
74 changed files with 8348 additions and 0 deletions
+154
View File
@@ -0,0 +1,154 @@
package tracing
import (
"context"
"errors"
"fmt"
"git.lzcwy.cn/zhengchiliu/zt-closet-manage-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-manage-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
}