diff --git a/paperforge/logging_config.py b/paperforge/logging_config.py new file mode 100644 index 00000000..843bbe22 --- /dev/null +++ b/paperforge/logging_config.py @@ -0,0 +1,69 @@ +"""PaperForge Lite — logging configuration single entry point. + +Call ``configure_logging(verbose)`` once at CLI startup before command dispatch. +All worker and command modules use ``logging.getLogger(__name__)`` for +diagnostic/trace/error output. + +Environment: + PAPERFORGE_LOG_LEVEL — default log level (DEBUG/INFO/WARNING/ERROR). + Invalid values silently fall back to WARNING. +""" + +import logging +import os +import sys + + +def configure_logging(verbose: bool = False) -> None: + """Configure the root ``paperforge`` logger. + + Sets up a StreamHandler writing to stderr with a ``LEVEL:name:message`` + format string. Idempotent — does nothing if handlers are already + configured (guards against double ``basicConfig()`` or ``dictConfig()``). + + Args: + verbose: If True, forces ``DEBUG`` level regardless of env var. + """ + logger = logging.getLogger("paperforge") + + # Idempotency guard: if handlers already exist, only adjust level. + # This prevents duplicate log lines from multiple configure_logging calls. + if logger.handlers: + if verbose: + logger.setLevel(logging.DEBUG) + return + + # Resolve log level from environment or default + level_name = os.environ.get("PAPERFORGE_LOG_LEVEL", "INFO").strip().upper() + level = getattr(logging, level_name, None) + if not isinstance(level, int) or level_name not in ("DEBUG", "INFO", "WARNING", "ERROR"): + level = logging.WARNING + + if verbose: + level = logging.DEBUG + + logger.setLevel(level) + + handler = logging.StreamHandler(stream=sys.stderr) + handler.setLevel(logging.DEBUG) # handler-level always DEBUG; logger controls filtering + formatter = logging.Formatter( + fmt="%(levelname)s:%(name)s:%(message)s", + datefmt="%H:%M:%S", + ) + handler.setFormatter(formatter) + logger.addHandler(handler) + + +def get_paperforge_logger() -> logging.Logger: + """Convenience accessor for the ``paperforge`` logger. + + Equivalent to ``logging.getLogger("paperforge")`` but provides a single + well-known reference point for early-boot logging before + ``configure_logging()`` has been called. + + Callers in worker/command modules should still prefer the per-module + ``logger = logging.getLogger(__name__)`` pattern. This function exists + for places (like ``cli.py:main()``) that need the root logger before + per-module loggers are established. + """ + return logging.getLogger("paperforge")