126 lines
3.2 KiB
Go
126 lines
3.2 KiB
Go
// 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(),
|
|
}
|
|
}
|