From 5bedd24e72e1fb0a809733bb4081201036cc04e9 Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Mon, 30 Dec 2024 14:52:15 +0100 Subject: [PATCH] Set default logger to stdout and add output directory setting option for file loggers (#139) * Set the default logger to a stdout stream, and add option to manually specify an output directory for file loggers * Add default None in input parameters of the default logger definition in MicroManagerCoupling * Set correct getter function name in the Config class and create the output dir before setting it in the handler * Add sys.stdout in SystemHandler * Add mock function of get_output_dir() * Add formatting for date and time in the default stdout logger * Add Changelog entry --- CHANGELOG.md | 1 + micro_manager/adaptivity/adaptivity.py | 15 +++++++++++--- micro_manager/config.py | 20 +++++++++++++++++++ micro_manager/micro_manager.py | 4 +--- micro_manager/tools/logging_wrapper.py | 18 ++++++++++++----- ...ger-config-global-adaptivity-parallel.json | 1 + tests/unit/test_adaptivity_parallel.py | 5 +++++ tests/unit/test_adaptivity_serial.py | 8 ++++++++ 8 files changed, 61 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af8bb05..e7eef2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## latest +- Set default logger to stdout and add output directory setting option for file loggers https://github.com/precice/micro-manager/pull/139 - Remove the `adaptivity_data` data structure and handle all adaptivity data internally https://github.com/precice/micro-manager/pull/137 - Improve logging by wrapping Python logger in a class https://github.com/precice/micro-manager/pull/133 - Refactor large parts of solve and adaptivity to group datasets and simplify handling https://github.com/precice/micro-manager/pull/135 diff --git a/micro_manager/adaptivity/adaptivity.py b/micro_manager/adaptivity/adaptivity.py index 4feb63b..bcf156e 100644 --- a/micro_manager/adaptivity/adaptivity.py +++ b/micro_manager/adaptivity/adaptivity.py @@ -5,6 +5,7 @@ from math import exp from typing import Callable from warnings import warn +import subprocess from micro_manager.tools.logging_wrapper import Logger import numpy as np @@ -37,9 +38,17 @@ def __init__(self, configurator, rank) -> None: configurator.get_adaptivity_similarity_measure() ) - self._metrics_logger = Logger( - "Adaptivity", "adaptivity-metrics.csv", rank, csv_logger=True - ) + output_dir = configurator.get_output_dir() + + if output_dir is not None: + subprocess.run(["mkdir", "-p", output_dir]) # Create output directory + self._metrics_logger = Logger( + __name__, output_dir + "/adaptivity-metrics.csv", rank, csv_logger=True + ) + else: + self._metrics_logger = Logger( + __name__, "adaptivity-metrics.csv", rank=rank, csv_logger=True + ) self._metrics_logger.log_info_one_rank( "Time Window,Avg Active Sims,Avg Inactive Sims,Max Active,Max Inactive" diff --git a/micro_manager/config.py b/micro_manager/config.py index 05eb310..1b31bdc 100644 --- a/micro_manager/config.py +++ b/micro_manager/config.py @@ -58,6 +58,8 @@ def __init__(self, config_file_name): self._postprocessing_file_name = None self._initialize_once = False + self._output_dir = None + def set_logger(self, logger): """ Set the logger for the Config class. @@ -91,6 +93,13 @@ def _read_json(self, config_file_name): .replace(".py", "") ) + try: + self._output_dir = self._data["output_dir"] + except BaseException: + self._logger.log_info_one_rank( + "No output directory provided. Output (including logging) will be saved in the current working directory." + ) + try: self._write_data_names = self._data["coupling_params"]["write_data_names"] assert isinstance( @@ -640,3 +649,14 @@ def create_single_sim_object(self): True if initialization is done only once, False otherwise. """ return self._initialize_once + + def get_output_dir(self): + """ + Get the name of the output directory. + + Returns + ------- + output_dir : string + Name of the output folder. + """ + return self._output_dir diff --git a/micro_manager/micro_manager.py b/micro_manager/micro_manager.py index 9636b83..2a45cce 100644 --- a/micro_manager/micro_manager.py +++ b/micro_manager/micro_manager.py @@ -57,9 +57,7 @@ def __init__(self, config_file: str) -> None: """ super().__init__(config_file) - self._logger = Logger( - "MicroManagerCoupling", "micro-manager-coupling.log", self._rank - ) + self._logger = Logger(__name__, None, self._rank) self._config.set_logger(self._logger) self._config.read_json_micro_manager() diff --git a/micro_manager/tools/logging_wrapper.py b/micro_manager/tools/logging_wrapper.py index 8ef7c6b..3d5f865 100644 --- a/micro_manager/tools/logging_wrapper.py +++ b/micro_manager/tools/logging_wrapper.py @@ -3,6 +3,7 @@ Provides a logging wrapper for the Micro Manager classes. """ import logging +import sys class Logger: @@ -10,7 +11,9 @@ class Logger: Provides a logging wrapper for the Micro Manager classes. """ - def __init__(self, name, log_file, rank=0, level=logging.INFO, csv_logger=False): + def __init__( + self, name, log_file=None, rank=0, level=logging.INFO, csv_logger=False + ): """ Set up a logger. @@ -19,7 +22,7 @@ def __init__(self, name, log_file, rank=0, level=logging.INFO, csv_logger=False) name : string Name of the logger. log_file : string - Name of the log file. + Name of the log file (default is None). rank : int, optional Rank of the logger (default is 0). level : int, optional @@ -30,16 +33,21 @@ def __init__(self, name, log_file, rank=0, level=logging.INFO, csv_logger=False) self._rank = rank - handler = logging.FileHandler(log_file) + if log_file is None: + handler = logging.StreamHandler(sys.stdout) + else: + handler = logging.FileHandler(log_file) + handler.setLevel(level) if csv_logger: formatter = logging.Formatter("%(message)s") else: formatter = logging.Formatter( - "[" + "(" + str(self._rank) - + "] %(asctime)s - %(name)s - %(levelname)s - %(message)s" + + ") %(asctime)s - %(name)s - %(levelname)s - %(message)s", + datefmt="%m/%d/%Y %I:%M:%S %p", ) handler.setFormatter(formatter) diff --git a/tests/integration/test_unit_cube/micro-manager-config-global-adaptivity-parallel.json b/tests/integration/test_unit_cube/micro-manager-config-global-adaptivity-parallel.json index bd2c738..cc2cd67 100644 --- a/tests/integration/test_unit_cube/micro-manager-config-global-adaptivity-parallel.json +++ b/tests/integration/test_unit_cube/micro-manager-config-global-adaptivity-parallel.json @@ -1,5 +1,6 @@ { "micro_file_name": "micro_dummy", + "output_dir": "adaptivity_output", "coupling_params": { "precice_config_file_name": "precice-config.xml", "macro_mesh_name": "macro-cube-mesh", diff --git a/tests/unit/test_adaptivity_parallel.py b/tests/unit/test_adaptivity_parallel.py index 3db3294..25c01ab 100644 --- a/tests/unit/test_adaptivity_parallel.py +++ b/tests/unit/test_adaptivity_parallel.py @@ -30,6 +30,8 @@ def test_update_inactive_sims_global_adaptivity(self): configurator = MagicMock() configurator.get_adaptivity_similarity_measure = MagicMock(return_value="L1") + configurator.get_output_dir = MagicMock(return_value="output_dir") + adaptivity_controller = GlobalAdaptivityCalculator( configurator, 5, global_ids, rank=self._rank, comm=self._comm ) @@ -108,6 +110,7 @@ def test_update_all_active_sims_global_adaptivity(self): configurator.get_adaptivity_refining_const = MagicMock(return_value=0.05) configurator.get_adaptivity_coarsening_const = MagicMock(return_value=0.2) configurator.get_adaptivity_similarity_measure = MagicMock(return_value="L2rel") + configurator.get_output_dir = MagicMock(return_value="output_dir") adaptivity_controller = GlobalAdaptivityCalculator( configurator, 5, global_ids, rank=self._rank, comm=self._comm @@ -175,6 +178,8 @@ def test_communicate_micro_output(self): configurator = MagicMock() configurator.get_adaptivity_similarity_measure = MagicMock(return_value="L1") + configurator.get_output_dir = MagicMock(return_value="output_dir") + adaptivity_controller = GlobalAdaptivityCalculator( configurator, 5, global_ids, rank=self._rank, comm=self._comm ) diff --git a/tests/unit/test_adaptivity_serial.py b/tests/unit/test_adaptivity_serial.py index de9e71b..6853c24 100644 --- a/tests/unit/test_adaptivity_serial.py +++ b/tests/unit/test_adaptivity_serial.py @@ -68,6 +68,8 @@ def test_get_similarity_dists(self): """ configurator = MagicMock() configurator.get_adaptivity_similarity_measure = MagicMock(return_value="L1") + configurator.get_output_dir = MagicMock(return_value="output_dir") + adaptivity_controller = AdaptivityCalculator(configurator, 0) adaptivity_controller._hist_param = 0.5 adaptivity_controller._adaptivity_data_names = [ @@ -102,6 +104,8 @@ def test_update_active_sims(self): """ configurator = MagicMock() configurator.get_adaptivity_similarity_measure = MagicMock(return_value="L1") + configurator.get_output_dir = MagicMock(return_value="output_dir") + adaptivity_controller = AdaptivityCalculator(configurator, 0) adaptivity_controller._refine_const = self._refine_const adaptivity_controller._coarse_const = self._coarse_const @@ -208,6 +212,8 @@ def test_associate_active_to_inactive(self): """ configurator = MagicMock() configurator.get_adaptivity_similarity_measure = MagicMock(return_value="L1") + configurator.get_output_dir = MagicMock(return_value="output_dir") + adaptivity_controller = AdaptivityCalculator(configurator, 0) adaptivity_controller._refine_const = self._refine_const adaptivity_controller._coarse_const = self._coarse_const @@ -235,6 +241,8 @@ def test_update_inactive_sims_local_adaptivity(self): """ configurator = MagicMock() configurator.get_adaptivity_similarity_measure = MagicMock(return_value="L1") + configurator.get_output_dir = MagicMock(return_value="output_dir") + adaptivity_controller = LocalAdaptivityCalculator( configurator, 0, MagicMock(), 5 )