diff --git a/.gitignore b/.gitignore index 4e46d2e71..0076848e0 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ coverage/ skills/custom/* logs/ log/ +debug.log # Local git hooks (keep only on this machine, do not push) .githooks/ diff --git a/backend/debug.py b/backend/debug.py index 2031557c1..3e0694cef 100644 --- a/backend/debug.py +++ b/backend/debug.py @@ -19,10 +19,6 @@ import asyncio import logging from dotenv import load_dotenv -from langchain_core.messages import HumanMessage -from langgraph.runtime import Runtime - -from deerflow.agents import make_lead_agent try: from prompt_toolkit import PromptSession @@ -34,18 +30,67 @@ except ImportError: load_dotenv() -logging.basicConfig( - level=logging.INFO, - format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", - datefmt="%Y-%m-%d %H:%M:%S", -) +_LOG_FMT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" +_LOG_DATEFMT = "%Y-%m-%d %H:%M:%S" + + +def _logging_level_from_config(name: str) -> int: + """Map ``config.yaml`` ``log_level`` string to a ``logging`` level constant.""" + mapping = logging.getLevelNamesMapping() + return mapping.get((name or "info").strip().upper(), logging.INFO) + + +def _setup_logging(log_level: str) -> None: + """Send application logs to ``debug.log`` at *log_level*; do not print them on the console. + + Idempotent: any pre-existing handlers on the root logger (e.g. installed by + ``logging.basicConfig`` in transitively imported modules) are removed so the + debug session output only lands in ``debug.log``. + """ + level = _logging_level_from_config(log_level) + root = logging.root + for h in list(root.handlers): + root.removeHandler(h) + h.close() + root.setLevel(level) + + file_handler = logging.FileHandler("debug.log", mode="a", encoding="utf-8") + file_handler.setLevel(level) + file_handler.setFormatter(logging.Formatter(_LOG_FMT, datefmt=_LOG_DATEFMT)) + root.addHandler(file_handler) + + +def _update_logging_level(log_level: str) -> None: + """Update the root logger and existing handlers to *log_level*.""" + level = _logging_level_from_config(log_level) + root = logging.root + root.setLevel(level) + for handler in root.handlers: + handler.setLevel(level) async def main(): + # Install file logging first so warnings emitted while loading config do not + # leak onto the interactive terminal via Python's lastResort handler. + _setup_logging("info") + + from deerflow.config import get_app_config + + app_config = get_app_config() + _update_logging_level(app_config.log_level) + + # Delay the rest of the deerflow imports until *after* logging is installed + # so that any import-time side effects (e.g. deerflow.agents starts a + # background skill-loader thread on import) emit logs to debug.log instead + # of leaking onto the interactive terminal via Python's lastResort handler. + from langchain_core.messages import HumanMessage + from langgraph.runtime import Runtime + + from deerflow.agents import make_lead_agent + from deerflow.mcp import initialize_mcp_tools + # Initialize MCP tools at startup try: - from deerflow.mcp import initialize_mcp_tools - await initialize_mcp_tools() except Exception as e: print(f"Warning: Failed to initialize MCP tools: {e}") @@ -71,6 +116,7 @@ async def main(): print("=" * 50) print("Lead Agent Debug Mode") print("Type 'quit' or 'exit' to stop") + print(f"Logs: debug.log (log_level={app_config.log_level})") if not _HAS_PROMPT_TOOLKIT: print("Tip: `uv sync --group dev` to enable arrow-key & history support") print("=" * 50)