"""Structured logging with syslog support.""" import json import logging import sys from logging.handlers import SysLogHandler from guarddog_nexus.config import config from guarddog_nexus.constants import APP_PACKAGE class JsonFormatter(logging.Formatter): def format(self, record: logging.LogRecord) -> str: payload = { "timestamp": self.formatTime(record, self.datefmt), "level": record.levelname, "logger": record.name, "message": record.getMessage(), } if record.exc_info and record.exc_info[1]: payload["exception"] = str(record.exc_info[1]) return json.dumps(payload, ensure_ascii=False) def _resolve_facility(value: str) -> int: """Resolve a facility name string to a SysLogHandler constant.""" mapping = { "local0": SysLogHandler.LOG_LOCAL0, "local1": SysLogHandler.LOG_LOCAL1, "local2": SysLogHandler.LOG_LOCAL2, "local3": SysLogHandler.LOG_LOCAL3, "local4": SysLogHandler.LOG_LOCAL4, "local5": SysLogHandler.LOG_LOCAL5, "local6": SysLogHandler.LOG_LOCAL6, "local7": SysLogHandler.LOG_LOCAL7, } return mapping.get(value.lower(), SysLogHandler.LOG_LOCAL0) def setup_logging() -> logging.Logger: logger = logging.getLogger(APP_PACKAGE) logger.setLevel(config.log_level.upper()) stdout_handler = logging.StreamHandler(sys.stdout) stdout_handler.setFormatter(JsonFormatter()) logger.addHandler(stdout_handler) if config.log_syslog_host: facility = _resolve_facility(config.log_syslog_facility) syslog_handler = SysLogHandler( address=(config.log_syslog_host, config.log_syslog_port), facility=facility, ) syslog_handler.setFormatter(JsonFormatter()) logger.addHandler(syslog_handler) return logger log = setup_logging()