From 69d9a2d50ef30b3204702bd2b47f4db34d927ac1 Mon Sep 17 00:00:00 2001 From: ZZiigguurraatt <187441685+ZZiigguurraatt@users.noreply.github.com> Date: Mon, 29 Jun 2026 14:36:42 -0400 Subject: [PATCH] collectors: fix nil Logger panic in collector startup NewChannelsCollector launched its cache-refresh goroutine at construction time, but the package-global Logger was only initialized later in PrometheusExporter.Start via initLogRotator. If refreshClosedChannelsCache failed before Start ran, the goroutine dereferenced a nil Logger and panicked. Fix the initialization ordering: move initLogRotator to the top of NewPrometheusExporter so logging is ready before any collector is constructed and before any collector goroutine can run. The constructor now returns an error; a logging init failure is fatal and surfaces on stderr so it stays detectable instead of being silently swallowed. Fixes #129. --- collectors/channels_collector.go | 5 ++++- collectors/prometheus.go | 25 ++++++++++++++----------- lndmon.go | 5 ++++- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/collectors/channels_collector.go b/collectors/channels_collector.go index db56032..056fa5c 100644 --- a/collectors/channels_collector.go +++ b/collectors/channels_collector.go @@ -194,7 +194,10 @@ func NewChannelsCollector(lnd lndclient.LightningClient, errChan chan<- error, quit: quitChan, } - // Start a ticker to update the cache once per 10m + // Start a ticker to update the cache once per 10m. This is safe to + // launch here because NewPrometheusExporter initializes logging before + // constructing any collector, so the package-global Logger is ready by + // the time this goroutine can log on failure. go func() { ticker := time.NewTicker(cacheRefreshInterval) defer ticker.Stop() diff --git a/collectors/prometheus.go b/collectors/prometheus.go index 42be6ad..c01fc61 100644 --- a/collectors/prometheus.go +++ b/collectors/prometheus.go @@ -94,7 +94,19 @@ func DefaultConfig() *PrometheusConfig { // the address to listen for Prometheus on and an lnd gRPC client. func NewPrometheusExporter(cfg *PrometheusConfig, lnd *lndclient.LndServices, monitoringCfg *MonitoringConfig, - quitChan chan struct{}) *PrometheusExporter { + quitChan chan struct{}) (*PrometheusExporter, error) { + + // Initialize logging before constructing any collectors. Some collectors + // launch background goroutines that log on failure, so the package-global + // Logger must be ready beforehand. A failure here is fatal: we return the + // error so lndmon exits instead of running with collectors that would + // dereference a nil Logger. + if err := initLogRotator( + filepath.Join(cfg.LogDir, defaultLogFilename), + defaultLogFileSize, defaultMaxLogFile, + ); err != nil { + return nil, err + } // We have six collectors and a htlc monitor running, so we buffer our // error channel by 8 so that we do not need to consume all errors from @@ -145,21 +157,12 @@ func NewPrometheusExporter(cfg *PrometheusConfig, lnd *lndclient.LndServices, htlcMonitor: htlcMonitor, paymentsMonitor: paymentsMonitor, errChan: errChan, - } + }, nil } // Start registers all relevant metrics with the Prometheus library, then // launches the HTTP server that Prometheus will hit to scrape our metrics. func (p *PrometheusExporter) Start() error { - err := initLogRotator( - filepath.Join(p.cfg.LogDir, defaultLogFilename), - defaultLogFileSize, - defaultMaxLogFile, - ) - if err != nil { - return err - } - Logger.Info("Starting Prometheus exporter...") if p.lnd == nil { return fmt.Errorf("cannot start PrometheusExporter without " + diff --git a/lndmon.go b/lndmon.go index 44e5bae..5e69a1a 100755 --- a/lndmon.go +++ b/lndmon.go @@ -78,9 +78,12 @@ func start() error { // Start our Prometheus exporter. This exporter spawns a goroutine // that pulls metrics from our lnd client on a set interval. - exporter := collectors.NewPrometheusExporter( + exporter, err := collectors.NewPrometheusExporter( cfg.Prometheus, &lnd.LndServices, &monitoringCfg, quit, ) + if err != nil { + return err + } if err := exporter.Start(); err != nil { return err }