From 91625a11ab8797dc644d0fffafe64c1e4e4518ae Mon Sep 17 00:00:00 2001 From: Wenqi Li Date: Wed, 22 Feb 2023 22:36:33 +0000 Subject: [PATCH 1/8] update stathandler Signed-off-by: Wenqi Li --- monai/handlers/stats_handler.py | 10 +++++++--- tests/test_handler_stats.py | 3 ++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/monai/handlers/stats_handler.py b/monai/handlers/stats_handler.py index 8471a87e8e..0eeae115cf 100644 --- a/monai/handlers/stats_handler.py +++ b/monai/handlers/stats_handler.py @@ -135,10 +135,14 @@ def attach(self, engine: Engine) -> None: """ if self.name is None: self.logger = engine.logger - if self.logger.getEffectiveLevel() > logging.INFO or logging.root.getEffectiveLevel() > logging.INFO: + if self.logger.getEffectiveLevel() > logging.INFO: + suggested = f"\n\nimport logging\nlogging.getLogger('{self.logger.name}').setLevel(logging.INFO)" + if self.logger.name != engine.logger.name: + suggested += f"\nlogging.getLogger('{engine.logger.name}').setLevel(logging.INFO)" + suggested += "\n\n" warnings.warn( - "the effective log level of engine logger or RootLogger is higher than INFO, may not record log," - " please call `logging.basicConfig(stream=sys.stdout, level=logging.INFO)` to enable it." + f"the effective log level of {self.logger.name} higher than INFO, StatsHandler may not generate logs," + f" please use the following code before running the engine to enable it: {suggested}" ) if self.iteration_log and not engine.has_event_handler(self.iteration_completed, Events.ITERATION_COMPLETED): event = Events.ITERATION_COMPLETED diff --git a/tests/test_handler_stats.py b/tests/test_handler_stats.py index 84477f9221..6be827c3bd 100644 --- a/tests/test_handler_stats.py +++ b/tests/test_handler_stats.py @@ -254,7 +254,8 @@ def _train_func(engine, batch): # set up testing handler stats_handler = StatsHandler(name=None, tag_name=key_to_print) - stats_handler.attach(engine) + with self.assertWarns(Warning): # engine logging level warn + stats_handler.attach(engine) # leverage `engine.logger` to print info engine.logger.setLevel(logging.INFO) level = logging.root.getEffectiveLevel() From 45f2513a0720b95280b6335e49cca819c1c1beff Mon Sep 17 00:00:00 2001 From: Wenqi Li Date: Wed, 22 Feb 2023 22:43:10 +0000 Subject: [PATCH 2/8] update docstring Signed-off-by: Wenqi Li --- monai/handlers/stats_handler.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/monai/handlers/stats_handler.py b/monai/handlers/stats_handler.py index 0eeae115cf..af57f705d3 100644 --- a/monai/handlers/stats_handler.py +++ b/monai/handlers/stats_handler.py @@ -41,9 +41,8 @@ class StatsHandler: Note that if `name` arg is None, will leverage `engine.logger` as default logger directly, otherwise, get logger from `logging.getLogger(name)`, we can setup a logger outside first with the same `name`. - As the default log level of `RootLogger` is `WARNING`, may need to call - `logging.basicConfig(stream=sys.stdout, level=logging.INFO)` before running this handler to enable - the stats logging. + As the default log level is `WARNING`, it's recommended to call + `logging.getLogger(name).setLevel(logging.INFO)` before running this handler to enable the stats logging. Default behaviors: - When EPOCH_COMPLETED, logs ``engine.state.metrics`` using ``self.logger``. @@ -52,7 +51,7 @@ class StatsHandler: Usage example:: - logging.basicConfig(stream=sys.stdout, level=logging.INFO) + logging.getLogger("train_stats").setLevel(logging.INFO) trainer = SupervisedTrainer(...) StatsHandler(name="train_stats").attach(trainer) @@ -142,7 +141,7 @@ def attach(self, engine: Engine) -> None: suggested += "\n\n" warnings.warn( f"the effective log level of {self.logger.name} higher than INFO, StatsHandler may not generate logs," - f" please use the following code before running the engine to enable it: {suggested}" + f"\nplease use the following code before running the engine to enable it: {suggested}" ) if self.iteration_log and not engine.has_event_handler(self.iteration_completed, Events.ITERATION_COMPLETED): event = Events.ITERATION_COMPLETED From 170a8caa108302403a4c26f83ee6ec515e43862f Mon Sep 17 00:00:00 2001 From: Wenqi Li Date: Fri, 24 Feb 2023 08:08:13 +0000 Subject: [PATCH 3/8] deprecate name=None default Signed-off-by: Wenqi Li --- monai/handlers/stats_handler.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/monai/handlers/stats_handler.py b/monai/handlers/stats_handler.py index af57f705d3..ba7dfd2736 100644 --- a/monai/handlers/stats_handler.py +++ b/monai/handlers/stats_handler.py @@ -19,7 +19,7 @@ import torch from monai.config import IgniteInfo -from monai.utils import is_scalar, min_version, optional_import +from monai.utils import deprecated_arg_default, is_scalar, min_version, optional_import Events, _ = optional_import("ignite.engine", IgniteInfo.OPT_IMPORT_VERSION, min_version, "Events") if TYPE_CHECKING: @@ -63,6 +63,14 @@ class StatsHandler: """ + @deprecated_arg_default( + "name", + old_default=None, + new_default="StatsHandler", + since="1.1", + replaced="1.3", + msg_suffix="the default logger is 'StatsHandler'.", + ) def __init__( self, iteration_log: bool | Callable[[Engine, int], bool] = True, From 0021ae6b805798e70f041e7a0dab0b2768fa3df3 Mon Sep 17 00:00:00 2001 From: Wenqi Li Date: Sat, 25 Feb 2023 15:49:03 +0000 Subject: [PATCH 4/8] update based on comments Signed-off-by: Wenqi Li --- monai/handlers/stats_handler.py | 28 ++++++++++++++++------------ tests/test_handler_stats.py | 1 + 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/monai/handlers/stats_handler.py b/monai/handlers/stats_handler.py index ba7dfd2736..e54b4907a1 100644 --- a/monai/handlers/stats_handler.py +++ b/monai/handlers/stats_handler.py @@ -18,6 +18,7 @@ import torch +import monai from monai.config import IgniteInfo from monai.utils import deprecated_arg_default, is_scalar, min_version, optional_import @@ -39,10 +40,11 @@ class StatsHandler: It can be used for any Ignite Engine(trainer, validator and evaluator). And it can support logging for epoch level and iteration level with pre-defined loggers. - Note that if `name` arg is None, will leverage `engine.logger` as default logger directly, otherwise, - get logger from `logging.getLogger(name)`, we can setup a logger outside first with the same `name`. - As the default log level is `WARNING`, it's recommended to call - `logging.getLogger(name).setLevel(logging.INFO)` before running this handler to enable the stats logging. + Note that if ``name`` is None, this class will leverage `engine.logger` as the logger, otherwise, + ``logging.getLogger(name)`` is used. In both cases, it's important to make sure that the logging level is at least + ``INFO``. To change the level of logging, please call ``import ignite; ignite.utils.setup_logger(name)`` + (when ``name`` is not None) or ``engine.logger = ignite.utils.setup_logger(engine.logger.name)`` + (when ``name`` is None) before running the engine with this handler attached. Default behaviors: - When EPOCH_COMPLETED, logs ``engine.state.metrics`` using ``self.logger``. @@ -51,12 +53,14 @@ class StatsHandler: Usage example:: - logging.getLogger("train_stats").setLevel(logging.INFO) + import ignite + import monai - trainer = SupervisedTrainer(...) - StatsHandler(name="train_stats").attach(trainer) + ignite.utils.setup_logger("train_stats") + trainer = ignite.engine.Engine(lambda x, y: [0.0]) # an example trainer + monai.handlers.StatsHandler(name="train_stats").attach(trainer) - trainer.run() + trainer.run(range(3), max_epochs=4) More details of example is available in the tutorial: https://github.com/Project-MONAI/tutorials/blob/master/modules/engines/unet_training_dict.py. @@ -129,7 +133,7 @@ def __init__( self.state_attributes = state_attributes self.tag_name = tag_name self.key_var_format = key_var_format - self.logger = logging.getLogger(name) # if `name` is None, will default to `engine.logger` when attached + self.logger = logging.getLogger(name) # if `name` is None, will default to `engine.logger` when attached self.name = name def attach(self, engine: Engine) -> None: @@ -143,12 +147,12 @@ def attach(self, engine: Engine) -> None: if self.name is None: self.logger = engine.logger if self.logger.getEffectiveLevel() > logging.INFO: - suggested = f"\n\nimport logging\nlogging.getLogger('{self.logger.name}').setLevel(logging.INFO)" + suggested = f"\n\nimport ignite\nignite.utils.setup_logger('{self.logger.name}')" if self.logger.name != engine.logger.name: - suggested += f"\nlogging.getLogger('{engine.logger.name}').setLevel(logging.INFO)" + suggested += f"\nignite.utils.setup_logger('{engine.logger.name}')" suggested += "\n\n" warnings.warn( - f"the effective log level of {self.logger.name} higher than INFO, StatsHandler may not generate logs," + f"the effective log level of {self.logger.name} is higher than INFO, StatsHandler may not output logs," f"\nplease use the following code before running the engine to enable it: {suggested}" ) if self.iteration_log and not engine.has_event_handler(self.iteration_completed, Events.ITERATION_COMPLETED): diff --git a/tests/test_handler_stats.py b/tests/test_handler_stats.py index 6be827c3bd..1842e08635 100644 --- a/tests/test_handler_stats.py +++ b/tests/test_handler_stats.py @@ -254,6 +254,7 @@ def _train_func(engine, batch): # set up testing handler stats_handler = StatsHandler(name=None, tag_name=key_to_print) + engine.logger.setLevel(logging.WARNING) with self.assertWarns(Warning): # engine logging level warn stats_handler.attach(engine) # leverage `engine.logger` to print info From dbd872544e4f1a6c2bdd85cc5a867d63f5be0bd7 Mon Sep 17 00:00:00 2001 From: Wenqi Li Date: Sat, 25 Feb 2023 16:44:18 +0000 Subject: [PATCH 5/8] update to use monai.apps.get_logger Signed-off-by: Wenqi Li --- monai/apps/utils.py | 10 ++++++---- monai/handlers/stats_handler.py | 3 +-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/monai/apps/utils.py b/monai/apps/utils.py index a5d729368c..a36caf2e66 100644 --- a/monai/apps/utils.py +++ b/monai/apps/utils.py @@ -58,13 +58,15 @@ def get_logger( (https://docs.python.org/3/library/logging.html#formatter-objects). `logger_handler` can be used to add an additional handler. """ + adds_stdout_handler = module_name is not None and module_name not in logging.root.manager.loggerDict logger = logging.getLogger(module_name) logger.propagate = False logger.setLevel(logging.INFO) - handler = logging.StreamHandler(sys.stdout) - formatter = logging.Formatter(fmt=fmt, datefmt=datefmt) - handler.setFormatter(formatter) - logger.addHandler(handler) + if adds_stdout_handler: # don't add multiple stdout or add to the root + handler = logging.StreamHandler(sys.stdout) + formatter = logging.Formatter(fmt=fmt, datefmt=datefmt) + handler.setFormatter(formatter) + logger.addHandler(handler) if logger_handler is not None: logger.addHandler(logger_handler) return logger diff --git a/monai/handlers/stats_handler.py b/monai/handlers/stats_handler.py index e54b4907a1..ddf794ebfa 100644 --- a/monai/handlers/stats_handler.py +++ b/monai/handlers/stats_handler.py @@ -56,7 +56,6 @@ class StatsHandler: import ignite import monai - ignite.utils.setup_logger("train_stats") trainer = ignite.engine.Engine(lambda x, y: [0.0]) # an example trainer monai.handlers.StatsHandler(name="train_stats").attach(trainer) @@ -133,7 +132,7 @@ def __init__( self.state_attributes = state_attributes self.tag_name = tag_name self.key_var_format = key_var_format - self.logger = logging.getLogger(name) # if `name` is None, will default to `engine.logger` when attached + self.logger = monai.apps.get_logger(name) # if `name` is None, will default to `engine.logger` when attached self.name = name def attach(self, engine: Engine) -> None: From 035f1448cfd2bf97b816a1bffceb39b26c81f0b3 Mon Sep 17 00:00:00 2001 From: Wenqi Li Date: Sat, 25 Feb 2023 17:16:05 +0000 Subject: [PATCH 6/8] update docstring Signed-off-by: Wenqi Li --- monai/handlers/stats_handler.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monai/handlers/stats_handler.py b/monai/handlers/stats_handler.py index ddf794ebfa..60b9397b00 100644 --- a/monai/handlers/stats_handler.py +++ b/monai/handlers/stats_handler.py @@ -43,7 +43,7 @@ class StatsHandler: Note that if ``name`` is None, this class will leverage `engine.logger` as the logger, otherwise, ``logging.getLogger(name)`` is used. In both cases, it's important to make sure that the logging level is at least ``INFO``. To change the level of logging, please call ``import ignite; ignite.utils.setup_logger(name)`` - (when ``name`` is not None) or ``engine.logger = ignite.utils.setup_logger(engine.logger.name)`` + (when ``name`` is not None) or ``engine.logger = ignite.utils.setup_logger(engine.logger.name, reset=True)`` (when ``name`` is None) before running the engine with this handler attached. Default behaviors: @@ -146,9 +146,9 @@ def attach(self, engine: Engine) -> None: if self.name is None: self.logger = engine.logger if self.logger.getEffectiveLevel() > logging.INFO: - suggested = f"\n\nimport ignite\nignite.utils.setup_logger('{self.logger.name}')" + suggested = f"\n\nimport ignite\nignite.utils.setup_logger('{self.logger.name}', reset=True)" if self.logger.name != engine.logger.name: - suggested += f"\nignite.utils.setup_logger('{engine.logger.name}')" + suggested += f"\nignite.utils.setup_logger('{engine.logger.name}', reset=True)" suggested += "\n\n" warnings.warn( f"the effective log level of {self.logger.name} is higher than INFO, StatsHandler may not output logs," From 4162f0185d9efd19adda4dc8c0e80948f39e61da Mon Sep 17 00:00:00 2001 From: Wenqi Li Date: Sat, 25 Feb 2023 17:31:28 +0000 Subject: [PATCH 7/8] update deprecated msg Signed-off-by: Wenqi Li --- monai/handlers/stats_handler.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/monai/handlers/stats_handler.py b/monai/handlers/stats_handler.py index 60b9397b00..01be635e35 100644 --- a/monai/handlers/stats_handler.py +++ b/monai/handlers/stats_handler.py @@ -66,14 +66,7 @@ class StatsHandler: """ - @deprecated_arg_default( - "name", - old_default=None, - new_default="StatsHandler", - since="1.1", - replaced="1.3", - msg_suffix="the default logger is 'StatsHandler'.", - ) + @deprecated_arg_default("name", old_default=None, new_default="StatsHandler", since="1.1", replaced="1.3") def __init__( self, iteration_log: bool | Callable[[Engine, int], bool] = True, From 806f28ef567134980e59fbd64f00400ea2a8da33 Mon Sep 17 00:00:00 2001 From: Wenqi Li Date: Sat, 25 Feb 2023 17:41:52 +0000 Subject: [PATCH 8/8] fixes mypy issue Signed-off-by: Wenqi Li --- monai/handlers/stats_handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monai/handlers/stats_handler.py b/monai/handlers/stats_handler.py index 01be635e35..4e4ad78798 100644 --- a/monai/handlers/stats_handler.py +++ b/monai/handlers/stats_handler.py @@ -18,7 +18,7 @@ import torch -import monai +from monai.apps import get_logger from monai.config import IgniteInfo from monai.utils import deprecated_arg_default, is_scalar, min_version, optional_import @@ -125,7 +125,7 @@ def __init__( self.state_attributes = state_attributes self.tag_name = tag_name self.key_var_format = key_var_format - self.logger = monai.apps.get_logger(name) # if `name` is None, will default to `engine.logger` when attached + self.logger = get_logger(name) # type: ignore self.name = name def attach(self, engine: Engine) -> None: