From 311da4098e519837407d623fcb477634d822befd Mon Sep 17 00:00:00 2001 From: anti Date: Fri, 24 Apr 2026 01:00:42 -0400 Subject: [PATCH] fix(logging): don't crash the process if the system log can't be opened MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _configure_logging opened InodeAwareRotatingFileHandler against DECNET_SYSTEM_LOGS (default: relative decnet.system.log) without guarding OSError. Under systemd with ProtectSystem=full + ProtectHome=read-only and no writable path baked into the unit, the first import of decnet.config raised OSError and the daemon died before it could even print a useful error — the root-cause log line showed up in journalctl as a stack trace rather than a warning. Wrap the handler attachment in try/except OSError and log a single WARNING via the already-installed stream handler. stderr is always attached, so losing the file handler means operators tail journalctl / docker logs instead — the daemon keeps running. --- decnet/config.py | 40 +++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/decnet/config.py b/decnet/config.py index b0f1e9f0..5fd2af05 100644 --- a/decnet/config.py +++ b/decnet/config.py @@ -82,19 +82,33 @@ def _configure_logging(dev: bool) -> None: _in_pytest = any(k.startswith("PYTEST") for k in os.environ) if not _in_pytest: _log_path = os.environ.get("DECNET_SYSTEM_LOGS", "decnet.system.log") - file_handler = InodeAwareRotatingFileHandler( - _log_path, - mode="a", - maxBytes=10 * 1024 * 1024, # 10 MB - backupCount=5, - encoding="utf-8", - ) - file_handler.setFormatter(fmt) - root.addHandler(file_handler) - # Drop root ownership when invoked via sudo so non-root follow-up - # commands (e.g. `decnet api` after `sudo decnet deploy`) can append. - from decnet.privdrop import chown_to_invoking_user - chown_to_invoking_user(_log_path) + # Never let file-handler attach failure kill the process. The + # stream handler above is already installed, so losing the file + # handler just means 'tail syslog / journalctl instead' — the + # daemon itself must keep running. This path trips most + # commonly under systemd with ProtectSystem=full + ProtectHome= + # read-only when an operator hasn't passed a writable + # DECNET_SYSTEM_LOGS yet. + try: + file_handler = InodeAwareRotatingFileHandler( + _log_path, + mode="a", + maxBytes=10 * 1024 * 1024, # 10 MB + backupCount=5, + encoding="utf-8", + ) + file_handler.setFormatter(fmt) + root.addHandler(file_handler) + # Drop root ownership when invoked via sudo so non-root follow-up + # commands (e.g. `decnet api` after `sudo decnet deploy`) can append. + from decnet.privdrop import chown_to_invoking_user + chown_to_invoking_user(_log_path) + except OSError as exc: + logging.getLogger(__name__).warning( + "could not open %s (%s); continuing with stderr-only logging. " + "Set DECNET_SYSTEM_LOGS to a writable path to silence this.", + _log_path, exc, + ) _dev = os.environ.get("DECNET_DEVELOPER", "").lower() == "true"