From e67624452ec1822327a05079c49b9ff83ae344f2 Mon Sep 17 00:00:00 2001 From: anti Date: Wed, 15 Apr 2026 16:23:28 -0400 Subject: [PATCH] feat: centralize microservice logging to DECNET_SYSTEM_LOGS (default: decnet.system.log) --- decnet/config.py | 35 ++++++++++++++++++++++++++++++----- decnet/env.py | 10 ++++++++++ 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/decnet/config.py b/decnet/config.py index 80c7e38..a136a56 100644 --- a/decnet/config.py +++ b/decnet/config.py @@ -56,16 +56,41 @@ class Rfc5424Formatter(logging.Formatter): def _configure_logging(dev: bool) -> None: - """Install the RFC 5424 handler on the root logger (idempotent).""" + """Install RFC 5424 handlers on the root logger (idempotent). + + Always adds a StreamHandler (stderr). Also adds a RotatingFileHandler + writing to DECNET_SYSTEM_LOGS (default: decnet.system.log in $PWD) so + all microservice daemons โ€” which redirect stderr to /dev/null โ€” still + produce readable logs. File handler is skipped under pytest. + """ + import logging.handlers as _lh + root = logging.getLogger() - # Avoid adding duplicate handlers on re-import (e.g. during testing) + # Guard: if our StreamHandler is already installed, all handlers are set. if any(isinstance(h, logging.StreamHandler) and isinstance(h.formatter, Rfc5424Formatter) for h in root.handlers): return - handler = logging.StreamHandler() - handler.setFormatter(Rfc5424Formatter()) + + fmt = Rfc5424Formatter() root.setLevel(logging.DEBUG if dev else logging.INFO) - root.addHandler(handler) + + stream_handler = logging.StreamHandler() + stream_handler.setFormatter(fmt) + root.addHandler(stream_handler) + + # Skip the file handler during pytest runs to avoid polluting the test cwd. + _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 = _lh.RotatingFileHandler( + _log_path, + mode="a", + maxBytes=10 * 1024 * 1024, # 10 MB + backupCount=5, + encoding="utf-8", + ) + file_handler.setFormatter(fmt) + root.addHandler(file_handler) _dev = os.environ.get("DECNET_DEVELOPER", "").lower() == "true" diff --git a/decnet/env.py b/decnet/env.py index 8afa5c2..3b30f9b 100644 --- a/decnet/env.py +++ b/decnet/env.py @@ -40,9 +40,19 @@ def _require_env(name: str) -> str: f"Environment variable '{name}' is set to an insecure default ('{value}'). " f"Choose a strong, unique value before starting DECNET." ) + if name == "DECNET_JWT_SECRET" and len(value) < 32: + _developer = os.environ.get("DECNET_DEVELOPER", "False").lower() == "true" + if not _developer: + raise ValueError( + f"DECNET_JWT_SECRET is too short ({len(value)} bytes). " + f"Use at least 32 characters to satisfy HS256 requirements (RFC 7518 ยง3.2)." + ) return value +# System logging โ€” all microservice daemons append here. +DECNET_SYSTEM_LOGS: str = os.environ.get("DECNET_SYSTEM_LOGS", "decnet.system.log") + # API Options DECNET_API_HOST: str = os.environ.get("DECNET_API_HOST", "0.0.0.0") # nosec B104 DECNET_API_PORT: int = _port("DECNET_API_PORT", 8000)