Add telemetry

This commit is contained in:
Alexander
2026-04-14 10:31:56 +02:00
parent 20049881ad
commit 9cc052c162
15 changed files with 580 additions and 62 deletions
+107
View File
@@ -0,0 +1,107 @@
package telemetry
import (
"context"
"io"
"github.com/fujin/anthropic-proxy/internal/config"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
otellog "go.opentelemetry.io/otel/log/global"
"go.opentelemetry.io/otel/sdk/log"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
"go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
)
// Setup initializes OpenTelemetry providers. It always creates a MeterProvider
// so metrics can be recorded in-process. When cfg.ExportEnabled(), OTLP gRPC
// exporters are additionally configured to push to the LGTM stack.
// Returns a shutdown function and an optional io.Writer for the log bridge.
func Setup(ctx context.Context, cfg config.TelemetryConfig) (shutdown func(context.Context) error, logWriter io.Writer, err error) {
res, err := resource.New(ctx,
resource.WithAttributes(
semconv.ServiceName(cfg.ServiceName),
),
)
if err != nil {
return nil, nil, err
}
if !cfg.ExportEnabled() {
// No export — set up in-memory meter provider only so metric
// instruments are valid (they just don't export anywhere).
mp := sdkmetric.NewMeterProvider(sdkmetric.WithResource(res))
otel.SetMeterProvider(mp)
InitMetrics(mp.Meter(cfg.ServiceName))
return func(ctx context.Context) error { return mp.Shutdown(ctx) }, nil, nil
}
// Build exporter options
traceOpts := []otlptracegrpc.Option{otlptracegrpc.WithEndpoint(cfg.Endpoint)}
metricOpts := []otlpmetricgrpc.Option{
otlpmetricgrpc.WithEndpoint(cfg.Endpoint),
otlpmetricgrpc.WithTemporalitySelector(sdkmetric.CumulativeTemporalitySelector),
}
logOpts := []otlploggrpc.Option{otlploggrpc.WithEndpoint(cfg.Endpoint)}
if cfg.Insecure {
traceOpts = append(traceOpts, otlptracegrpc.WithInsecure())
metricOpts = append(metricOpts, otlpmetricgrpc.WithInsecure())
logOpts = append(logOpts, otlploggrpc.WithInsecure())
}
// Trace exporter
traceExp, err := otlptracegrpc.New(ctx, traceOpts...)
if err != nil {
return nil, nil, err
}
tp := trace.NewTracerProvider(
trace.WithBatcher(traceExp),
trace.WithResource(res),
)
otel.SetTracerProvider(tp)
// Metric exporter
metricExp, err := otlpmetricgrpc.New(ctx, metricOpts...)
if err != nil {
return nil, nil, err
}
mp := sdkmetric.NewMeterProvider(
sdkmetric.WithReader(sdkmetric.NewPeriodicReader(metricExp)),
sdkmetric.WithResource(res),
)
otel.SetMeterProvider(mp)
InitMetrics(mp.Meter(cfg.ServiceName))
// Log exporter
logExp, err := otlploggrpc.New(ctx, logOpts...)
if err != nil {
return nil, nil, err
}
lp := log.NewLoggerProvider(
log.WithProcessor(log.NewBatchProcessor(logExp)),
log.WithResource(res),
)
otellog.SetLoggerProvider(lp)
bridge := &LogBridge{provider: lp}
shutdownFn := func(ctx context.Context) error {
var firstErr error
if e := tp.Shutdown(ctx); e != nil && firstErr == nil {
firstErr = e
}
if e := mp.Shutdown(ctx); e != nil && firstErr == nil {
firstErr = e
}
if e := lp.Shutdown(ctx); e != nil && firstErr == nil {
firstErr = e
}
return firstErr
}
return shutdownFn, bridge, nil
}