From 51313f60dcf18a2b44cb1a72e4992f1aa5106005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D9=8A?= Date: Thu, 12 Mar 2026 15:05:30 +0530 Subject: [PATCH] Unify backend logging configuration entrypoint --- backend/app.py | 4 +- backend/logging_config.py | 68 +++++++++++++++++++++++++++----- backend/main.py | 4 +- backend/start_alwrity_backend.py | 4 +- 4 files changed, 64 insertions(+), 16 deletions(-) diff --git a/backend/app.py b/backend/app.py index 0b190fb9..daf30bff 100644 --- a/backend/app.py +++ b/backend/app.py @@ -49,8 +49,8 @@ load_dotenv(project_root / '.env') # root .env (fallback) load_dotenv() # CWD .env (fallback) # Set up clean logging for end users -from logging_config import setup_clean_logging -setup_clean_logging() +from logging_config import configure_logging +configure_logging(mode="default", app_name="ALwrity") # Import middleware from middleware.auth_middleware import get_current_user diff --git a/backend/logging_config.py b/backend/logging_config.py index 539db62e..53099454 100644 --- a/backend/logging_config.py +++ b/backend/logging_config.py @@ -9,13 +9,40 @@ import sys from loguru import logger -def setup_clean_logging(): - """Set up clean logging for end users.""" - verbose_mode = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true" - - # Always remove all existing handlers first to prevent conflicts +_LOGGING_CONFIGURED = False + + +class LoguruInterceptHandler(logging.Handler): + """Forward stdlib logging records to Loguru.""" + + def emit(self, record: logging.LogRecord) -> None: + try: + level = logger.level(record.levelname).name + except ValueError: + level = record.levelno + + frame, depth = logging.currentframe(), 2 + while frame and frame.f_code.co_filename == logging.__file__: + frame = frame.f_back + depth += 1 + + logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage()) + + +def configure_logging(mode: str = "default", verbose: bool | None = None, app_name: str = "alwrity") -> bool: + """Configure Loguru and stdlib logging into one shared pipeline.""" + global _LOGGING_CONFIGURED + + if verbose is None: + verbose_mode = mode == "verbose" or os.getenv("ALWRITY_VERBOSE", "false").lower() == "true" + else: + verbose_mode = verbose + + if _LOGGING_CONFIGURED: + return verbose_mode + logger.remove() - + if not verbose_mode: # Suppress verbose logging for end users - be more aggressive logging.getLogger('sqlalchemy.engine').setLevel(logging.CRITICAL) @@ -90,7 +117,7 @@ def setup_clean_logging(): logger.add( sys.stdout.write, level="WARNING", - format="{time:HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}\n", + format=f"{app_name} | {{time:HH:mm:ss}} | {{level: <8}} | {{name}}:{{function}}:{{line}} - {{message}}\n", filter=warning_only_filter ) # Add a focused sink to surface Story Video Generation INFO logs in console @@ -108,7 +135,7 @@ def setup_clean_logging(): logger.add( sys.stdout.write, level="INFO", - format="{time:HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}\n", + format=f"{app_name} | {{time:HH:mm:ss}} | {{level: <8}} | {{name}}:{{function}}:{{line}} - {{message}}\n", filter=video_generation_filter ) else: @@ -116,12 +143,33 @@ def setup_clean_logging(): logger.add( sys.stdout.write, level="DEBUG", - format="{time:HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}\n" + format=f"{app_name} | {{time:HH:mm:ss}} | {{level: <8}} | {{name}}:{{function}}:{{line}} - {{message}}\n" ) - + + intercept_handler = LoguruInterceptHandler() + root_logger = logging.getLogger() + root_logger.handlers = [intercept_handler] + root_logger.setLevel(logging.DEBUG if verbose_mode else logging.WARNING) + + logging.captureWarnings(True) + warnings_logger = logging.getLogger("py.warnings") + warnings_logger.handlers = [intercept_handler] + warnings_logger.propagate = True + + for existing_logger in logging.root.manager.loggerDict.values(): + if isinstance(existing_logger, logging.Logger): + existing_logger.handlers = [] + existing_logger.propagate = True + + _LOGGING_CONFIGURED = True return verbose_mode +def setup_clean_logging(): + """Backward-compatible wrapper for existing startup files.""" + return configure_logging(mode="default") + + def get_uvicorn_log_level(): """Get appropriate uvicorn log level based on verbose mode.""" verbose_mode = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true" diff --git a/backend/main.py b/backend/main.py index 7f5ee43f..e0fb57dd 100644 --- a/backend/main.py +++ b/backend/main.py @@ -49,8 +49,8 @@ load_dotenv(project_root / '.env') # root .env (fallback) load_dotenv() # CWD .env (fallback) # Set up clean logging for end users -from logging_config import setup_clean_logging -setup_clean_logging() +from logging_config import configure_logging +configure_logging(mode="default", app_name="ALwrity") # Import middleware from middleware.auth_middleware import get_current_user diff --git a/backend/start_alwrity_backend.py b/backend/start_alwrity_backend.py index 58286dd6..0fe21630 100644 --- a/backend/start_alwrity_backend.py +++ b/backend/start_alwrity_backend.py @@ -216,7 +216,7 @@ def start_backend(enable_reload=False, production_mode=False): print("=" * 50) # Set up clean logging for end users - from logging_config import setup_clean_logging, get_uvicorn_log_level + from logging_config import configure_logging, get_uvicorn_log_level # Video stack preflight (diagnostics + version assert) try: from services.story_writer.video_preflight import ( @@ -228,7 +228,7 @@ def start_backend(enable_reload=False, production_mode=False): log_video_stack_diagnostics = None assert_supported_moviepy = None - verbose_mode = setup_clean_logging() + verbose_mode = configure_logging(mode="default", app_name="ALwrity") uvicorn_log_level = get_uvicorn_log_level() # Log diagnostics and assert versions (fail fast if misconfigured)