from __future__ import annotations import logging from typing import Iterable, Optional _CONFIGURED = False class _SrcOnlyFilter(logging.Filter): def filter(self, record: logging.LogRecord) -> bool: return bool(record.name.startswith("src.")) def configure_logging( *, level: int = logging.INFO, log_file: Optional[str] = "fin_assistant.log", src_only: bool = True, quiet_loggers: Optional[Iterable[str]] = None, ) -> None: """ Configure application logging once. - src_only=True: only emit logs from modules under the `src.` namespace. - quiet_loggers: set these logger names to WARNING to reduce noise. """ global _CONFIGURED if _CONFIGURED: return root = logging.getLogger() root.setLevel(level) formatter = logging.Formatter( fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) handlers: list[logging.Handler] = [] stream_handler = logging.StreamHandler() stream_handler.setFormatter(formatter) handlers.append(stream_handler) if log_file: file_handler = logging.FileHandler(log_file) file_handler.setFormatter(formatter) handlers.append(file_handler) if src_only: f = _SrcOnlyFilter() for h in handlers: h.addFilter(f) # Avoid duplicating handlers on Streamlit reloads if something else configured root. # Only attach if root has no handlers yet. if not root.handlers: for h in handlers: root.addHandler(h) # Silence noisy third-party loggers by default. defaults = [ "httpx", "httpcore", "openai", "chromadb", "urllib3", "requests", "langchain", "langchain_core", "langchain_community", ] for name in list(quiet_loggers or []) + defaults: logging.getLogger(name).setLevel(logging.WARNING) _CONFIGURED = True