0a345a0f22
- Reduce plot-container margin-bottom from 450px to 2rem in view-controls.css - Reduce body padding-bottom from 400px to 2rem in base.css - Fix unnecessary spacing since stats box uses fixed positioning - Enhance logger with professional tree-structured output and colors - Replace print statements with structured logging throughout codebase
251 lines
8.5 KiB
Python
251 lines
8.5 KiB
Python
"""
|
|
Gallery logging wrapper using Python's built-in logging module with tree-like output.
|
|
"""
|
|
|
|
import logging
|
|
import sys
|
|
|
|
|
|
class Colors:
|
|
"""ANSI color codes for terminal output"""
|
|
RESET = '\033[0m'
|
|
BOLD = '\033[1m'
|
|
|
|
# Standard colors
|
|
RED = '\033[31m'
|
|
GREEN = '\033[32m'
|
|
YELLOW = '\033[33m'
|
|
BLUE = '\033[34m'
|
|
MAGENTA = '\033[35m'
|
|
CYAN = '\033[36m'
|
|
WHITE = '\033[37m'
|
|
|
|
# Bright colors
|
|
BRIGHT_GREEN = '\033[92m'
|
|
BRIGHT_YELLOW = '\033[93m'
|
|
BRIGHT_BLUE = '\033[94m'
|
|
BRIGHT_CYAN = '\033[96m'
|
|
|
|
|
|
class TreeFormatter(logging.Formatter):
|
|
"""Custom formatter that creates clean output with colors"""
|
|
|
|
def __init__(self, use_colors: bool = True):
|
|
super().__init__()
|
|
self.use_colors = use_colors and sys.stdout.isatty()
|
|
|
|
def _colorize(self, text: str, color: str) -> str:
|
|
"""Apply color to text if colors are enabled"""
|
|
if not self.use_colors:
|
|
return text
|
|
return f"{color}{text}{Colors.RESET}"
|
|
|
|
def format(self, record):
|
|
# Extract custom attributes from the record
|
|
indent = getattr(record, 'indent', 0)
|
|
|
|
# Create simple indentation
|
|
prefix = " " * indent
|
|
|
|
# Apply colors based on level and content
|
|
message = record.getMessage()
|
|
|
|
if record.levelname == 'INFO':
|
|
if 'Processing:' in message:
|
|
# Main source headers
|
|
message = self._colorize(message, Colors.BOLD + Colors.MAGENTA)
|
|
elif 'Generated' in message:
|
|
message = self._colorize(message, Colors.GREEN)
|
|
elif 'PDF files' in message:
|
|
message = self._colorize(message, Colors.BLUE)
|
|
elif 'Converting' in message:
|
|
message = self._colorize(message, Colors.CYAN)
|
|
elif 'Completed' in message and 'sources' in message:
|
|
message = self._colorize(message, Colors.BOLD + Colors.GREEN)
|
|
else:
|
|
message = self._colorize(message, Colors.WHITE)
|
|
elif record.levelname == 'WARNING':
|
|
message = self._colorize(f"WARNING: {message}", Colors.YELLOW)
|
|
elif record.levelname == 'ERROR':
|
|
message = self._colorize(f"ERROR: {message}", Colors.RED)
|
|
elif record.levelname == 'DEBUG':
|
|
message = self._colorize(message, Colors.CYAN)
|
|
else:
|
|
message = message
|
|
|
|
return f"{prefix}{message}"
|
|
|
|
|
|
class GalleryLogger:
|
|
"""
|
|
Simple logger for gallery generation using Python's logging module.
|
|
Provides convenient methods for common logging patterns with clean output.
|
|
"""
|
|
|
|
def __init__(self, name: str = "gallery", level: int = logging.INFO,
|
|
verbose: bool = False, use_colors: bool = True):
|
|
"""
|
|
Initialize the gallery logger.
|
|
|
|
Args:
|
|
name: Logger name
|
|
level: Logging level (default: INFO)
|
|
verbose: If True, enables DEBUG level logging
|
|
use_colors: If True, enables colored output
|
|
"""
|
|
self.logger = logging.getLogger(name)
|
|
self.use_colors = use_colors
|
|
self.current_source = None
|
|
self.current_depth = 0
|
|
|
|
# Set level based on verbose flag
|
|
if verbose:
|
|
self.logger.setLevel(logging.DEBUG)
|
|
else:
|
|
self.logger.setLevel(level)
|
|
|
|
# Only add handler if logger doesn't have one already
|
|
if not self.logger.handlers:
|
|
handler = logging.StreamHandler(sys.stdout)
|
|
formatter = TreeFormatter(use_colors=use_colors)
|
|
handler.setFormatter(formatter)
|
|
self.logger.addHandler(handler)
|
|
|
|
# Prevent propagation to avoid duplicate messages
|
|
self.logger.propagate = False
|
|
|
|
def _log_with_tree(self, level: int, message: str, indent: int = 0):
|
|
"""Log a message with simple indentation"""
|
|
# Store indentation info in a way the formatter can access
|
|
original_makeRecord = self.logger.makeRecord
|
|
|
|
def makeRecord_with_indent(*args, **kwargs):
|
|
record = original_makeRecord(*args, **kwargs)
|
|
record.indent = indent
|
|
return record
|
|
|
|
# Temporarily replace makeRecord to add our custom attributes
|
|
self.logger.makeRecord = makeRecord_with_indent
|
|
try:
|
|
self.logger.log(level, message)
|
|
finally:
|
|
# Restore original makeRecord
|
|
self.logger.makeRecord = original_makeRecord
|
|
|
|
def start_source(self, source_name: str):
|
|
"""Begin processing a new source"""
|
|
self.current_source = source_name
|
|
self.current_depth = 0
|
|
if self.use_colors:
|
|
colored_name = f"{Colors.BOLD}{Colors.MAGENTA}{source_name}{Colors.RESET}"
|
|
self.logger.info(f"\nProcessing: {colored_name}")
|
|
else:
|
|
self.logger.info(f"\nProcessing: {source_name}")
|
|
|
|
def info(self, message: str, indent: int = 0):
|
|
"""Log an info message"""
|
|
self._log_with_tree(logging.INFO, message, indent)
|
|
|
|
def debug(self, message: str, indent: int = 0):
|
|
"""Log a debug message"""
|
|
self._log_with_tree(logging.DEBUG, message, indent)
|
|
|
|
def warning(self, message: str, indent: int = 0):
|
|
"""Log a warning message"""
|
|
self._log_with_tree(logging.WARNING, message, indent)
|
|
|
|
def error(self, message: str, indent: int = 0):
|
|
"""Log an error message"""
|
|
self._log_with_tree(logging.ERROR, message, indent)
|
|
|
|
def found_directory(self, dir_name: str, pdf_count: int, indent: int = 1):
|
|
"""Log discovery of a directory with PDFs"""
|
|
if pdf_count > 0:
|
|
self._log_with_tree(
|
|
logging.INFO,
|
|
f"Found {dir_name}/ ({pdf_count} PDFs)",
|
|
indent
|
|
)
|
|
|
|
def found_pdf(self, pdf_name: str, indent: int = 2):
|
|
"""Log discovery of a PDF file (debug only)"""
|
|
self._log_with_tree(logging.DEBUG, f"Found PDF: {pdf_name}", indent)
|
|
|
|
def found_metadata(self, metadata_file: str, field_count: int, indent: int = 1):
|
|
"""Log discovery of a metadata file"""
|
|
self._log_with_tree(
|
|
logging.INFO,
|
|
f"Found metadata: {metadata_file} ({field_count} fields)",
|
|
indent
|
|
)
|
|
|
|
def converted_pdf(self, pdf_name: str, duration: float = None, indent: int = 2):
|
|
"""Log successful PDF conversion"""
|
|
if duration is not None:
|
|
self._log_with_tree(
|
|
logging.INFO,
|
|
f"Converting {pdf_name} ({duration:.2f}s)",
|
|
indent
|
|
)
|
|
else:
|
|
self._log_with_tree(logging.INFO, f"Converting {pdf_name}", indent)
|
|
|
|
def skipped_pdf(self, pdf_name: str, reason: str = "up-to-date", indent: int = 2):
|
|
"""Log skipped PDF conversion"""
|
|
self._log_with_tree(logging.DEBUG, f"Skipping {pdf_name} ({reason})", indent)
|
|
|
|
def generated_html(self, html_path: str, plot_count: int, indent: int = 1):
|
|
"""Log HTML page generation"""
|
|
# Extract just the meaningful part of the path
|
|
if '/gallery/' in html_path:
|
|
short_path = html_path.split('/gallery/')[-1]
|
|
else:
|
|
short_path = html_path
|
|
|
|
if plot_count > 0:
|
|
self._log_with_tree(
|
|
logging.INFO,
|
|
f"Generated {short_path} ({plot_count} plots)",
|
|
indent
|
|
)
|
|
else:
|
|
self._log_with_tree(
|
|
logging.DEBUG,
|
|
f"Generated {short_path} (index only)",
|
|
indent
|
|
)
|
|
|
|
def assets_updated(self):
|
|
"""Log assets update"""
|
|
self.logger.info("Assets updated")
|
|
|
|
def summary(self, source_count: int):
|
|
"""Print a summary"""
|
|
if self.use_colors:
|
|
message = f"{Colors.BOLD}{Colors.GREEN}Completed processing {source_count} sources{Colors.RESET}"
|
|
else:
|
|
message = f"Completed processing {source_count} sources"
|
|
self.logger.info(f"\n{message}")
|
|
|
|
|
|
def create_logger(verbose: bool = False, quiet: bool = False, use_colors: bool = True) -> GalleryLogger:
|
|
"""
|
|
Create a configured logger for the gallery application.
|
|
|
|
Args:
|
|
verbose: Enable debug-level logging
|
|
quiet: Suppress most output (only errors and warnings)
|
|
use_colors: Enable colored output
|
|
|
|
Returns:
|
|
Configured GalleryLogger instance
|
|
"""
|
|
if quiet:
|
|
level = logging.WARNING
|
|
elif verbose:
|
|
level = logging.DEBUG
|
|
else:
|
|
level = logging.INFO
|
|
|
|
return GalleryLogger(level=level, verbose=verbose, use_colors=use_colors)
|