Skip to content

Commit e7ad10e

Browse files
authored
✨ Add log to file. (#560)
* ✨ Add log to file. * ✨ Split logger into info and warning files. * 🐛 Fix tests (and code) to not have file loggers during tests. * 🐛 Create default directoryb before start logging.
1 parent b56a411 commit e7ad10e

File tree

5 files changed

+64
-7
lines changed

5 files changed

+64
-7
lines changed

src/muse/__init__.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,49 @@ def _create_logger(color: bool = True):
3434
return logger
3535

3636

37+
def add_file_logger() -> None:
38+
"""Adds a file logger to the main logger.
39+
40+
The file logger is split into two files: one for INFO and DEBUG messages, and one
41+
for WARNING messages and above to avoid cluttering the main log file and highlight
42+
potential issues.
43+
"""
44+
import logging
45+
from pathlib import Path
46+
47+
from .defaults import DEFAULT_OUTPUT_DIRECTORY
48+
49+
formatter = logging.Formatter(
50+
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
51+
)
52+
53+
DEFAULT_OUTPUT_DIRECTORY.mkdir(parents=True, exist_ok=True)
54+
55+
# Sets the warning log, for warnings and above
56+
warning_file = Path(DEFAULT_OUTPUT_DIRECTORY) / "muse_warning.log"
57+
if warning_file.exists():
58+
warning_file.unlink()
59+
60+
warning_file_handler = logging.FileHandler(warning_file)
61+
warning_file_handler.setLevel(logging.WARNING)
62+
warning_file_handler.setFormatter(formatter)
63+
warning_file_handler.filters = [lambda record: record.levelno > logging.INFO]
64+
65+
logging.getLogger("muse").addHandler(warning_file_handler)
66+
67+
# Sets the info log, for debug and info only
68+
info_file = Path(DEFAULT_OUTPUT_DIRECTORY) / "muse_info.log"
69+
if info_file.exists():
70+
info_file.unlink()
71+
72+
info_file_handler = logging.FileHandler(info_file)
73+
info_file_handler.setLevel(logging.DEBUG)
74+
info_file_handler.setFormatter(formatter)
75+
info_file_handler.filters = [lambda record: record.levelno <= logging.INFO]
76+
77+
logging.getLogger("muse").addHandler(info_file_handler)
78+
79+
3780
logger = _create_logger(os.environ.get("MUSE_COLOR_LOG") != "False")
3881
""" Main logger """
3982

src/muse/__main__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def muse_main(settings, model, copy):
3838
from logging import getLogger
3939
from pathlib import Path
4040

41-
from muse import examples
41+
from muse import add_file_logger, examples
4242
from muse.mca import MCA
4343
from muse.readers.toml import read_settings
4444

@@ -53,6 +53,7 @@ def muse_main(settings, model, copy):
5353
else:
5454
settings = read_settings(settings)
5555
getLogger("muse").setLevel(settings.log_level)
56+
add_file_logger()
5657
MCA.factory(settings).run()
5758

5859

src/muse/examples.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,21 @@ def available_examples() -> list[str]:
5151
return [d.stem for d in example_data_dir().iterdir() if d.is_dir()]
5252

5353

54-
def model(name: str = "default") -> MCA:
55-
"""Fully constructs a given example model."""
54+
def model(name: str = "default", test: bool = False) -> MCA:
55+
"""Fully constructs a given example model.
56+
57+
File logging is added if ``test`` is False.
58+
59+
Args:
60+
name: Name of the model to load.
61+
test: If True, the logging to file is not added.
62+
63+
Returns:
64+
The MCA model.
65+
"""
5666
from tempfile import TemporaryDirectory
5767

68+
from muse import add_file_logger
5869
from muse.readers.toml import read_settings
5970

6071
# we could modify the settings directly, but instead we use the copy_model function.
@@ -63,6 +74,8 @@ def model(name: str = "default") -> MCA:
6374
path = copy_model(name, tmpdir)
6475
settings = read_settings(path / "settings.toml")
6576
getLogger("muse").setLevel(settings.log_level)
77+
if not test:
78+
add_file_logger()
6679
return MCA.factory(settings)
6780

6881

tests/test_aggregoutput.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ def test_aggregate_sector():
99
"""
1010
from pandas import DataFrame, concat
1111

12-
mca = examples.model("multiple_agents")
12+
mca = examples.model("multiple_agents", test=True)
1313
year = [2020, 2025]
1414
sector_list = [sector for sector in mca.sectors if "preset" not in sector.name]
1515
agent_list = [list(a.agents) for a in sector_list]
@@ -43,7 +43,7 @@ def test_aggregate_sectors():
4343

4444
from muse.outputs.mca import _aggregate_sectors
4545

46-
mca = examples.model("multiple_agents")
46+
mca = examples.model("multiple_agents", test=True)
4747
year = [2020, 2025, 2030]
4848
sector_list = [sector for sector in mca.sectors if "preset" not in sector.name]
4949
agent_list = [list(a.agents) for a in sector_list]
@@ -83,7 +83,7 @@ def test_aggregate_sector_manyregions():
8383

8484
from muse.outputs.mca import _aggregate_sectors
8585

86-
mca = examples.model("multiple_agents")
86+
mca = examples.model("multiple_agents", test=True)
8787
residential = next(sector for sector in mca.sectors if sector.name == "residential")
8888
agents = list(residential.agents)
8989
agents[0].assets["region"] = "BELARUS"

tests/test_subsector.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def test_subsector_investing_aggregation():
3434
sector_list = ["residential", "power", "gas"]
3535

3636
for model in model_list:
37-
mca = examples.model(model)
37+
mca = examples.model(model, test=True)
3838
for sname in sector_list:
3939
agents = list(examples.sector(sname, model).agents)
4040
sector = next(sector for sector in mca.sectors if sector.name == sname)

0 commit comments

Comments
 (0)