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

1"""Application logging setup (Qt-independent). 

2 

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 

11 

12_CONFIGURED = False 

13 

14 

15def log_path(): 

16 """Return the path to the application log file (dir created on demand).""" 

17 from . import config 

18 

19 return config.config_dir() / "app.log" 

20 

21 

22def configure_logging(level=logging.INFO): 

23 """Install a rotating file handler for the package logger. 

24 

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 

49 

50 

51def get_logger(name=None): 

52 """Return a logger under the ``paintmaskanimator`` namespace. 

53 

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}")