Coverage for paintmaskanimator/logging_setup.py: 94%
29 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-02 04:50 +0000
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-02 04:50 +0000
1"""Application logging setup (Qt-independent).
3Provides a single ``configure_logging()`` entry point that installs a rotating
4file handler under the per-user config directory, plus a ``get_logger()``
5helper so modules can obtain a namespaced logger without repeating the package
6prefix. Keeping this separate from ``__main__`` means non-GUI code paths (and
7tests) can log without pulling in Qt.
8"""
9import logging
10import logging.handlers
12_CONFIGURED = False
15def log_path():
16 """Return the path to the application log file (dir created on demand)."""
17 from . import config
19 return config.config_dir() / "app.log"
22def configure_logging(level=logging.INFO):
23 """Install a rotating file handler for the package logger.
25 Idempotent: repeated calls are no-ops so importing modules or re-entering
26 ``main()`` never stacks duplicate handlers. Falls back silently to a
27 console handler if the log file cannot be opened (e.g. read-only install
28 dir), since logging must never itself crash the app.
29 """
30 global _CONFIGURED
31 if _CONFIGURED:
32 return
33 logger = logging.getLogger("paintmaskanimator")
34 logger.setLevel(level)
35 formatter = logging.Formatter(
36 "%(asctime)s %(levelname)s %(name)s: %(message)s"
37 )
38 handler = None
39 try:
40 path = log_path()
41 handler = logging.handlers.RotatingFileHandler(
42 path, maxBytes=1_000_000, backupCount=3, encoding="utf-8"
43 )
44 except OSError:
45 handler = logging.StreamHandler()
46 handler.setFormatter(formatter)
47 logger.addHandler(handler)
48 _CONFIGURED = True
51def get_logger(name=None):
52 """Return a logger under the ``paintmaskanimator`` namespace.
54 ``name`` is usually ``__name__``; the package prefix is stripped so the
55 logger name stays short and consistent regardless of import path.
56 """
57 base = "paintmaskanimator"
58 if not name or name == base:
59 return logging.getLogger(base)
60 suffix = name
61 if name.startswith(base + "."):
62 suffix = name[len(base) + 1:]
63 return logging.getLogger(f"{base}.{suffix}")