ACH-ARKIVO-ImportMedia/logging_config.py

82 lines
3.7 KiB
Python

import logging
from logging.handlers import TimedRotatingFileHandler
import os
from pathlib import Path
from dotenv import load_dotenv
load_dotenv()
# Custom log level (optional). Keep for backward compatibility.
CUSTOM_ERROR_LEVEL = 35
logging.addLevelName(CUSTOM_ERROR_LEVEL, "CUSTOM_ERROR")
def custom_error(self, message, *args, **kwargs):
"""Helper to log at the custom error level."""
if self.isEnabledFor(CUSTOM_ERROR_LEVEL):
self._log(CUSTOM_ERROR_LEVEL, message, args, **kwargs)
# Attach helper to the Logger class so callers can do: logging.getLogger().custom_error(...)
logging.Logger.custom_error = custom_error
def _ensure_dir_for_file(path: str):
"""Ensure the parent directory for `path` exists."""
Path(path).resolve().parent.mkdir(parents=True, exist_ok=True)
def _create_timed_handler(path: str, level=None, when='midnight', interval=1, backupCount=7, fmt=None):
"""
Create and configure a TimedRotatingFileHandler.
Uses the handler's built-in rotation logic which is more robust and easier
to maintain than a custom doRollover implementation.
"""
_ensure_dir_for_file(path)
handler = TimedRotatingFileHandler(path, when=when, interval=interval, backupCount=backupCount, encoding='utf-8')
# Use a readable suffix for rotated files (handler will append this after the filename)
handler.suffix = "%Y%m%d_%H%M%S"
if fmt:
handler.setFormatter(fmt)
if level is not None:
handler.setLevel(level)
return handler
def setup_logging():
"""
Configure logging for the application and return (logger, info_handler, error_handler, warning_handler).
This version uses standard TimedRotatingFileHandler to keep the logic simple and
avoid fragile file-renaming on Windows.
"""
# Select a format depending on environment for easier debugging in dev
if os.getenv('ACH_ENV') == 'development':
log_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s - %(pathname)s:%(lineno)d')
elif os.getenv('ACH_ENV') == 'production':
log_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
else:
log_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s')
error_log_path = os.getenv('ERROR_LOG_FILE_PATH', "./logs/ACH_media_import_errors.log")
warning_log_path = os.getenv('WARNING_LOG_FILE_PATH', "./logs/ACH_media_import_warnings.log")
info_log_path = os.getenv('INFO_LOG_FILE_PATH', "./logs/ACH_media_import_info.log")
# Create three handlers: info (all), warning (warning+), error (error+)
info_handler = _create_timed_handler(info_log_path, level=logging.INFO, fmt=log_formatter, backupCount=int(os.getenv('LOG_BACKUP_COUNT', '7')))
warning_handler = _create_timed_handler(warning_log_path, level=logging.WARNING, fmt=log_formatter, backupCount=int(os.getenv('LOG_BACKUP_COUNT', '7')))
error_handler = _create_timed_handler(error_log_path, level=logging.ERROR, fmt=log_formatter, backupCount=int(os.getenv('LOG_BACKUP_COUNT', '7')))
console_handler = logging.StreamHandler()
console_handler.setFormatter(log_formatter)
# Configure root logger explicitly
root_logger = logging.getLogger()
root_logger.setLevel(logging.INFO)
# Clear existing handlers to avoid duplicate logs when unit tests or reloads occur
root_logger.handlers = []
root_logger.addHandler(info_handler)
root_logger.addHandler(warning_handler)
root_logger.addHandler(error_handler)
root_logger.addHandler(console_handler)
# Return the root logger and handlers (so callers can do manual rollovers if they truly need to)
return root_logger, info_handler, error_handler, warning_handler