From b62f7ed6fbd50dba4d0813e35e9a801d21a8adbb Mon Sep 17 00:00:00 2001
From: Paul Andrey <paul.andrey@inria.fr>
Date: Fri, 31 Mar 2023 15:42:17 +0200
Subject: [PATCH] Add a custom logging level to filter multiprocessing outputs.

---
 declearn/main/utils/_training.py |  5 +++--
 declearn/quickrun/run.py         | 11 +++++++++--
 declearn/utils/__init__.py       | 12 ++++++++++--
 declearn/utils/_logging.py       | 12 ++++++++++++
 4 files changed, 34 insertions(+), 6 deletions(-)

diff --git a/declearn/main/utils/_training.py b/declearn/main/utils/_training.py
index 76e46bcc..901d9538 100644
--- a/declearn/main/utils/_training.py
+++ b/declearn/main/utils/_training.py
@@ -33,7 +33,7 @@ from declearn.metrics import MeanMetric, Metric, MetricInputType, MetricSet
 from declearn.model.api import Model
 from declearn.optimizer import Optimizer
 from declearn.typing import Batch
-from declearn.utils import get_logger
+from declearn.utils import LOGGING_LEVEL_MAJOR, get_logger
 
 __all__ = [
     "TrainingManager",
@@ -344,7 +344,8 @@ class TrainingManager:
         effort = constraints.get_values()
         result = self.metrics.get_result()
         states = self.metrics.get_states()
-        self.logger.info(
+        self.logger.log(
+            LOGGING_LEVEL_MAJOR,
             "Local scalar evaluation metrics: %s",
             {k: v for k, v in result.items() if isinstance(v, float)},
         )
diff --git a/declearn/quickrun/run.py b/declearn/quickrun/run.py
index bb9969e3..6d653a63 100644
--- a/declearn/quickrun/run.py
+++ b/declearn/quickrun/run.py
@@ -33,6 +33,7 @@ using privided model and data, and stores its result in the same folder.
 
 import argparse
 import importlib
+import logging
 import os
 import re
 import textwrap
@@ -51,7 +52,7 @@ from declearn.quickrun._config import (
 from declearn.quickrun._parser import parse_data_folder
 from declearn.quickrun._split_data import split_data
 from declearn.test_utils import make_importable
-from declearn.utils import run_as_processes
+from declearn.utils import LOGGING_LEVEL_MAJOR, get_logger, run_as_processes
 
 __all__ = ["quickrun"]
 
@@ -85,8 +86,9 @@ def run_server(
     else:
         checkpoint = os.path.join(folder, "result")
     checkpoint = os.path.join(checkpoint, "server")
+    logger = get_logger("Server", fpath=os.path.join(checkpoint, "logger.txt"))
     server = FederatedServer(
-        model, network, optim, expe_config.metrics, checkpoint
+        model, network, optim, expe_config.metrics, checkpoint, logger
     )
     server.run(config)
 
@@ -110,6 +112,11 @@ def run_client(
     else:
         checkpoint = os.path.join(folder, "result")
     checkpoint = os.path.join(checkpoint, name)
+    # Set up a logger: write everything to file, but filter console outputs.
+    logger = get_logger(name, fpath=os.path.join(checkpoint, "logs.txt"))
+    for handler in logger.handlers:
+        if isinstance(handler, logging.StreamHandler):
+            handler.setLevel(LOGGING_LEVEL_MAJOR)
     # Wrap train and validation data as Dataset objects.
     train = InMemoryDataset(
         paths.get("train_data"),
diff --git a/declearn/utils/__init__.py b/declearn/utils/__init__.py
index ba1084fb..451517d3 100644
--- a/declearn/utils/__init__.py
+++ b/declearn/utils/__init__.py
@@ -80,6 +80,15 @@ Utils to access or update parameters defining a global device-selection policy.
 * [set_device_policy][declearn.utils.set_device_policy]:
     Update the current global device policy.
 
+Logging utils
+-------------
+Utils to set up and configure loggers:
+
+* [get_logger][declearn.utils.get_logger]:
+    Access or create a logger, automating basic handlers' configuration.
+* [LOGGING_LEVEL_MAJOR][declearn.utils.LOGGING_LEVEL_MAJOR]:
+    Custom "MAJOR" severity level, between stdlib "INFO" and "WARNING".
+
 Miscellaneous
 -------------
 
@@ -89,8 +98,6 @@ Miscellaneous
     Automatically build a dataclass matching a function's signature.
 * [dataclass_from_init][declearn.utils.dataclass_from_init]:
     Automatically build a dataclass matching a class's init signature.
-* [get_logger][declearn.utils.get_logger]:
-    Access or create a logger, automating basic handlers' configuration.
 * [run_as_processes][declearn.utils.run_as_processes]:
     Run coroutines concurrently within individual processes.
 """
@@ -112,6 +119,7 @@ from ._json import (
     json_unpack,
 )
 from ._logging import (
+    LOGGING_LEVEL_MAJOR,
     get_logger,
 )
 from ._multiprocess import run_as_processes
diff --git a/declearn/utils/_logging.py b/declearn/utils/_logging.py
index b1c92691..07f47627 100644
--- a/declearn/utils/_logging.py
+++ b/declearn/utils/_logging.py
@@ -22,6 +22,18 @@ import os
 from typing import Optional
 
 
+__all__ = [
+    "get_logger",
+    "LOGGING_LEVEL_MAJOR",
+]
+
+
+# Add a logging level between INFO and WARNING.
+LOGGING_LEVEL_MAJOR = (logging.WARNING + logging.INFO) // 2
+"""Custom "MAJOR" severity level, between stdlib "INFO" and "WARNING"."""
+logging.addLevelName(level=LOGGING_LEVEL_MAJOR, levelName="MAJOR")
+
+
 DEFAULT_FORMAT = "%(asctime)s:%(name)s:%(levelname)s: %(message)s"
 
 
-- 
GitLab