Coverage for src/instawell/utils/logging_util.py: 97%

31 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-07 15:47 -0600

1# instawell/logging_util.py 

2import logging 

3from logging import FileHandler, Formatter 

4from pathlib import Path 

5 

6 

7def setup_experiment_logging( 

8 experiment_dir: Path, 

9 filename: str = "experiment.log", 

10 level: int = logging.INFO, 

11) -> Path: 

12 """ 

13 Attach a file handler for this experiment to the `instawell` logger. 

14 Safe to call multiple times; it won't duplicate handlers for the same file. 

15 """ 

16 experiment_dir.mkdir(parents=True, exist_ok=True) 

17 log_path = experiment_dir / filename 

18 

19 pkg_logger = logging.getLogger("instawell") 

20 pkg_logger.setLevel(level) 

21 

22 # Don't add duplicate handlers for the same file 

23 for h in pkg_logger.handlers: 

24 if isinstance(h, FileHandler) and getattr(h, "baseFilename", None) == str(log_path): 

25 return log_path 

26 

27 fh = FileHandler(log_path, encoding="utf-8") 

28 fh.setLevel(level) 

29 fh.setFormatter(Formatter("%(asctime)s [%(levelname)s] %(name)s: %(message)s")) 

30 pkg_logger.addHandler(fh) 

31 

32 pkg_logger.propagate = True 

33 return log_path 

34 

35 

36def ensure_experiment_context( 

37 experiment_name: str, 

38 *, 

39 experiments_root: str | Path = "experiments", 

40 log_to_file: bool = True, 

41 log_level: int = logging.INFO, 

42) -> Path: 

43 """ 

44 Common setup used by all step entrypoints. 

45 

46 - Creates a top-level experiments_root directory (default: ./experiments) 

47 - Creates this experiment's directory under it 

48 - Ensures a .gitignore in the experiment directory 

49 - Optionally wires all `instawell` loggers to experiment.log 

50 

51 Returns 

52 ------- 

53 experiment_dir : Path 

54 The path to this experiment's directory. 

55 """ 

56 # Resolve the base experiments directory 

57 experiments_root_path = Path(experiments_root) 

58 

59 # If relative, anchor to current working directory 

60 if not experiments_root_path.is_absolute(): 

61 experiments_root_path = Path.cwd() / experiments_root_path 

62 

63 experiments_root_path.mkdir(parents=True, exist_ok=True) 

64 

65 # Now the specific experiment directory, e.g. ./experiments/exp_001 

66 experiment_dir = experiments_root_path / experiment_name 

67 experiment_dir.mkdir(parents=True, exist_ok=True) 

68 

69 # Keep experiment artifacts out of git by default 

70 gitignore = experiment_dir / ".gitignore" 

71 if not gitignore.exists(): 

72 gitignore.write_text("*\n", encoding="utf-8") 

73 

74 if log_to_file: 

75 setup_experiment_logging(experiment_dir=experiment_dir, level=log_level) 

76 

77 # check if any .csv files exist in the experiment directory 

78 csv_files = list(experiment_dir.glob("*.csv")) 

79 

80 return experiment_dir