frostfs-observability/tracing/setup.go

161 lines
3.6 KiB
Go
Raw Normal View History

package tracing
import (
"context"
"fmt"
"sync"
"sync/atomic"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/sdk/trace/tracetest"
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/noop"
)
var (
// tracingLock protects provider, done, config and tracer from concurrent update.
// These fields change when the config is updated or the application is shutdown.
tracingLock = &sync.Mutex{}
provider *sdktrace.TracerProvider
done bool
config = Config{}
tracer = getDefaultTracer()
)
// Setup initializes global tracer.
// Returns true if global tracer was updated.
// Shutdown method must be called for graceful shutdown.
func Setup(ctx context.Context, cfg Config) (bool, error) {
if err := cfg.validate(); err != nil {
return false, err
}
tracingLock.Lock()
defer tracingLock.Unlock()
if done {
return false, fmt.Errorf("failed to setup tracing: already shutdown")
}
if !config.hasChange(&cfg) {
return false, nil
}
if !cfg.Enabled {
config = cfg
tracer.Store(&tracerHolder{Tracer: noop.NewTracerProvider().Tracer("")})
return true, flushAndShutdown(ctx)
}
exp, err := getExporter(ctx, &cfg)
if err != nil {
return false, err
}
prevProvider := provider
provider = sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exp),
sdktrace.WithResource(newResource(&cfg)),
)
config = cfg
tracer.Store(&tracerHolder{Tracer: provider.Tracer(cfg.Service)})
var retErr error
if prevProvider != nil {
retErr = prevProvider.ForceFlush(ctx)
if err := prevProvider.Shutdown(ctx); err != nil {
if retErr == nil {
retErr = err
} else {
retErr = fmt.Errorf("%v ; %v", retErr, err)
}
}
}
return true, retErr
}
// Shutdown shutdowns tracing.
func Shutdown(ctx context.Context) error {
tracingLock.Lock()
defer tracingLock.Unlock()
if done {
return nil
}
done = true
config = Config{}
tracer.Store(&tracerHolder{Tracer: noop.NewTracerProvider().Tracer("")})
return flushAndShutdown(ctx)
}
func getDefaultTracer() *atomic.Pointer[tracerHolder] {
v := new(atomic.Pointer[tracerHolder])
v.Store(&tracerHolder{Tracer: noop.NewTracerProvider().Tracer("")})
return v
}
func flushAndShutdown(ctx context.Context) error {
if provider == nil {
return nil
}
tmp := provider
provider = nil
var retErr error
retErr = tmp.ForceFlush(ctx)
if err := tmp.Shutdown(ctx); err != nil {
if retErr == nil {
retErr = err
} else {
retErr = fmt.Errorf("%v ; %v", retErr, err)
}
}
return retErr
}
func getExporter(ctx context.Context, cfg *Config) (sdktrace.SpanExporter, error) {
switch cfg.Exporter {
default:
return nil, fmt.Errorf("failed to setup tracing: unknown tracing exporter (%s)", cfg.Exporter)
case StdoutExporter:
return stdouttrace.New()
case NoOpExporter:
return tracetest.NewNoopExporter(), nil
case OTLPgRPCExporter:
return otlptracegrpc.New(ctx, otlptracegrpc.WithEndpoint(cfg.Endpoint), otlptracegrpc.WithInsecure())
}
}
func newResource(cfg *Config) *resource.Resource {
attrs := []attribute.KeyValue{
semconv.ServiceName(cfg.Service),
}
if len(cfg.Version) > 0 {
attrs = append(attrs, semconv.ServiceVersion(cfg.Version))
}
if len(cfg.InstanceID) > 0 {
attrs = append(attrs, semconv.ServiceInstanceID(cfg.InstanceID))
}
return resource.NewWithAttributes(
semconv.SchemaURL,
attrs...,
)
}
type tracerHolder struct {
Tracer trace.Tracer
}