|
| 1 | +import sys |
| 2 | +from typing import Optional |
| 3 | +import logging |
| 4 | +from datetime import datetime |
| 5 | +from logging.handlers import RotatingFileHandler |
| 6 | + |
| 7 | +__all__ = [ |
| 8 | + 'DATE_FORMAT', |
| 9 | + 'LOG_FORMAT', |
| 10 | + 'get_logger', |
| 11 | + 'get_timestamped_log_path', |
| 12 | + 'setup_logging', |
| 13 | +] |
| 14 | + |
| 15 | +# Default format as per the requirements, adding levelname and module name for context. |
| 16 | +LOG_FORMAT = '[%(asctime)s] [%(levelname)s] [%(name)s] %(message)s' |
| 17 | +DATE_FORMAT = '%Y-%m-%d %H:%M:%S' |
| 18 | + |
| 19 | + |
| 20 | +def setup_logging( |
| 21 | + level: int = logging.INFO, |
| 22 | + log_file: Optional[str] = None, |
| 23 | + max_bytes: int = 10 * 1024 * 1024, # 10 MB |
| 24 | + backup_count: int = 5, |
| 25 | +): |
| 26 | + """Configures the root logger for the application with log rotation. |
| 27 | +
|
| 28 | + This sets up handlers for console and optional file logging. |
| 29 | + It uses a standardized format for all log messages and rotates |
| 30 | + log files when they reach a specified size. |
| 31 | +
|
| 32 | + :param level: The minimum logging level to capture (e.g., logging.INFO). |
| 33 | + :param log_file: Optional path to a file for log output. |
| 34 | + :param max_bytes: The maximum size in bytes for a log file before it is rotated. |
| 35 | + :param backup_count: The number of backup log files to keep. |
| 36 | + """ |
| 37 | + formatter = logging.Formatter(LOG_FORMAT, datefmt=DATE_FORMAT) |
| 38 | + |
| 39 | + handlers = [] |
| 40 | + |
| 41 | + # Console handler |
| 42 | + console_handler = logging.StreamHandler(sys.stdout) |
| 43 | + console_handler.setFormatter(formatter) |
| 44 | + handlers.append(console_handler) |
| 45 | + |
| 46 | + # Rotating file handler (if a path is provided) |
| 47 | + if log_file: |
| 48 | + # Use RotatingFileHandler for automatic log rotation. |
| 49 | + file_handler = RotatingFileHandler( |
| 50 | + log_file, maxBytes=max_bytes, backupCount=backup_count |
| 51 | + ) |
| 52 | + file_handler.setFormatter(formatter) |
| 53 | + handlers.append(file_handler) |
| 54 | + |
| 55 | + # The `force=True` argument removes any existing handlers |
| 56 | + # on the root logger, ensuring our configuration is the only one. |
| 57 | + logging.basicConfig(level=level, handlers=handlers, force=True) |
| 58 | + |
| 59 | + # Set up a hook for unhandled exceptions to be logged automatically. |
| 60 | + # This addresses the "Log traceback" requirement. |
| 61 | + def handle_exception(exc_type, exc_value, exc_traceback): |
| 62 | + if issubclass(exc_type, KeyboardInterrupt): |
| 63 | + sys.__excepthook__(exc_type, exc_value, exc_traceback) |
| 64 | + return |
| 65 | + logging.getLogger().critical( |
| 66 | + 'Unhandled exception', exc_info=(exc_type, exc_value, exc_traceback) |
| 67 | + ) |
| 68 | + |
| 69 | + sys.excepthook = handle_exception |
| 70 | + |
| 71 | + |
| 72 | +def get_timestamped_log_path(log_dir: str, app_name: str) -> str: |
| 73 | + """Generates a timestamped log file path. |
| 74 | +
|
| 75 | + :param log_dir: The directory where the log file should be stored. |
| 76 | + :param app_name: The base name for the log file. |
| 77 | + :return: A string representing the full path to the log file. |
| 78 | + """ |
| 79 | + timestamp = datetime.now().strftime('%Y-%m-%d') |
| 80 | + return f'{log_dir}/{app_name}_{timestamp}.log' |
| 81 | + |
| 82 | + |
| 83 | +def get_logger(name: str) -> logging.Logger: |
| 84 | + """Returns a logger instance for the given name. |
| 85 | +
|
| 86 | + This is the primary function that developers will use to get a |
| 87 | + pre-configured logger within their modules. |
| 88 | + """ |
| 89 | + return logging.getLogger(name) |
0 commit comments