mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-10-18 10:13:15 +01:00
fix(otel): keep telemetry off unless an exporter is configured
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"go.opentelemetry.io/contrib/bridges/otelslog"
|
"go.opentelemetry.io/contrib/bridges/otelslog"
|
||||||
@@ -74,6 +75,24 @@ func buildResource(ctx context.Context, logger *slog.Logger, serviceName, servic
|
|||||||
return merged
|
return merged
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OTEL_*_EXPORTER select the exporter for each signal. autoexport treats an
|
||||||
|
// unset or empty value as a request for the OTLP exporter, which then fails
|
||||||
|
// against the default localhost:4318 endpoint when nothing listens there and,
|
||||||
|
// for metrics, keeps retrying on the periodic reader's timer. Gotenberg keeps
|
||||||
|
// telemetry opt-in: a signal with no exporter configured is built without one
|
||||||
|
// and stays inert. See https://github.com/gotenberg/gotenberg/issues/1643.
|
||||||
|
const (
|
||||||
|
tracesExporterEnvKey = "OTEL_TRACES_EXPORTER"
|
||||||
|
metricsExporterEnvKey = "OTEL_METRICS_EXPORTER"
|
||||||
|
logsExporterEnvKey = "OTEL_LOGS_EXPORTER"
|
||||||
|
)
|
||||||
|
|
||||||
|
// exporterConfigured reports whether the operator selected an exporter for the
|
||||||
|
// signal owning envKey. An unset or blank value keeps that signal off.
|
||||||
|
func exporterConfigured(envKey string) bool {
|
||||||
|
return strings.TrimSpace(os.Getenv(envKey)) != ""
|
||||||
|
}
|
||||||
|
|
||||||
// InitTracerProvider initializes the OpenTelemetry tracer provider.
|
// InitTracerProvider initializes the OpenTelemetry tracer provider.
|
||||||
func InitTracerProvider(logger *slog.Logger, serviceName, serviceVersion string) (shutdown func(context.Context) error, err error) {
|
func InitTracerProvider(logger *slog.Logger, serviceName, serviceVersion string) (shutdown func(context.Context) error, err error) {
|
||||||
initOtelLogger(logger)
|
initOtelLogger(logger)
|
||||||
@@ -86,14 +105,15 @@ func InitTracerProvider(logger *slog.Logger, serviceName, serviceVersion string)
|
|||||||
trace.WithResource(res),
|
trace.WithResource(res),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if exporterConfigured(tracesExporterEnvKey) {
|
||||||
traceExporter, err := autoexport.NewSpanExporter(ctx)
|
traceExporter, err := autoexport.NewSpanExporter(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !autoexport.IsNoneSpanExporter(traceExporter) {
|
if !autoexport.IsNoneSpanExporter(traceExporter) {
|
||||||
traceOpts = append(traceOpts, trace.WithBatcher(traceExporter))
|
traceOpts = append(traceOpts, trace.WithBatcher(traceExporter))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
traceProvider := trace.NewTracerProvider(traceOpts...)
|
traceProvider := trace.NewTracerProvider(traceOpts...)
|
||||||
otel.SetTracerProvider(traceProvider)
|
otel.SetTracerProvider(traceProvider)
|
||||||
@@ -119,14 +139,15 @@ func InitMeterProvider(logger *slog.Logger, serviceName, serviceVersion string)
|
|||||||
}
|
}
|
||||||
metricOpts = append(metricOpts, exemplarFilterOptions()...)
|
metricOpts = append(metricOpts, exemplarFilterOptions()...)
|
||||||
|
|
||||||
|
if exporterConfigured(metricsExporterEnvKey) {
|
||||||
metricReader, err := autoexport.NewMetricReader(ctx)
|
metricReader, err := autoexport.NewMetricReader(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !autoexport.IsNoneMetricReader(metricReader) {
|
if !autoexport.IsNoneMetricReader(metricReader) {
|
||||||
metricOpts = append(metricOpts, metric.WithReader(metricReader))
|
metricOpts = append(metricOpts, metric.WithReader(metricReader))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
meterProvider := metric.NewMeterProvider(metricOpts...)
|
meterProvider := metric.NewMeterProvider(metricOpts...)
|
||||||
otel.SetMeterProvider(meterProvider)
|
otel.SetMeterProvider(meterProvider)
|
||||||
@@ -157,14 +178,15 @@ func InitLoggerProvider(logger *slog.Logger, serviceName, serviceVersion string)
|
|||||||
log.WithResource(res),
|
log.WithResource(res),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if exporterConfigured(logsExporterEnvKey) {
|
||||||
logExporter, err := autoexport.NewLogExporter(ctx)
|
logExporter, err := autoexport.NewLogExporter(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !autoexport.IsNoneLogExporter(logExporter) {
|
if !autoexport.IsNoneLogExporter(logExporter) {
|
||||||
logOpts = append(logOpts, log.WithProcessor(log.NewBatchProcessor(logExporter)))
|
logOpts = append(logOpts, log.WithProcessor(log.NewBatchProcessor(logExporter)))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
loggerProvider := log.NewLoggerProvider(logOpts...)
|
loggerProvider := log.NewLoggerProvider(logOpts...)
|
||||||
otelHandler := otelslog.NewHandler(serviceName, otelslog.WithLoggerProvider(loggerProvider))
|
otelHandler := otelslog.NewHandler(serviceName, otelslog.WithLoggerProvider(loggerProvider))
|
||||||
|
|||||||
@@ -89,6 +89,49 @@ func TestInitTracerProvider_HonorsSamplerEnv(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestExporterConfigured pins the opt-in gate: an unset or blank
|
||||||
|
// OTEL_*_EXPORTER keeps the signal off, so Gotenberg never wires the OTLP
|
||||||
|
// exporter that autoexport would otherwise default to and fail to reach at
|
||||||
|
// localhost:4318. See https://github.com/gotenberg/gotenberg/issues/1643.
|
||||||
|
func TestExporterConfigured(t *testing.T) {
|
||||||
|
const key = "OTEL_METRICS_EXPORTER"
|
||||||
|
|
||||||
|
orig, had := os.LookupEnv(key)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if had {
|
||||||
|
os.Setenv(key, orig)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
os.Unsetenv(key)
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
scenario string
|
||||||
|
unset bool
|
||||||
|
value string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{"unset", true, "", false},
|
||||||
|
{"empty", false, "", false},
|
||||||
|
{"whitespace only", false, " ", false},
|
||||||
|
{"none", false, "none", true},
|
||||||
|
{"otlp", false, "otlp", true},
|
||||||
|
{"padded value", false, " otlp ", true},
|
||||||
|
} {
|
||||||
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
|
if tc.unset {
|
||||||
|
os.Unsetenv(key)
|
||||||
|
} else {
|
||||||
|
os.Setenv(key, tc.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := exporterConfigured(key); got != tc.want {
|
||||||
|
t.Errorf("exporterConfigured(%q) = %v, want %v", key, got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestExemplarFilterOptions(t *testing.T) {
|
func TestExemplarFilterOptions(t *testing.T) {
|
||||||
t.Run("default pins trace-based", func(t *testing.T) {
|
t.Run("default pins trace-based", func(t *testing.T) {
|
||||||
if v, ok := os.LookupEnv("OTEL_METRICS_EXEMPLAR_FILTER"); ok {
|
if v, ok := os.LookupEnv("OTEL_METRICS_EXEMPLAR_FILTER"); ok {
|
||||||
|
|||||||
Reference in New Issue
Block a user