82 lines
3.7 KiB
Python
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 |