Spaces:
Running
on
Zero
Running
on
Zero
File size: 6,008 Bytes
5b481a8 c69799e 5391050 5b481a8 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
"""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)
|