fix(logging): don't crash the process if the system log can't be opened

_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.
This commit is contained in:
2026-04-24 01:00:42 -04:00
parent d4b714dc39
commit 311da4098e

View File

@@ -82,19 +82,33 @@ def _configure_logging(dev: bool) -> None:
_in_pytest = any(k.startswith("PYTEST") for k in os.environ) _in_pytest = any(k.startswith("PYTEST") for k in os.environ)
if not _in_pytest: if not _in_pytest:
_log_path = os.environ.get("DECNET_SYSTEM_LOGS", "decnet.system.log") _log_path = os.environ.get("DECNET_SYSTEM_LOGS", "decnet.system.log")
file_handler = InodeAwareRotatingFileHandler( # Never let file-handler attach failure kill the process. The
_log_path, # stream handler above is already installed, so losing the file
mode="a", # handler just means 'tail syslog / journalctl instead' — the
maxBytes=10 * 1024 * 1024, # 10 MB # daemon itself must keep running. This path trips most
backupCount=5, # commonly under systemd with ProtectSystem=full + ProtectHome=
encoding="utf-8", # read-only when an operator hasn't passed a writable
) # DECNET_SYSTEM_LOGS yet.
file_handler.setFormatter(fmt) try:
root.addHandler(file_handler) file_handler = InodeAwareRotatingFileHandler(
# Drop root ownership when invoked via sudo so non-root follow-up _log_path,
# commands (e.g. `decnet api` after `sudo decnet deploy`) can append. mode="a",
from decnet.privdrop import chown_to_invoking_user maxBytes=10 * 1024 * 1024, # 10 MB
chown_to_invoking_user(_log_path) 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" _dev = os.environ.get("DECNET_DEVELOPER", "").lower() == "true"