From 212204d177905016ead419f9b375096daacaa10c Mon Sep 17 00:00:00 2001 From: tqn <927455605@qq.com> Date: Sun, 10 Aug 2025 17:49:48 +0800 Subject: [PATCH 1/2] Add log rotation --- api/logging_config.py | 84 +++++++++++++++++++++++++++++-------------- 1 file changed, 57 insertions(+), 27 deletions(-) diff --git a/api/logging_config.py b/api/logging_config.py index d3561a5b9..5faef556f 100644 --- a/api/logging_config.py +++ b/api/logging_config.py @@ -1,16 +1,26 @@ import logging import os from pathlib import Path +from logging.handlers import RotatingFileHandler + class IgnoreLogChangeDetectedFilter(logging.Filter): def filter(self, record: logging.LogRecord): return "Detected file change in" not in record.getMessage() + def setup_logging(format: str = None): """ - Configure logging for the application. - Reads LOG_LEVEL and LOG_FILE_PATH from environment (defaults: INFO, logs/application.log). - Ensures log directory exists, and configures both file and console handlers. + Configure logging for the application with log rotation. + + Environment variables: + LOG_LEVEL: Log level (default: INFO) + LOG_FILE_PATH: Path to log file (default: logs/application.log) + LOG_MAX_SIZE: Max size in bytes before rotating (default: 10MB) + LOG_BACKUP_COUNT: Number of backup files to keep (default: 5) + + Ensures log directory exists, prevents path traversal, and configures + both rotating file and console handlers. """ # Determine log directory and default file path base_dir = Path(__file__).parent @@ -18,37 +28,57 @@ def setup_logging(format: str = None): log_dir.mkdir(parents=True, exist_ok=True) default_log_file = log_dir / "application.log" - # Get log level and file path from environment + # Get log level from environment log_level_str = os.environ.get("LOG_LEVEL", "INFO").upper() log_level = getattr(logging, log_level_str, logging.INFO) - log_file_path = Path(os.environ.get( - "LOG_FILE_PATH", str(default_log_file))) - # ensure log_file_path is within the project's logs directory to prevent path traversal + # Get log file path + log_file_path = Path(os.environ.get("LOG_FILE_PATH", str(default_log_file))) + + # Secure path check: must be inside logs/ directory log_dir_resolved = log_dir.resolve() resolved_path = log_file_path.resolve() if not str(resolved_path).startswith(str(log_dir_resolved) + os.sep): - raise ValueError( - f"LOG_FILE_PATH '{log_file_path}' is outside the trusted log directory '{log_dir_resolved}'" - ) - # Ensure parent dirs exist for the log file + raise ValueError(f"LOG_FILE_PATH '{log_file_path}' is outside the trusted log directory '{log_dir_resolved}'") + + # Ensure parent directories exist resolved_path.parent.mkdir(parents=True, exist_ok=True) - # Configure logging handlers and format - logging.basicConfig( - level=log_level, - format = format or "%(asctime)s - %(levelname)s - %(name)s - %(filename)s:%(lineno)d - %(message)s", - handlers=[ - logging.FileHandler(resolved_path), - logging.StreamHandler() - ], - force=True - ) - - # Ignore log file's change detection - for handler in logging.getLogger().handlers: - handler.addFilter(IgnoreLogChangeDetectedFilter()) + # Get max log file size (default: 10MB) + try: + max_bytes = int(os.environ.get("LOG_MAX_SIZE" * 1024 * 1024, 10 * 1024 * 1024)) # 10MB default + except (TypeError, ValueError): + max_bytes = 10 * 1024 * 1024 # fallback to 10MB on error - # Initial debug message to confirm configuration + # Get backup count (default: 5) + try: + backup_count = int(os.environ.get("LOG_BACKUP_COUNT", 5)) + except (TypeError, ValueError): + backup_count = 5 + + # Configure format + log_format = format or "%(asctime)s - %(levelname)s - %(name)s - %(filename)s:%(lineno)d - %(message)s" + + # Create handlers + file_handler = RotatingFileHandler(resolved_path, maxBytes=max_bytes, backupCount=backup_count, encoding="utf-8") + console_handler = logging.StreamHandler() + + # Set format for both handlers + formatter = logging.Formatter(log_format) + file_handler.setFormatter(formatter) + console_handler.setFormatter(formatter) + + # Add filter to suppress "Detected file change" messages + file_handler.addFilter(IgnoreLogChangeDetectedFilter()) + console_handler.addFilter(IgnoreLogChangeDetectedFilter()) + + # Apply logging configuration + logging.basicConfig(level=log_level, handlers=[file_handler, console_handler], force=True) + + # Log configuration info logger = logging.getLogger(__name__) - logger.debug(f"Log level set to {log_level_str}, log file: {resolved_path}") + logger.debug( + f"Logging configured: level={log_level_str}, " + f"file={resolved_path}, max_size={max_bytes} bytes, " + f"backup_count={backup_count}" + ) From b07d8e63133ee6e057bf1b608e320a191f19d294 Mon Sep 17 00:00:00 2001 From: tqn <927455605@qq.com> Date: Sun, 10 Aug 2025 18:18:17 +0800 Subject: [PATCH 2/2] Add log rotation --- api/logging_config.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/api/logging_config.py b/api/logging_config.py index 5faef556f..d2726c626 100644 --- a/api/logging_config.py +++ b/api/logging_config.py @@ -16,7 +16,7 @@ def setup_logging(format: str = None): Environment variables: LOG_LEVEL: Log level (default: INFO) LOG_FILE_PATH: Path to log file (default: logs/application.log) - LOG_MAX_SIZE: Max size in bytes before rotating (default: 10MB) + LOG_MAX_SIZE: Max size in MB before rotating (default: 10MB) LOG_BACKUP_COUNT: Number of backup files to keep (default: 5) Ensures log directory exists, prevents path traversal, and configures @@ -46,14 +46,15 @@ def setup_logging(format: str = None): # Get max log file size (default: 10MB) try: - max_bytes = int(os.environ.get("LOG_MAX_SIZE" * 1024 * 1024, 10 * 1024 * 1024)) # 10MB default + max_mb = int(os.environ.get("LOG_MAX_SIZE", 10)) # 10MB default + max_bytes = max_mb * 1024 * 1024 except (TypeError, ValueError): max_bytes = 10 * 1024 * 1024 # fallback to 10MB on error # Get backup count (default: 5) try: backup_count = int(os.environ.get("LOG_BACKUP_COUNT", 5)) - except (TypeError, ValueError): + except ValueError: backup_count = 5 # Configure format