From 37c550e0a3f6df3b51692af500ccc0e97a9ddec6 Mon Sep 17 00:00:00 2001 From: Uzair Hameed Date: Tue, 11 Aug 2026 09:48:31 +0500 Subject: [PATCH] fix: treat missing/disabled endpoint as no-op tracing, not an error Signed-off-by: Uzair Hameed --- tracing/tracing.go | 16 +++++--- tracing/tracing_test.go | 89 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 94 insertions(+), 11 deletions(-) diff --git a/tracing/tracing.go b/tracing/tracing.go index 9dc7914ee..996d97fa9 100644 --- a/tracing/tracing.go +++ b/tracing/tracing.go @@ -25,12 +25,16 @@ type Config struct { Endpoint string `yaml:"endpoint" json:"endpoint"` // Insecure determines whether to use an insecure connection (no TLS) Insecure bool `yaml:"insecure" json:"insecure"` + // Enabled explicitly controls whether tracing is active. When nil, + // tracing is enabled only if Endpoint is set. When explicitly false, + // tracing is always disabled regardless of Endpoint. + Enabled *bool `yaml:"enabled" json:"enabled"` } func InitTracerFromYamlConfig(ctx context.Context, config string) (*sdktrace.TracerProvider, error) { cfg := Config{} - err := yaml.Unmarshal([]byte(config),&cfg) + err := yaml.Unmarshal([]byte(config), &cfg) if err != nil { return nil, fmt.Errorf("failed to parse tracing config: %w", err) @@ -42,14 +46,16 @@ func InitTracerFromYamlConfig(ctx context.Context, config string) (*sdktrace.Tra // InitTracer initializes and configures the global OpenTelemetry trace provider // It sets up OTLP gRPC exporter, resource attributes, and W3C trace context propagation func InitTracer(ctx context.Context, cfg Config) (*sdktrace.TracerProvider, error) { + + // Tracing is a no-op if explicitly disabled, or if no endpoint is + // configured — this is not an error, just an intentional off state. + if (cfg.Enabled != nil && !*cfg.Enabled) || cfg.Endpoint == "" { + return nil, nil + } // Validate configuration if cfg.ServiceName == "" { return nil, fmt.Errorf("service name is required") } - if cfg.Endpoint == "" { - return nil, fmt.Errorf("endpoint is required") - } - // Configure OTLP exporter options opts := []otlptracegrpc.Option{ otlptracegrpc.WithEndpoint(cfg.Endpoint), diff --git a/tracing/tracing_test.go b/tracing/tracing_test.go index b4ef7fcda..df406a66d 100644 --- a/tracing/tracing_test.go +++ b/tracing/tracing_test.go @@ -14,10 +14,14 @@ import ( func TestInitTracer(t *testing.T) { // Note: These tests validate configuration without attempting real connections + falseVal := false + trueVal := true + tests := []struct { - name string - config Config - wantErr bool + name string + config Config + wantErr bool + wantNilProv bool }{ { name: "missing service name", @@ -29,29 +33,102 @@ func TestInitTracer(t *testing.T) { wantErr: true, }, { - name: "missing endpoint", + name: "missing endpoint is a no-op, not an error", config: Config{ ServiceName: "test-service", ServiceVersion: "1.0.0", Insecure: true, }, - wantErr: true, + wantErr: false, + wantNilProv: true, + }, + { + name: "explicitly disabled is a no-op, even with endpoint set", + config: Config{ + ServiceName: "test-service", + Endpoint: "localhost:4317", + Enabled: &falseVal, + }, + wantErr: false, + wantNilProv: true, + }, + { + name: "explicitly enabled with empty endpoint is still a no-op", + config: Config{ + ServiceName: "test-service", + Enabled: &trueVal, + }, + wantErr: false, + wantNilProv: true, + }, + { + name: "enabled with valid endpoint returns a real provider", + config: Config{ + ServiceName: "test-service", + Endpoint: "localhost:4317", + Insecure: true, + Enabled: &trueVal, + }, + wantErr: false, + wantNilProv: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + prevProvider := otel.GetTracerProvider() + prevPropagator := otel.GetTextMapPropagator() + t.Cleanup(func() { + otel.SetTracerProvider(prevProvider) + otel.SetTextMapPropagator(prevPropagator) + }) ctx := context.Background() - _, err := InitTracer(ctx, tt.config) + tp, err := InitTracer(ctx, tt.config) if (err != nil) != tt.wantErr { t.Errorf("InitTracer() error = %v, wantErr %v", err, tt.wantErr) return } + if !tt.wantErr { + if tt.wantNilProv && tp != nil { + t.Errorf("InitTracer() expected nil provider, got %v", tp) + } + if !tt.wantNilProv && tp == nil { + t.Errorf("InitTracer() expected non-nil provider, got nil") + } + } + if tp != nil { + _ = tp.Shutdown(ctx) + } }) } } +func TestInitTracerFromYamlConfig_Disabled(t *testing.T) { + prevProvider := otel.GetTracerProvider() + prevPropagator := otel.GetTextMapPropagator() + t.Cleanup(func() { + otel.SetTracerProvider(prevProvider) + otel.SetTextMapPropagator(prevPropagator) + }) + + yamlConfig := ` +service_name: test-service +endpoint: localhost:4317 +enabled: false +` + ctx := context.Background() + tp, err := InitTracerFromYamlConfig(ctx, yamlConfig) + + if err != nil { + t.Errorf("InitTracerFromYamlConfig() unexpected error = %v", err) + } + if tp != nil { + t.Errorf("InitTracerFromYamlConfig() expected nil provider when enabled: false, got %v", tp) + _ = tp.Shutdown(ctx) + } +} + func TestNewResource(t *testing.T) { tests := []struct { name string