diff --git a/core/cmd/gds-controller/main.go b/core/cmd/gds-controller/main.go index aa73e63..07a50f5 100644 --- a/core/cmd/gds-controller/main.go +++ b/core/cmd/gds-controller/main.go @@ -139,7 +139,7 @@ func run(ctx context.Context, arguments []string, stdout io.Writer, stderr io.Wr } service := &controller.Service{ Store: store, Webhook: receiver, Worker: worker, Reconciler: runner, Backup: backups, - Telemetry: telemetryExporter, + Telemetry: telemetryFlusher(telemetryExporter), WebhookPath: runtime.Config.Controller.WebhookPath, WebhookPoll: time.Duration( runtime.Config.Controller.Schedule.WebhookPollMilliseconds, @@ -177,6 +177,19 @@ func openState(ctx context.Context, path string) (*state.Store, error) { } } +// telemetryFlusher hands the exporter to the service's optional sink slot. +// FromEnvironment yields a typed-nil *Exporter when no endpoint is +// configured, and storing that inside the interface field would defeat the +// service's nil guard: the loop would spawn and Flush would panic on a nil +// receiver — measured live as a crash after the first successful +// reconciliation. Absence must stay an untyped nil. +func telemetryFlusher(exporter *telemetry.Exporter) controller.TelemetryFlusher { + if exporter == nil { + return nil + } + return exporter +} + func startupError(writer io.Writer, phase string, err error) int { _, _ = fmt.Fprintf(writer, "gds-controller: %s startup failed (%T)\n", phase, err) return 1 diff --git a/core/cmd/gds-controller/main_test.go b/core/cmd/gds-controller/main_test.go index f71e1b1..237ce48 100644 --- a/core/cmd/gds-controller/main_test.go +++ b/core/cmd/gds-controller/main_test.go @@ -26,3 +26,9 @@ func TestRunRequiresExplicitPrivateRuntime(t *testing.T) { t.Fatalf("stderr=%q", stderr.String()) } } + +func TestTelemetryFlusherKeepsAbsentExporterUntyped(t *testing.T) { + if telemetryFlusher(nil) != nil { + t.Fatal("absent telemetry became a typed nil inside the interface") + } +}