Spaces:
Running
Running
File size: 2,850 Bytes
e28d52e f8381b8 e28d52e f8381b8 e28d52e f8381b8 1ea0743 f8381b8 1ea0743 f8381b8 e28d52e f8381b8 | 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 | """Centralised logging for MealGraph.
Agents and tools used to ``print`` directly to stdout. That worked in a notebook
but coupled the agentic system to the I/O layer. This module provides a single
``get_logger`` entrypoint so:
* user-mode emoji status lines flow through ``logger.info`` (visible by default),
* debug-mode raw LLM dumps flow through ``logger.debug`` (hidden unless
``settings.debug_mode`` is True),
* later phases can attach extra handlers (SSE event stream for the API, JSON
file handler for trace persistence, etc.) without touching agent code.
Idempotent: calling :func:`configure_logging` more than once is a no-op unless
``force=True``.
"""
from __future__ import annotations
import logging
import sys
from config import get_settings
_BASE = "mealgraph"
_CONFIGURED = False
def configure_logging(*, force: bool = False) -> None:
"""Wire up the ``mealgraph`` logger tree.
Reads ``settings.debug_mode`` to choose between INFO (user mode) and DEBUG.
Safe to call from library code; only the first call attaches a handler.
Reconfigures ``sys.stdout`` to UTF-8 when possible (Windows defaults to
cp1252 which chokes on the emoji in agent status messages). Falls back to
a 'replace' error handler so a stray glyph never crashes a log call.
"""
global _CONFIGURED
if _CONFIGURED and not force:
return
settings = get_settings()
level = logging.DEBUG if settings.debug_mode else logging.INFO
# Best-effort: re-encode stdout to UTF-8 so the emoji status lines render.
reconf = getattr(sys.stdout, "reconfigure", None)
if callable(reconf):
try:
reconf(encoding="utf-8", errors="replace")
except Exception: # pragma: no cover - depends on stream type
pass
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(level)
handler.setFormatter(logging.Formatter("%(message)s"))
root = logging.getLogger(_BASE)
root.handlers = [handler]
root.setLevel(level)
root.propagate = False
_CONFIGURED = True
def get_logger(name: str) -> logging.Logger:
"""Return a sub-logger under the ``mealgraph`` namespace.
Conventional names: ``agents.coach``, ``agents.medical``, ``tools.computation``,
``utils.api_pool``.
"""
if not _CONFIGURED:
configure_logging()
return logging.getLogger(f"{_BASE}.{name}")
def refresh_level() -> None:
"""Re-read ``settings.debug_mode`` and adjust handler levels in place.
Call this after toggling debug mode at runtime.
"""
settings = get_settings()
level = logging.DEBUG if settings.debug_mode else logging.INFO
root = logging.getLogger(_BASE)
root.setLevel(level)
for handler in root.handlers:
handler.setLevel(level)
__all__ = ["configure_logging", "get_logger", "refresh_level"]
|