diff --git a/src/py/flwr/common/logger.py b/src/py/flwr/common/logger.py index 258809ce062f..f9c70afc25f8 100644 --- a/src/py/flwr/common/logger.py +++ b/src/py/flwr/common/logger.py @@ -188,3 +188,29 @@ def warn_deprecated_feature(name: str) -> None: """, name, ) + + +def set_logger_propagation( + child_logger: logging.Logger, value: bool = True +) -> logging.Logger: + """Set the logger propagation attribute. + + Parameters + ---------- + child_logger : logging.Logger + Child logger object + value : bool + Boolean setting for propagation. If True, both parent and child logger + display messages. Otherwise, only the child logger displays a message. + This False setting prevents duplicate logs in Colab notebooks. + Reference: https://stackoverflow.com/a/19561320 + + Returns + ------- + logging.Logger + Child logger object with updated propagation setting + """ + child_logger.propagate = value + if not child_logger.propagate: + child_logger.log(logging.DEBUG, "Logger propagate set to False") + return child_logger diff --git a/src/py/flwr/simulation/app.py b/src/py/flwr/simulation/app.py index ff18f37664be..4b4b7249ccd3 100644 --- a/src/py/flwr/simulation/app.py +++ b/src/py/flwr/simulation/app.py @@ -15,6 +15,8 @@ """Flower simulation app.""" +import asyncio +import logging import sys import threading import traceback @@ -27,7 +29,7 @@ from flwr.client import ClientFn from flwr.common import EventType, event -from flwr.common.logger import log +from flwr.common.logger import log, set_logger_propagation from flwr.server.client_manager import ClientManager from flwr.server.history import History from flwr.server.server import Server, init_defaults, run_fl @@ -156,6 +158,7 @@ def start_simulation( is an advanced feature. For all details, please refer to the Ray documentation: https://docs.ray.io/en/latest/ray-core/scheduling/index.html + Returns ------- hist : flwr.server.history.History @@ -167,6 +170,18 @@ def start_simulation( {"num_clients": len(clients_ids) if clients_ids is not None else num_clients}, ) + # Set logger propagation + loop: Optional[asyncio.AbstractEventLoop] = None + try: + loop = asyncio.get_running_loop() + except RuntimeError: + loop = None + finally: + if loop and loop.is_running(): + # Set logger propagation to False to prevent duplicated log output in Colab. + logger = logging.getLogger("flwr") + _ = set_logger_propagation(logger, False) + # Initialize server and server config initialized_server, initialized_config = init_defaults( server=server, diff --git a/src/py/flwr/simulation/run_simulation.py b/src/py/flwr/simulation/run_simulation.py index d5f1a655adc3..113b4c594ba5 100644 --- a/src/py/flwr/simulation/run_simulation.py +++ b/src/py/flwr/simulation/run_simulation.py @@ -28,6 +28,7 @@ from flwr.client import ClientApp from flwr.common import EventType, event, log +from flwr.common.logger import set_logger_propagation from flwr.common.typing import ConfigsRecordValues from flwr.server.driver import Driver, GrpcDriver from flwr.server.run_serverapp import run @@ -364,6 +365,8 @@ def _run_simulation( finally: if run_in_thread: + # Set logger propagation to False to prevent duplicated log output in Colab. + logger = set_logger_propagation(logger, False) log(DEBUG, "Starting Simulation Engine on a new thread.") simulation_engine_th = threading.Thread(target=_main_loop, args=args) simulation_engine_th.start()