Single-call OTel SDK bootstrap setting all three global providers (traces → Tempo, metrics → Mimir, logs → Loki) over OTLP gRPC. What's included: - New(ctx, Config): bootstraps TracerProvider, MeterProvider, and LoggerProvider with OTLP gRPC exporters; sets OTel globals - W3C TraceContext + Baggage propagation set globally - Resource tagging: service.name, service.version, deployment.environment merged with SDK defaults - OTLPInsecure bool for development environments without TLS - Sequential rollback on partial initialization failure — no dangling exporters on error - Returns shutdown func(context.Context) error; caller defers in main or wires into launcher BeforeStop - Tier 5 module: must be imported only by application main packages; zero micro-lib dependencies Tested-via: todo-api POC integration Reviewed-against: docs/adr/
130 lines
4.2 KiB
Go
130 lines
4.2 KiB
Go
package telemetry
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"go.opentelemetry.io/otel"
|
|
"go.opentelemetry.io/otel/attribute"
|
|
otlploggrpc "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc"
|
|
otlpmetricgrpc "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
|
|
otlptracegrpc "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
|
"go.opentelemetry.io/otel/log/global"
|
|
"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"
|
|
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
|
|
)
|
|
|
|
// Config holds OTel bootstrap configuration.
|
|
type Config struct {
|
|
// ServiceName identifies the service in traces, metrics, and logs.
|
|
ServiceName string `env:"OTEL_SERVICE_NAME,required"`
|
|
// ServiceVersion is the deployed version (e.g. "1.4.2").
|
|
ServiceVersion string `env:"OTEL_SERVICE_VERSION" envDefault:"unknown"`
|
|
// Environment is the deployment environment (e.g. "production", "staging").
|
|
Environment string `env:"OTEL_ENVIRONMENT" envDefault:"development"`
|
|
// OTLPEndpoint is the OTLP gRPC collector address (e.g. "alloy:4317").
|
|
OTLPEndpoint string `env:"OTEL_EXPORTER_OTLP_ENDPOINT,required"`
|
|
// OTLPInsecure disables TLS for the OTLP connection. Set true in development.
|
|
OTLPInsecure bool `env:"OTEL_EXPORTER_OTLP_INSECURE" envDefault:"false"`
|
|
}
|
|
|
|
// New bootstraps the full OTel SDK:
|
|
// - TracerProvider → OTLP gRPC → Grafana Alloy → Tempo
|
|
// - MeterProvider → OTLP gRPC → Grafana Alloy → Mimir
|
|
// - LoggerProvider → OTLP gRPC → Grafana Alloy → Loki
|
|
//
|
|
// Sets the three OTel globals so all micro-libs using the global API
|
|
// auto-instrument without importing this module.
|
|
//
|
|
// The returned shutdown function flushes all exporters and must be called
|
|
// before process exit (defer it in main or wire it into the launcher).
|
|
// Returns (shutdown, nil) on success, (nil, err) on failure.
|
|
func New(ctx context.Context, cfg Config) (func(context.Context) error, error) {
|
|
res := newResource(cfg)
|
|
|
|
// --- TracerProvider (traces → Tempo) ---
|
|
traceOpts := []otlptracegrpc.Option{
|
|
otlptracegrpc.WithEndpoint(cfg.OTLPEndpoint),
|
|
}
|
|
if cfg.OTLPInsecure {
|
|
traceOpts = append(traceOpts, otlptracegrpc.WithInsecure())
|
|
}
|
|
traceExporter, err := otlptracegrpc.New(ctx, traceOpts...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tp := sdktrace.NewTracerProvider(
|
|
sdktrace.WithBatcher(traceExporter),
|
|
sdktrace.WithResource(res),
|
|
)
|
|
otel.SetTracerProvider(tp)
|
|
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
|
|
propagation.TraceContext{},
|
|
propagation.Baggage{},
|
|
))
|
|
|
|
// --- MeterProvider (metrics → Mimir) ---
|
|
metricOpts := []otlpmetricgrpc.Option{
|
|
otlpmetricgrpc.WithEndpoint(cfg.OTLPEndpoint),
|
|
}
|
|
if cfg.OTLPInsecure {
|
|
metricOpts = append(metricOpts, otlpmetricgrpc.WithInsecure())
|
|
}
|
|
metricExporter, err := otlpmetricgrpc.New(ctx, metricOpts...)
|
|
if err != nil {
|
|
_ = tp.Shutdown(ctx)
|
|
return nil, err
|
|
}
|
|
mp := sdkmetric.NewMeterProvider(
|
|
sdkmetric.WithReader(sdkmetric.NewPeriodicReader(metricExporter)),
|
|
sdkmetric.WithResource(res),
|
|
)
|
|
otel.SetMeterProvider(mp)
|
|
|
|
// --- LoggerProvider (logs → Loki) ---
|
|
logOpts := []otlploggrpc.Option{
|
|
otlploggrpc.WithEndpoint(cfg.OTLPEndpoint),
|
|
}
|
|
if cfg.OTLPInsecure {
|
|
logOpts = append(logOpts, otlploggrpc.WithInsecure())
|
|
}
|
|
logExporter, err := otlploggrpc.New(ctx, logOpts...)
|
|
if err != nil {
|
|
_ = tp.Shutdown(ctx)
|
|
_ = mp.Shutdown(ctx)
|
|
return nil, err
|
|
}
|
|
lp := sdklog.NewLoggerProvider(
|
|
sdklog.WithProcessor(sdklog.NewBatchProcessor(logExporter)),
|
|
sdklog.WithResource(res),
|
|
)
|
|
global.SetLoggerProvider(lp)
|
|
|
|
shutdown := func(ctx context.Context) error {
|
|
return errors.Join(
|
|
tp.Shutdown(ctx),
|
|
mp.Shutdown(ctx),
|
|
lp.Shutdown(ctx),
|
|
)
|
|
}
|
|
return shutdown, nil
|
|
}
|
|
|
|
// newResource builds an OTel resource with service identity and environment attributes.
|
|
func newResource(cfg Config) *resource.Resource {
|
|
r, _ := resource.Merge(
|
|
resource.Default(),
|
|
resource.NewWithAttributes(
|
|
semconv.SchemaURL,
|
|
semconv.ServiceName(cfg.ServiceName),
|
|
semconv.ServiceVersion(cfg.ServiceVersion),
|
|
attribute.String("deployment.environment", cfg.Environment),
|
|
),
|
|
)
|
|
return r
|
|
}
|