Spaces:
Sleeping
Sleeping
| """ | |
| Logging Utilities | |
| ================= | |
| Centralized logging setup for Nano Banana Streamlit. | |
| Provides both file logging and in-memory log storage for UI display. | |
| """ | |
| import logging | |
| import threading | |
| from collections import deque | |
| from logging.handlers import RotatingFileHandler | |
| from pathlib import Path | |
| from typing import List | |
| from config.settings import Settings | |
| # ============================================================================= | |
| # IN-MEMORY LOG STORAGE FOR UI DISPLAY | |
| # ============================================================================= | |
| # Global log storage queue (thread-safe) | |
| _log_queue = deque(maxlen=1000) # Keep last 1000 messages | |
| _log_lock = threading.Lock() | |
| class MemoryLogHandler(logging.Handler): | |
| """ | |
| Custom logging handler that stores log messages in memory. | |
| Stores messages in a thread-safe queue that can be retrieved | |
| for display in the Streamlit UI. | |
| """ | |
| def emit(self, record): | |
| """ | |
| Handle a log record by storing it in the queue. | |
| Args: | |
| record: LogRecord to process | |
| """ | |
| try: | |
| msg = self.format(record) | |
| with _log_lock: | |
| _log_queue.append(msg) | |
| except Exception: | |
| self.handleError(record) | |
| # ============================================================================= | |
| # LOGGER SETUP FUNCTIONS | |
| # ============================================================================= | |
| def setup_logger(name: str, level: str = None) -> logging.Logger: | |
| """ | |
| Set up a logger with both file and memory handlers. | |
| Creates a logger that writes to: | |
| - Rotating log file (configured in Settings) | |
| - In-memory queue (for UI display) | |
| Args: | |
| name: Logger name (usually module name) | |
| level: Logging level (default: from Settings.LOG_LEVEL) | |
| Returns: | |
| Configured logger instance | |
| """ | |
| # Get or create logger | |
| logger = logging.getLogger(name) | |
| # Set level | |
| log_level = level or Settings.LOG_LEVEL | |
| logger.setLevel(getattr(logging, log_level)) | |
| # Clear any existing handlers (avoid duplicates) | |
| logger.handlers.clear() | |
| # Create formatter | |
| formatter = logging.Formatter( | |
| Settings.LOG_FORMAT, | |
| datefmt=Settings.LOG_DATE_FORMAT | |
| ) | |
| # File handler (rotating) | |
| file_handler = RotatingFileHandler( | |
| Settings.LOG_FILE, | |
| maxBytes=Settings.LOG_MAX_BYTES, | |
| backupCount=Settings.LOG_BACKUP_COUNT, | |
| encoding='utf-8' | |
| ) | |
| file_handler.setLevel(getattr(logging, log_level)) | |
| file_handler.setFormatter(formatter) | |
| # Memory handler (for UI display) | |
| memory_handler = MemoryLogHandler() | |
| memory_handler.setLevel(getattr(logging, log_level)) | |
| memory_handler.setFormatter(formatter) | |
| # Add handlers | |
| logger.addHandler(file_handler) | |
| logger.addHandler(memory_handler) | |
| # Prevent propagation to root logger | |
| logger.propagate = False | |
| return logger | |
| def get_logger(name: str) -> logging.Logger: | |
| """ | |
| Get or create a logger for a module. | |
| If the logger hasn't been set up yet, it will be initialized | |
| with default settings. | |
| Args: | |
| name: Logger name (usually __name__ from calling module) | |
| Returns: | |
| Logger instance | |
| """ | |
| logger = logging.getLogger(name) | |
| # If logger has no handlers, set it up | |
| if not logger.handlers: | |
| return setup_logger(name) | |
| return logger | |
| # ============================================================================= | |
| # LOG RETRIEVAL FUNCTIONS | |
| # ============================================================================= | |
| def get_recent_logs(count: int = None, limit: int = None) -> List[str]: | |
| """ | |
| Retrieve recent log messages. | |
| Args: | |
| count: Maximum number of recent messages to retrieve (deprecated, use limit) | |
| limit: Maximum number of recent messages to retrieve | |
| Returns: | |
| List of log message strings | |
| """ | |
| # Support both 'count' and 'limit' parameter names | |
| n = limit if limit is not None else (count if count is not None else 100) | |
| with _log_lock: | |
| messages = list(_log_queue) | |
| # Return last N messages | |
| recent = messages[-n:] if len(messages) > n else messages | |
| return recent | |
| def get_recent_logs_as_string(count: int = 100) -> str: | |
| """ | |
| Retrieve recent log messages as a formatted string. | |
| Args: | |
| count: Maximum number of recent messages to retrieve | |
| Returns: | |
| Formatted string with log messages (one per line) | |
| """ | |
| messages = get_recent_logs(count) | |
| return '\n'.join(messages) | |
| def clear_log_memory(): | |
| """Clear the in-memory log queue.""" | |
| with _log_lock: | |
| _log_queue.clear() | |
| def get_log_count() -> int: | |
| """ | |
| Get the current number of messages in the log queue. | |
| Returns: | |
| Number of messages currently stored | |
| """ | |
| with _log_lock: | |
| return len(_log_queue) | |
| # ============================================================================= | |
| # LOGGING CONTEXT MANAGERS | |
| # ============================================================================= | |
| class LoggingContext: | |
| """ | |
| Context manager for temporarily changing log level. | |
| Usage: | |
| with LoggingContext('my_module', 'DEBUG'): | |
| # Code here will log at DEBUG level | |
| pass | |
| """ | |
| def __init__(self, logger_name: str, level: str): | |
| """ | |
| Initialize logging context. | |
| Args: | |
| logger_name: Name of logger to modify | |
| level: Temporary log level ('DEBUG', 'INFO', 'WARNING', 'ERROR') | |
| """ | |
| self.logger = logging.getLogger(logger_name) | |
| self.original_level = self.logger.level | |
| self.new_level = getattr(logging, level) | |
| def __enter__(self): | |
| """Enter context - set new log level.""" | |
| self.logger.setLevel(self.new_level) | |
| return self.logger | |
| def __exit__(self, exc_type, exc_val, exc_tb): | |
| """Exit context - restore original log level.""" | |
| self.logger.setLevel(self.original_level) | |
| return False # Don't suppress exceptions | |
| # ============================================================================= | |
| # UTILITY FUNCTIONS | |
| # ============================================================================= | |
| def log_function_call(logger: logging.Logger, func_name: str, **kwargs): | |
| """ | |
| Log a function call with its parameters. | |
| Args: | |
| logger: Logger instance to use | |
| func_name: Name of function being called | |
| **kwargs: Function parameters to log | |
| """ | |
| params = ', '.join(f'{k}={v}' for k, v in kwargs.items()) | |
| logger.info(f"Calling {func_name}({params})") | |
| def log_stage(logger: logging.Logger, stage: str, message: str): | |
| """ | |
| Log a pipeline stage. | |
| Args: | |
| logger: Logger instance to use | |
| stage: Stage identifier (e.g., "Stage 1/6") | |
| message: Stage description | |
| """ | |
| separator = "=" * 60 | |
| logger.info(separator) | |
| logger.info(f"{stage}: {message}") | |
| logger.info(separator) | |
| def log_error_with_context(logger: logging.Logger, error: Exception, context: dict): | |
| """ | |
| Log an error with additional context. | |
| Args: | |
| logger: Logger instance to use | |
| error: Exception that occurred | |
| context: Dictionary of contextual information | |
| """ | |
| logger.error(f"Error: {str(error)}") | |
| logger.error(f"Error type: {type(error).__name__}") | |
| for key, value in context.items(): | |
| logger.error(f" {key}: {value}") | |