From 1c9b6c1ca9f1bc885c4e669be0781f764c407735 Mon Sep 17 00:00:00 2001 From: Andreas Wrede Date: Sat, 25 Apr 2026 16:29:54 +0200 Subject: [PATCH] fix: reconfigure logging to syslog after daemonize() instead of no-op basicConfig After daemonize() redirects stderr to /dev/null, the existing StreamHandler writes to /dev/null. logging.basicConfig() is a no-op when handlers are already configured, so log messages are silently lost. Replace the daemon block to: 1. Call daemonize() first 2. Explicitly remove existing handlers (pointing to /dev/null) 3. Add SysLogHandler pointing to /dev/log with fallback to UDP localhost:514 4. Log startup message to the new syslog handler Removes redundant syslog.openlog() call which is no longer needed. Co-Authored-By: Claude Sonnet 4.6 --- hbd/client/main.py | 46 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/hbd/client/main.py b/hbd/client/main.py index 4dd85ec..3101ef1 100644 --- a/hbd/client/main.py +++ b/hbd/client/main.py @@ -586,6 +586,41 @@ def daemonize( os.dup2(se.fileno(), sys.stderr.fileno()) +def _reconfigure_logging_for_daemon(log_level: int) -> None: + """Replace StreamHandlers (now writing to /dev/null) with a SysLogHandler.""" + from logging.handlers import SysLogHandler + + root = logging.getLogger() + for handler in root.handlers[:]: + root.removeHandler(handler) + handler.close() + + try: + syslog_handler = SysLogHandler( + address="/dev/log", + facility=SysLogHandler.LOG_DAEMON, + ) + except OSError: + syslog_handler = SysLogHandler( + address=("localhost", 514), + facility=SysLogHandler.LOG_DAEMON, + ) + # Attach the fallback first so the warning reaches syslog + syslog_handler.setFormatter( + logging.Formatter("hbc[%(process)d]: %(name)s %(levelname)s: %(message)s") + ) + root.addHandler(syslog_handler) + root.setLevel(log_level) + logging.warning("/dev/log not found, using syslog UDP localhost:514") + return + + syslog_handler.setFormatter( + logging.Formatter("hbc[%(process)d]: %(name)s %(levelname)s: %(message)s") + ) + root.addHandler(syslog_handler) + root.setLevel(log_level) + + def build_parser(): """Build argument parser.""" parser = argparse.ArgumentParser( @@ -663,16 +698,9 @@ def main(argv=None): # Daemonize if requested if args.daemon: print("Daemonizing...") - import syslog - syslog.openlog("hbc", syslog.LOG_PID, syslog.LOG_DAEMON) - syslog.syslog(syslog.LOG_INFO, f"Starting heartbeat to {', '.join(args.hosts)}") daemonize() - - # Reconfigure logging for syslog - logging.basicConfig( - level=log_level, - format="hbc[%(process)d]: %(name)s %(levelname)s: %(message)s" - ) + _reconfigure_logging_for_daemon(log_level) + logging.info(f"hbc starting, sending heartbeat to {', '.join(args.hosts)}") # Run async main try: