{"record":{"id":"dedc8b24f9b9c705","repo":"hashicorp/consul","slug":"logger-must-not-be-nil","errorCode":null,"errorMessage":"logger must not be nil","messagePattern":"logger must not be nil","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/controller/controller.go","lineNumber":157,"sourceCode":"\n// WithCustomWatch adds a new custom watch. Custom watches do not affect the controller cache.\nfunc (ctl *Controller) WithCustomWatch(source *Source, mapper CustomDependencyMapper) *Controller {\n\tif source == nil {\n\t\tpanic(\"source must not be nil\")\n\t}\n\n\tif mapper == nil {\n\t\tpanic(\"mapper must not be nil\")\n\t}\n\n\tctl.customWatches = append(ctl.customWatches, customWatch{source, mapper})\n\treturn ctl\n}\n\n// WithLogger changes the controller's logger.\nfunc (ctl *Controller) WithLogger(logger hclog.Logger) *Controller {\n\tif logger == nil {\n\t\tpanic(\"logger must not be nil\")\n\t}\n\n\tctl.logger = logger\n\treturn ctl\n}\n\n// WithBackoff changes the base and maximum backoff values for the controller's\n// retry rate limiter.\nfunc (ctl *Controller) WithBackoff(base, max time.Duration) *Controller {\n\tctl.baseBackoff = base\n\tctl.maxBackoff = max\n\treturn ctl\n}\n\n// WithPlacement changes where and how many replicas of the controller will run.\n// In the majority of cases, the default placement (one leader elected instance\n// per cluster) is the most appropriate and you shouldn't need to override it.\nfunc (ctl *Controller) WithPlacement(placement Placement) *Controller {","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/hashicorp/consul/blob/2397ff0d763d34f2fe37fe59fde6a7f7fc430a3e/internal/controller/controller.go#L139-L175","documentation":"The Consul controller builder method WithLogger (internal/controller/controller.go:155) panics when its logger argument is nil. NewController does not set a default logger, so the builder chain expects you to supply a valid hclog.Logger. The panic is a fail-fast guard: a nil logger stored on the Controller would otherwise cause nil-pointer dereferences later in the reconcile loop, far from the construction site.","triggerScenarios":"Calling controller.NewController(name, typ).WithLogger(nil); passing an hclog.Logger variable that was declared but never initialized; calling a helper that returns (hclog.Logger, error) and passing its nil result without checking the error.","commonSituations":"Test wiring where the logger was omitted; refactors that move logger creation after controller construction; helper functions that return nil on error and callers that forward the value unchecked.","solutions":["Pass hclog.NewNullLogger() (tests) or hclog.Default() when no real logger is configured","Initialize the hclog.Logger variable (hclog.New(&hclog.LoggerOptions{...})) before building the controller","Check the error of any function that returns a logger before passing it to WithLogger","Derive a sub-logger from an existing one, e.g. parentlogger.Named(\"controller-name\")"],"exampleFix":"// before\nvar logger hclog.Logger // nil\nctl := controller.NewController(\"demo\", typ).WithLogger(logger) // panics\n\n// after\nif logger == nil {\n\tlogger = hclog.NewNullLogger()\n}\nctl := controller.NewController(\"demo\", typ).WithLogger(logger)","handlingStrategy":"validation","validationCode":"if logger == nil {\n\tlogger = hclog.NewNullLogger() // or hclog.Default()\n}\nctl := controller.NewController(name, managedType).WithLogger(logger)","typeGuard":null,"tryCatchPattern":"// Go has no try/catch; wrap construction so the panic becomes an error\nfunc buildController(logger hclog.Logger) (ctl *controller.Controller, err error) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\terr = fmt.Errorf(\"controller construction failed: %v\", r)\n\t\t}\n\t}()\n\tctl = controller.NewController(\"demo\", typ).WithLogger(logger)\n\treturn\n}","preventionTips":["Never let a (hclog.Logger, error) return pair flow into WithLogger unchecked","Standardize on hclog.NewNullLogger() as the test default so nil never appears","Treat all With* builder panics in this package as constructor contracts: validate inputs at the call site"],"tags":["go","consul","controller","panic","nil-check","hclog","builder-pattern"],"backgroundTag":null,"analyzedSha":"2397ff0d763d34f2fe37fe59fde6a7f7fc430a3e","analyzedAt":"2026-08-15T19:19:47.700Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}