first commit
This commit is contained in:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user