feat: centralize microservice logging to DECNET_SYSTEM_LOGS (default: decnet.system.log)

This commit is contained in:
2026-04-15 16:23:28 -04:00
parent e05b632e56
commit e67624452e
2 changed files with 40 additions and 5 deletions

View File

@@ -56,16 +56,41 @@ class Rfc5424Formatter(logging.Formatter):
def _configure_logging(dev: bool) -> None: 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() 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) if any(isinstance(h, logging.StreamHandler) and isinstance(h.formatter, Rfc5424Formatter)
for h in root.handlers): for h in root.handlers):
return return
handler = logging.StreamHandler()
handler.setFormatter(Rfc5424Formatter()) fmt = Rfc5424Formatter()
root.setLevel(logging.DEBUG if dev else logging.INFO) 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" _dev = os.environ.get("DECNET_DEVELOPER", "").lower() == "true"

View File

@@ -40,9 +40,19 @@ def _require_env(name: str) -> str:
f"Environment variable '{name}' is set to an insecure default ('{value}'). " f"Environment variable '{name}' is set to an insecure default ('{value}'). "
f"Choose a strong, unique value before starting DECNET." 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 return value
# System logging — all microservice daemons append here.
DECNET_SYSTEM_LOGS: str = os.environ.get("DECNET_SYSTEM_LOGS", "decnet.system.log")
# API Options # API Options
DECNET_API_HOST: str = os.environ.get("DECNET_API_HOST", "0.0.0.0") # nosec B104 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) DECNET_API_PORT: int = _port("DECNET_API_PORT", 8000)