"""Module for logging configuration."""
import copy
import json
import logging.config
from logging import LogRecord
from typing import Any, Optional
from command_line_assistant.config import Config
#: Default formatter string for systemd/terminal
DEFAULT_FORMATTER: str = (
"[%(asctime)s] [%(filename)s:%(lineno)d] %(levelname)s: %(message)s"
)
#: Default date formatter string for systemd/terminal
DEFAULT_DATE_FORMATTER: str = "%m/%d/%Y %I:%M:%S %p"
#: Define the dictionary configuration for the logger instance
LOGGING_CONFIG_DICTIONARY = {
"version": 1,
"disable_existing_loggers": False,
"level": "INFO",
"formatters": {
"systemd": {
"format": DEFAULT_FORMATTER,
"datefmt": DEFAULT_DATE_FORMATTER,
},
"terminal": {
# Include a record separator prefix to allow parsing the logs easily
"format": f"\x1f{DEFAULT_FORMATTER}",
"datefmt": DEFAULT_DATE_FORMATTER,
},
"audit": {
"()": "command_line_assistant.logger.AuditFormatter",
"datefmt": DEFAULT_DATE_FORMATTER,
"format": "[%(asctime)s] [%(filename)s:%(lineno)d] %(levelname)s: %(message)s",
},
},
"filters": {
"audit_only": {
"()": "command_line_assistant.logger.AuditFilter",
},
"non_audit_only": {
"()": "command_line_assistant.logger.NonAuditFilter",
},
},
"handlers": {
"terminal": {
"class": "logging.StreamHandler",
"formatter": "terminal",
"stream": "ext://sys.stdout",
"filters": ["non_audit_only"],
},
"systemd": {
"class": "logging.StreamHandler",
"formatter": "systemd",
"stream": "ext://sys.stdout",
"filters": ["non_audit_only"],
},
"audit": {
"class": "logging.StreamHandler",
"formatter": "audit",
"stream": "ext://sys.stdout",
"filters": ["audit_only"],
},
},
"loggers": {
# Root logger
"root": {"handlers": [], "level": "INFO"},
},
}
#: Set of keys to skip during auditting. If any of those needs to be in the
#: audit log, simply remove them from this list.
EXTRAS_TO_SKIP = (
"args",
"asctime",
"created",
"exc_info",
"exc_text",
"filename",
"funcName",
"levelname",
"levelno",
"lineno",
"taskName",
"module",
"server",
"thread",
"process",
"processName",
"msecs",
"msg",
"name",
"pathname",
"relativeCreated",
"stack_info",
"threadName",
"audit",
"user_id",
)
[docs]
class AuditFilter(logging.Filter):
"""Filter to separate audit logs from regular logs."""
[docs]
def filter(self, record: LogRecord) -> bool:
"""Filter records based on the presence of audit attribute.
Arguments:
record (LogRecord): The log record to check
Returns:
bool: True if the record should be processed, False otherwise
"""
return bool(getattr(record, "audit", False))
[docs]
class NonAuditFilter(logging.Filter):
"""Filter to separate regular logs from audit logs."""
[docs]
def filter(self, record: LogRecord) -> bool:
"""Filter records based on the absence of audit attribute.
Arguments:
record (LogRecord): The log record to check
Returns:
bool: True if the record should be processed, False otherwise
"""
return not bool(getattr(record, "audit", False))
[docs]
def _setup_logging(logging_level: str, handlers: list[str]) -> None:
"""Internal method to handle logging configuration and initialization.
Arguments:
logging_level (str): The mininaml level to enable
handlers (list[str]): A list of handlers to add to the root loger.
"""
logging_configuration: dict = copy.deepcopy(LOGGING_CONFIG_DICTIONARY)
logging_configuration["level"] = logging_level
logging_configuration["loggers"]["root"]["level"] = logging_level
logging_configuration["loggers"]["root"]["handlers"].extend(handlers)
logging.config.dictConfig(logging_configuration)
[docs]
def setup_daemon_logging(config: Config) -> None:
"""Setup basic logging functionality.
Note:
This is intended to be called by the daemon to initialize their logging
routine.
Arguments:
config (Config): Instance of a config class.
"""
custom_handlers = ["systemd"]
# Add audit logging in case it is enabledc
if config.logging.audit.enabled:
custom_handlers.append("audit")
_setup_logging(config.logging.level, custom_handlers)
[docs]
def setup_client_logging() -> None:
"""Setup basic logging functionality.
Note:
This is intended to be called by the client to initialize their logging
routine.
"""
_setup_logging(logging_level="DEBUG", handlers=["terminal"])