kereru-forms/backend/internal/telemetry/telemetry.go

126 lines
3.2 KiB
Go
Raw Permalink Normal View History

// Package telemetry wires OpenTelemetry tracing and metrics.
package telemetry
import (
"context"
"strings"
"time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
"go.opentelemetry.io/otel/sdk/trace"
)
// Options configures the telemetry providers.
type Options struct {
Enabled bool
Endpoint string
ServiceName string
Environment string
Version string
}
// Provider owns the configured OpenTelemetry providers.
type Provider struct {
Enabled bool
shutdown []func(context.Context) error
}
// Setup installs global tracer and meter providers. When disabled, the
// OpenTelemetry no-op providers remain in place and Shutdown is a no-op.
func Setup(ctx context.Context, opts Options) (*Provider, error) {
p := &Provider{Enabled: opts.Enabled}
if !opts.Enabled {
return p, nil
}
serviceName := opts.ServiceName
if serviceName == "" {
serviceName = "kereru-forms"
}
endpoint := opts.Endpoint
if endpoint == "" {
endpoint = "localhost:4318"
}
res, err := resource.New(ctx,
resource.WithAttributes(
attribute.String("service.name", serviceName),
attribute.String("service.version", opts.Version),
attribute.String("deployment.environment", opts.Environment),
),
)
if err != nil {
return nil, err
}
traceExporter, err := otlptracehttp.New(ctx, traceOptions(endpoint)...)
if err != nil {
return nil, err
}
tp := trace.NewTracerProvider(
trace.WithBatcher(traceExporter),
trace.WithResource(res),
)
otel.SetTracerProvider(tp)
p.shutdown = append(p.shutdown, tp.Shutdown)
metricExporter, err := otlpmetrichttp.New(ctx, metricOptions(endpoint)...)
if err != nil {
_ = tp.Shutdown(ctx)
return nil, err
}
mp := metric.NewMeterProvider(
metric.WithReader(metric.NewPeriodicReader(metricExporter,
metric.WithInterval(15*time.Second))),
metric.WithResource(res),
)
otel.SetMeterProvider(mp)
p.shutdown = append(p.shutdown, mp.Shutdown)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{}, propagation.Baggage{},
))
return p, nil
}
// Shutdown flushes telemetry. Safe to call when telemetry is disabled.
func (p *Provider) Shutdown(ctx context.Context) error {
if p == nil {
return nil
}
var firstErr error
for _, fn := range p.shutdown {
if err := fn(ctx); err != nil && firstErr == nil {
firstErr = err
}
}
return firstErr
}
func traceOptions(endpoint string) []otlptracehttp.Option {
if strings.Contains(endpoint, "://") {
return []otlptracehttp.Option{otlptracehttp.WithEndpointURL(endpoint)}
}
return []otlptracehttp.Option{
otlptracehttp.WithEndpoint(endpoint),
otlptracehttp.WithInsecure(),
}
}
func metricOptions(endpoint string) []otlpmetrichttp.Option {
if strings.Contains(endpoint, "://") {
return []otlpmetrichttp.Option{otlpmetrichttp.WithEndpointURL(endpoint)}
}
return []otlpmetrichttp.Option{
otlpmetrichttp.WithEndpoint(endpoint),
otlpmetrichttp.WithInsecure(),
}
}