mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-07-02 00:17:40 +08:00
feat(otel): pin trace-based exemplar filter on the meter provider
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
"go.opentelemetry.io/otel/sdk/log"
|
||||
"go.opentelemetry.io/otel/sdk/metric"
|
||||
"go.opentelemetry.io/otel/sdk/metric/exemplar"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
"go.opentelemetry.io/otel/sdk/trace"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||
@@ -92,6 +93,7 @@ func InitMeterProvider(logger *slog.Logger, serviceName, serviceVersion string)
|
||||
metricOpts := []metric.Option{
|
||||
metric.WithResource(res),
|
||||
}
|
||||
metricOpts = append(metricOpts, exemplarFilterOptions()...)
|
||||
|
||||
metricReader, err := autoexport.NewMetricReader(ctx)
|
||||
if err != nil {
|
||||
@@ -108,6 +110,17 @@ func InitMeterProvider(logger *slog.Logger, serviceName, serviceVersion string)
|
||||
return meterProvider.Shutdown, nil
|
||||
}
|
||||
|
||||
// exemplarFilterOptions returns the meter provider options that pin trace-based
|
||||
// exemplars, so the histograms expose the trace id of a representative
|
||||
// measurement. It yields no option when the operator selects a filter via
|
||||
// OTEL_METRICS_EXEMPLAR_FILTER, letting the SDK's own env handling win.
|
||||
func exemplarFilterOptions() []metric.Option {
|
||||
if _, ok := os.LookupEnv("OTEL_METRICS_EXEMPLAR_FILTER"); ok {
|
||||
return nil
|
||||
}
|
||||
return []metric.Option{metric.WithExemplarFilter(exemplar.TraceBasedFilter)}
|
||||
}
|
||||
|
||||
// InitLoggerProvider initializes the OpenTelemetry logger provider.
|
||||
func InitLoggerProvider(logger *slog.Logger, serviceName, serviceVersion string) (shutdown func(context.Context) error, handler slog.Handler, err error) {
|
||||
initOtelLogger(logger)
|
||||
|
||||
@@ -3,9 +3,14 @@ package otel
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
|
||||
"go.opentelemetry.io/otel/sdk/metric/exemplar"
|
||||
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
)
|
||||
|
||||
// TestInitTracerProvider_HonorsSamplerEnv guards the contract that the tracer
|
||||
@@ -40,3 +45,71 @@ func TestInitTracerProvider_HonorsSamplerEnv(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExemplarFilterOptions(t *testing.T) {
|
||||
t.Run("default pins trace-based", func(t *testing.T) {
|
||||
if v, ok := os.LookupEnv("OTEL_METRICS_EXEMPLAR_FILTER"); ok {
|
||||
os.Unsetenv("OTEL_METRICS_EXEMPLAR_FILTER")
|
||||
t.Cleanup(func() { os.Setenv("OTEL_METRICS_EXEMPLAR_FILTER", v) })
|
||||
}
|
||||
if got := exemplarFilterOptions(); len(got) != 1 {
|
||||
t.Errorf("expected 1 option when env unset, got %d", len(got))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("env override yields no option", func(t *testing.T) {
|
||||
t.Setenv("OTEL_METRICS_EXEMPLAR_FILTER", "always_off")
|
||||
if got := exemplarFilterOptions(); len(got) != 0 {
|
||||
t.Errorf("expected 0 options when env set, got %d", len(got))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestMeterProvider_TraceBasedExemplar guards that the trace-based filter we pin
|
||||
// actually attaches a trace id to a histogram measurement recorded inside a
|
||||
// sampled span.
|
||||
func TestMeterProvider_TraceBasedExemplar(t *testing.T) {
|
||||
reader := sdkmetric.NewManualReader()
|
||||
provider := sdkmetric.NewMeterProvider(
|
||||
sdkmetric.WithReader(reader),
|
||||
sdkmetric.WithExemplarFilter(exemplar.TraceBasedFilter),
|
||||
)
|
||||
t.Cleanup(func() { _ = provider.Shutdown(context.Background()) })
|
||||
|
||||
hist, err := provider.Meter("test").Float64Histogram("conversion.duration")
|
||||
if err != nil {
|
||||
t.Fatalf("create histogram: %v", err)
|
||||
}
|
||||
|
||||
tracer := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample())).Tracer("test")
|
||||
ctx, span := tracer.Start(context.Background(), "conversion")
|
||||
hist.Record(ctx, 1.0)
|
||||
traceID := span.SpanContext().TraceID()
|
||||
span.End()
|
||||
|
||||
var rm metricdata.ResourceMetrics
|
||||
if err := reader.Collect(context.Background(), &rm); err != nil {
|
||||
t.Fatalf("collect: %v", err)
|
||||
}
|
||||
|
||||
var found bool
|
||||
for _, sm := range rm.ScopeMetrics {
|
||||
for _, m := range sm.Metrics {
|
||||
hd, ok := m.Data.(metricdata.Histogram[float64])
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, dp := range hd.DataPoints {
|
||||
for _, ex := range dp.Exemplars {
|
||||
if string(ex.TraceID) == string(traceID[:]) {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Error("expected a trace-based exemplar carrying the span trace id")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user