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 MB before rotating (default: 10MB) LOG_BACKUP_COUNT
(format: str = None)
| 10 | |
| 11 | |
| 12 | def setup_logging(format: str = None): |
| 13 | """ |
| 14 | Configure logging for the application with log rotation. |
| 15 | |
| 16 | Environment variables: |
| 17 | LOG_LEVEL: Log level (default: INFO) |
| 18 | LOG_FILE_PATH: Path to log file (default: logs/application.log) |
| 19 | LOG_MAX_SIZE: Max size in MB before rotating (default: 10MB) |
| 20 | LOG_BACKUP_COUNT: Number of backup files to keep (default: 5) |
| 21 | |
| 22 | Ensures log directory exists, prevents path traversal, and configures |
| 23 | both rotating file and console handlers. |
| 24 | """ |
| 25 | # Determine log directory and default file path |
| 26 | base_dir = Path(__file__).parent |
| 27 | log_dir = base_dir / "logs" |
| 28 | log_dir.mkdir(parents=True, exist_ok=True) |
| 29 | default_log_file = log_dir / "application.log" |
| 30 | |
| 31 | # Get log level from environment |
| 32 | log_level_str = os.environ.get("LOG_LEVEL", "INFO").upper() |
| 33 | log_level = getattr(logging, log_level_str, logging.INFO) |
| 34 | |
| 35 | # Get log file path |
| 36 | log_file_path = Path(os.environ.get("LOG_FILE_PATH", str(default_log_file))) |
| 37 | |
| 38 | # Secure path check: must be inside logs/ directory |
| 39 | log_dir_resolved = log_dir.resolve() |
| 40 | resolved_path = log_file_path.resolve() |
| 41 | if not str(resolved_path).startswith(str(log_dir_resolved) + os.sep): |
| 42 | raise ValueError(f"LOG_FILE_PATH '{log_file_path}' is outside the trusted log directory '{log_dir_resolved}'") |
| 43 | |
| 44 | # Ensure parent directories exist |
| 45 | resolved_path.parent.mkdir(parents=True, exist_ok=True) |
| 46 | |
| 47 | # Get max log file size (default: 10MB) |
| 48 | try: |
| 49 | max_mb = int(os.environ.get("LOG_MAX_SIZE", 10)) # 10MB default |
| 50 | max_bytes = max_mb * 1024 * 1024 |
| 51 | except (TypeError, ValueError): |
| 52 | max_bytes = 10 * 1024 * 1024 # fallback to 10MB on error |
| 53 | |
| 54 | # Get backup count (default: 5) |
| 55 | try: |
| 56 | backup_count = int(os.environ.get("LOG_BACKUP_COUNT", 5)) |
| 57 | except ValueError: |
| 58 | backup_count = 5 |
| 59 | |
| 60 | # Configure format |
| 61 | log_format = format or "%(asctime)s - %(levelname)s - %(name)s - %(filename)s:%(lineno)d - %(message)s" |
| 62 | |
| 63 | # Create handlers |
| 64 | file_handler = RotatingFileHandler(resolved_path, maxBytes=max_bytes, backupCount=backup_count, encoding="utf-8") |
| 65 | console_handler = logging.StreamHandler() |
| 66 | |
| 67 | # Set format for both handlers |
| 68 | formatter = logging.Formatter(log_format) |
| 69 | file_handler.setFormatter(formatter) |
no test coverage detected