Spaces:
Running
on
Zero
Running
on
Zero
| """Centralised logging configuration for Portfolio Intelligence Platform. | |
| This module provides unified logging control via environment variables: | |
| - LOG_LEVEL: Set logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL, OFF) | |
| - LOGGING_ENABLED: Enable/disable all logging (true/false) | |
| Usage: | |
| from backend.logging_config import configure_logging | |
| # Call early in app startup (before other imports) | |
| configure_logging() | |
| """ | |
| import os | |
| import logging | |
| import sys | |
| from typing import Optional | |
| # Mapping of string levels to logging constants | |
| LOG_LEVELS = { | |
| "DEBUG": logging.DEBUG, | |
| "INFO": logging.INFO, | |
| "WARNING": logging.WARNING, | |
| "WARN": logging.WARNING, | |
| "ERROR": logging.ERROR, | |
| "CRITICAL": logging.CRITICAL, | |
| "OFF": logging.CRITICAL + 10, # Higher than any level to disable all | |
| "NONE": logging.CRITICAL + 10, | |
| } | |
| # Third-party loggers to suppress in production | |
| THIRD_PARTY_LOGGERS = [ | |
| "gradio", | |
| "gradio_client", | |
| "httpx", | |
| "uvicorn", | |
| "uvicorn.access", | |
| "uvicorn.error", | |
| "matplotlib", | |
| "matplotlib.font_manager", | |
| "PIL", | |
| "httpcore", | |
| "websockets", | |
| "asyncio", | |
| "multipart", | |
| "charset_normalizer", | |
| ] | |
| class NullHandler(logging.Handler): | |
| """A handler that does nothing - used to completely disable logging.""" | |
| def emit(self, record: logging.LogRecord) -> None: | |
| pass | |
| def get_log_level() -> int: | |
| """Get the configured log level from environment. | |
| Reads LOG_LEVEL environment variable and returns corresponding | |
| logging constant. Defaults to INFO if not set or invalid. | |
| Returns: | |
| Logging level constant (e.g., logging.INFO) | |
| """ | |
| level_str = os.getenv("LOG_LEVEL", "INFO").upper().strip() | |
| return LOG_LEVELS.get(level_str, logging.INFO) | |
| def is_logging_enabled() -> bool: | |
| """Check if logging is enabled via environment variable. | |
| Reads LOGGING_ENABLED environment variable. Defaults to True | |
| if not set. Accepts 'true', '1', 'yes', 'on' as truthy values. | |
| Returns: | |
| True if logging is enabled, False otherwise | |
| """ | |
| value = os.getenv("LOGGING_ENABLED", "true").lower().strip() | |
| return value in ("true", "1", "yes", "on") | |
| def is_production() -> bool: | |
| """Check if running in production environment. | |
| Detects HuggingFace Spaces via SPACE_ID environment variable | |
| or ENVIRONMENT=production. | |
| Returns: | |
| True if in production environment | |
| """ | |
| return bool(os.getenv("SPACE_ID")) or os.getenv("ENVIRONMENT") == "production" | |
| def configure_logging( | |
| level: Optional[int] = None, | |
| enabled: Optional[bool] = None, | |
| suppress_third_party: bool = True, | |
| format_string: Optional[str] = None, | |
| ) -> None: | |
| """Configure logging for the entire application. | |
| This function should be called early in application startup, | |
| before importing other modules that create loggers. | |
| Args: | |
| level: Override log level (uses LOG_LEVEL env var if None) | |
| enabled: Override enabled state (uses LOGGING_ENABLED env var if None) | |
| suppress_third_party: Whether to suppress noisy third-party loggers | |
| format_string: Custom format string (uses default if None) | |
| Environment Variables: | |
| LOG_LEVEL: DEBUG, INFO, WARNING, ERROR, CRITICAL, OFF | |
| LOGGING_ENABLED: true/false to enable/disable all logging | |
| Examples: | |
| # Use environment variables | |
| configure_logging() | |
| # Override with specific level | |
| configure_logging(level=logging.DEBUG) | |
| # Completely disable logging | |
| configure_logging(enabled=False) | |
| """ | |
| # Determine final settings | |
| log_level = level if level is not None else get_log_level() | |
| logging_enabled = enabled if enabled is not None else is_logging_enabled() | |
| # Get root logger | |
| root_logger = logging.getLogger() | |
| # Clear any existing handlers | |
| root_logger.handlers.clear() | |
| if not logging_enabled or log_level >= LOG_LEVELS["OFF"]: | |
| # Completely disable logging | |
| root_logger.addHandler(NullHandler()) | |
| root_logger.setLevel(LOG_LEVELS["OFF"]) | |
| return | |
| # Set up console handler | |
| handler = logging.StreamHandler(sys.stderr) | |
| handler.setLevel(log_level) | |
| # Configure format | |
| if format_string is None: | |
| if is_production(): | |
| # Compact format for production | |
| format_string = "%(levelname)s:%(name)s:%(message)s" | |
| else: | |
| # Detailed format for development | |
| format_string = "%(asctime)s - %(levelname)s - %(name)s - %(message)s" | |
| formatter = logging.Formatter(format_string) | |
| handler.setFormatter(formatter) | |
| # Configure root logger | |
| root_logger.addHandler(handler) | |
| root_logger.setLevel(log_level) | |
| # Suppress third-party loggers in production | |
| if suppress_third_party and is_production(): | |
| for logger_name in THIRD_PARTY_LOGGERS: | |
| logging.getLogger(logger_name).setLevel(logging.WARNING) | |
| # Always suppress matplotlib font manager (very noisy) | |
| logging.getLogger("matplotlib.font_manager").setLevel(logging.WARNING) | |
| # Note: Starlette BaseHTTPMiddleware ASGI assertion errors (triggered by | |
| # Gradio's "Use via API" button) cannot be suppressed via logging filters | |
| # because uvicorn prints them directly to stderr. This is a known | |
| # Gradio/Starlette limitation - the app works correctly despite the errors. | |
| # Log the configuration (if not suppressed) | |
| if log_level <= logging.INFO: | |
| config_logger = logging.getLogger(__name__) | |
| config_logger.info( | |
| f"Logging configured: level={logging.getLevelName(log_level)}, " | |
| f"production={is_production()}" | |
| ) | |
| def get_logger(name: str) -> logging.Logger: | |
| """Get a logger with the specified name. | |
| Convenience function that ensures logging is configured | |
| before returning the logger. | |
| Args: | |
| name: Logger name (typically __name__) | |
| Returns: | |
| Configured logger instance | |
| """ | |
| return logging.getLogger(name) | |