Mirrors the inode-check fix from 935a9a5 (collector worker) for the
stdlib-handler-based log paths. Both decnet.system.log (config.py) and
decnet.log (logging/file_handler.py) now use a subclass that stats the
target path before each emit and reopens on inode/device mismatch —
matching the behavior of stdlib WatchedFileHandler while preserving
size-based rotation.
Previously: rm decnet.system.log → handler kept writing to the orphaned
inode until maxBytes triggered; all lines between were lost.
73 lines
2.1 KiB
Python
73 lines
2.1 KiB
Python
"""
|
|
Rotating file handler for DECNET syslog output.
|
|
|
|
Writes RFC 5424 syslog lines to a local file.
|
|
Path is controlled by the DECNET_LOG_FILE environment variable
|
|
(default: /var/log/decnet/decnet.log).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import logging.handlers
|
|
import os
|
|
from pathlib import Path
|
|
|
|
from decnet.logging.inode_aware_handler import InodeAwareRotatingFileHandler
|
|
from decnet.privdrop import chown_to_invoking_user, chown_tree_to_invoking_user
|
|
from decnet.telemetry import traced as _traced
|
|
|
|
_LOG_FILE_ENV = "DECNET_LOG_FILE"
|
|
_DEFAULT_LOG_FILE = "/var/log/decnet/decnet.log"
|
|
_MAX_BYTES = 10 * 1024 * 1024 # 10 MB
|
|
_BACKUP_COUNT = 5
|
|
|
|
_handler: InodeAwareRotatingFileHandler | None = None
|
|
_logger: logging.Logger | None = None
|
|
|
|
|
|
@_traced("logging.init_file_handler")
|
|
def _init_file_handler() -> logging.Logger:
|
|
"""One-time initialisation of the rotating file handler."""
|
|
global _handler, _logger
|
|
|
|
log_path = Path(os.environ.get(_LOG_FILE_ENV, _DEFAULT_LOG_FILE))
|
|
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
# When running under sudo, hand the parent dir back to the invoking user
|
|
# so a subsequent non-root `decnet api` can also write to it.
|
|
chown_tree_to_invoking_user(log_path.parent)
|
|
|
|
_handler = InodeAwareRotatingFileHandler(
|
|
log_path,
|
|
maxBytes=_MAX_BYTES,
|
|
backupCount=_BACKUP_COUNT,
|
|
encoding="utf-8",
|
|
)
|
|
chown_to_invoking_user(log_path)
|
|
_handler.setFormatter(logging.Formatter("%(message)s"))
|
|
|
|
_logger = logging.getLogger("decnet.syslog")
|
|
_logger.setLevel(logging.DEBUG)
|
|
_logger.propagate = False
|
|
_logger.addHandler(_handler)
|
|
|
|
return _logger
|
|
|
|
|
|
def _get_logger() -> logging.Logger:
|
|
if _logger is not None:
|
|
return _logger
|
|
return _init_file_handler()
|
|
|
|
|
|
def write_syslog(line: str) -> None:
|
|
"""Write a single RFC 5424 syslog line to the rotating log file."""
|
|
try:
|
|
_get_logger().info(line)
|
|
except Exception: # nosec B110
|
|
pass
|
|
|
|
def get_log_path() -> Path:
|
|
"""Return the configured log file path (for tests/inspection)."""
|
|
return Path(os.environ.get(_LOG_FILE_ENV, _DEFAULT_LOG_FILE))
|