Initial commit: Video Library Manager
- Add core VLM modules (scanner, parser, planner, executor, analysis) - Add CLI with quarantine, reports, rollback, and state management - Add comprehensive test suite - Add project configuration and documentation - Add .gitignore for Python project
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
"""Logging configuration for Video Library Manager.
|
||||
|
||||
This module provides centralized logging configuration with:
|
||||
- Configurable log levels (DEBUG, INFO, WARNING, ERROR)
|
||||
- Dual output: console (INFO+) and file (DEBUG+)
|
||||
- Timestamps, operation type, and file paths in log entries
|
||||
- Log rotation at 10MB threshold
|
||||
"""
|
||||
|
||||
import logging
|
||||
import logging.handlers
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
|
||||
# Default log directory
|
||||
DEFAULT_LOG_DIR = Path.home() / ".vlm" / "logs"
|
||||
DEFAULT_LOG_FILE = "vlm.log"
|
||||
MAX_LOG_SIZE = 10 * 1024 * 1024 # 10MB in bytes
|
||||
BACKUP_COUNT = 5 # Keep 5 backup log files
|
||||
|
||||
|
||||
class OperationContextFilter(logging.Filter):
|
||||
"""Filter to add operation context to log records."""
|
||||
|
||||
def filter(self, record: logging.LogRecord) -> bool:
|
||||
"""Add operation_type and file_path attributes if not present."""
|
||||
if not hasattr(record, 'operation_type'):
|
||||
record.operation_type = 'general'
|
||||
if not hasattr(record, 'file_path'):
|
||||
record.file_path = ''
|
||||
return True
|
||||
|
||||
|
||||
def setup_logging(
|
||||
log_level: str = "INFO",
|
||||
log_dir: Optional[Path] = None,
|
||||
log_file: str = DEFAULT_LOG_FILE
|
||||
) -> logging.Logger:
|
||||
"""Configure logging with dual output (console and file) and rotation.
|
||||
|
||||
Args:
|
||||
log_level: Minimum log level for console output (DEBUG, INFO, WARNING, ERROR)
|
||||
log_dir: Directory for log files (defaults to ~/.vlm/logs)
|
||||
log_file: Name of the log file (defaults to vlm.log)
|
||||
|
||||
Returns:
|
||||
Configured logger instance
|
||||
|
||||
Raises:
|
||||
ValueError: If log_level is invalid
|
||||
"""
|
||||
# Validate log level
|
||||
numeric_level = getattr(logging, log_level.upper(), None)
|
||||
if not isinstance(numeric_level, int):
|
||||
raise ValueError(f"Invalid log level: {log_level}")
|
||||
|
||||
# Use default log directory if not specified
|
||||
if log_dir is None:
|
||||
log_dir = DEFAULT_LOG_DIR
|
||||
|
||||
# Create log directory if it doesn't exist
|
||||
log_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Get root logger
|
||||
logger = logging.getLogger("vlm")
|
||||
logger.setLevel(logging.DEBUG) # Capture all levels, handlers will filter
|
||||
|
||||
# Remove existing handlers to avoid duplicates
|
||||
logger.handlers.clear()
|
||||
|
||||
# Create formatter with timestamps, operation type, and file paths
|
||||
formatter = logging.Formatter(
|
||||
fmt='%(asctime)s - %(levelname)s - [%(operation_type)s] - %(message)s%(file_path)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
# Console handler (INFO+)
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setLevel(numeric_level)
|
||||
console_handler.setFormatter(formatter)
|
||||
console_handler.addFilter(OperationContextFilter())
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
# File handler with rotation (DEBUG+)
|
||||
log_file_path = log_dir / log_file
|
||||
file_handler = logging.handlers.RotatingFileHandler(
|
||||
filename=log_file_path,
|
||||
maxBytes=MAX_LOG_SIZE,
|
||||
backupCount=BACKUP_COUNT,
|
||||
encoding='utf-8'
|
||||
)
|
||||
file_handler.setLevel(logging.DEBUG)
|
||||
file_handler.setFormatter(formatter)
|
||||
file_handler.addFilter(OperationContextFilter())
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
# Prevent propagation to root logger
|
||||
logger.propagate = False
|
||||
|
||||
return logger
|
||||
|
||||
|
||||
def get_logger() -> logging.Logger:
|
||||
"""Get the configured VLM logger instance.
|
||||
|
||||
Returns:
|
||||
Logger instance (creates default configuration if not already set up)
|
||||
"""
|
||||
logger = logging.getLogger("vlm")
|
||||
|
||||
# If logger has no handlers, set up default configuration
|
||||
if not logger.handlers:
|
||||
setup_logging()
|
||||
|
||||
return logger
|
||||
|
||||
|
||||
def log_operation(
|
||||
logger: logging.Logger,
|
||||
level: int,
|
||||
message: str,
|
||||
operation_type: str = "general",
|
||||
file_path: Optional[Path] = None
|
||||
) -> None:
|
||||
"""Log a message with operation context.
|
||||
|
||||
Args:
|
||||
logger: Logger instance
|
||||
level: Log level (logging.DEBUG, logging.INFO, etc.)
|
||||
message: Log message
|
||||
operation_type: Type of operation (scan, parse, execute, etc.)
|
||||
file_path: Optional file path related to the operation
|
||||
"""
|
||||
extra = {
|
||||
'operation_type': operation_type,
|
||||
'file_path': f' - {file_path}' if file_path else ''
|
||||
}
|
||||
logger.log(level, message, extra=extra)
|
||||
Reference in New Issue
Block a user