feat(telemetry): initial stable release v0.9.0
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/
This commit is contained in:
129
telemetry.go
Normal file
129
telemetry.go
Normal file
@@ -0,0 +1,129 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user