Coverage for barbet/logging.py: 80.00%

35 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2026-08-17 06:22 +0000

1import sys 

2import logging 

3import io 

4from pathlib import Path 

5from rich.console import Console 

6 

7 

8class BarbetLogger(logging.Logger): 

9 def _log(self, level, msg, args, exc_info=None, extra=None, stack_info=False, stacklevel=1): 

10 if not isinstance(msg, str): 

11 try: 

12 buf = io.StringIO() 

13 Console(file=buf, force_terminal=False).print(msg) 

14 msg = buf.getvalue().rstrip() 

15 except Exception: 

16 msg = str(msg) 

17 super()._log(level, msg, args, exc_info=exc_info, extra=extra, stack_info=stack_info, stacklevel=stacklevel) 

18 

19 

20logging.setLoggerClass(BarbetLogger) 

21 

22 

23def setup_logger(output_dir: Path = None) -> logging.Logger: 

24 """Set up and return a logger configured with stdout and file handlers.""" 

25 if output_dir is None: 

26 output_dir = Path("output") 

27 output_dir = Path(output_dir) 

28 output_dir.mkdir(parents=True, exist_ok=True) 

29 log_file = output_dir / "barbet.log" 

30 

31 logger = logging.getLogger("barbet") 

32 logger.setLevel(logging.INFO) 

33 

34 file_handler_exists = any( 

35 isinstance(h, logging.FileHandler) and Path(h.baseFilename).resolve() == log_file.resolve() 

36 for h in logger.handlers 

37 ) 

38 

39 if not file_handler_exists: 

40 logger.handlers.clear() 

41 formatter = logging.Formatter("[%(asctime)s] %(levelname)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S") 

42 

43 # File handler 

44 fh = logging.FileHandler(log_file, mode="a") 

45 fh.setFormatter(formatter) 

46 logger.addHandler(fh) 

47 

48 # Console handler writing to stdout 

49 ch = logging.StreamHandler(sys.stdout) 

50 ch.setFormatter(formatter) 

51 logger.addHandler(ch) 

52 

53 return logger