This is an automated email from the ASF dual-hosted git repository. estrauss pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/systemds.git
The following commit(s) were added to refs/heads/main by this push: new 455a73882c [MINOR] PythonAPI: Fix for correct logging setup for debug 455a73882c is described below commit 455a73882c42246deeb4c2659952bd4729756e18 Author: e-strauss <lathan...@gmx.de> AuthorDate: Wed Jul 23 15:32:47 2025 +0200 [MINOR] PythonAPI: Fix for correct logging setup for debug Closes #2295 --- .../python/systemds/context/systemds_context.py | 57 +++++++++++++++----- .../python/tests/basics/test_context_creation.py | 61 ++++++++++++++++++++++ 2 files changed, 106 insertions(+), 12 deletions(-) diff --git a/src/main/python/systemds/context/systemds_context.py b/src/main/python/systemds/context/systemds_context.py index 25824bc663..a559850c33 100644 --- a/src/main/python/systemds/context/systemds_context.py +++ b/src/main/python/systemds/context/systemds_context.py @@ -66,6 +66,7 @@ class SystemDSContext(object): _log: logging.Logger __stdout: Queue = None __stderr: Queue = None + _logging_initialized = False def __init__( self, @@ -779,21 +780,53 @@ class SystemDSContext(object): :param level: The SystemDS logging part logging level. :param py4j_level: The Py4J logging level. """ - logging.basicConfig() + # Set py4j level every time py4j = logging.getLogger("py4j.java_gateway") py4j.setLevel(py4j_level) py4j.propagate = False + if not SystemDSContext._logging_initialized: + # Add handler only once + logging.basicConfig() + + root_logger = logging.getLogger(self.__class__.__name__) + root_logger.handlers.clear() + + f_handler = DynamicStderrHandler() + f_handler.setLevel(logging.NOTSET) + f_handler.setFormatter( + logging.Formatter( + "%(asctime)s %(levelname)s %(name)s: %(message)s", + "%y/%m/%d %H:%M:%S", + ) + ) + + root_logger.addHandler(f_handler) + root_logger.propagate = False + + SystemDSContext._logging_initialized = True + + # Per-instance logger setup self._log = logging.getLogger(self.__class__.__name__) - f_handler = logging.StreamHandler() - f_handler.setLevel(level) - f_format = logging.Formatter( - "%(asctime)s - SystemDS- %(levelname)s - %(message)s" + self._log.setLevel(level) + self._log.debug( + "Logging setup done (SystemDS level: %s, Py4J level: %s)", + logging.getLevelName(level), + logging.getLevelName(py4j_level), ) - f_handler.setFormatter(f_format) - self._log.addHandler - # avoid the logger to call loggers above. - self._log.propagate = False - # Reset all handlers to only this new handler. - self._log.handlers = [f_handler] - self._log.debug("Logging setup done") + + +class DynamicStderrHandler(logging.StreamHandler): + def __init__(self, level=logging.NOTSET, formatter=None): + # Avoid setting stream directly to sys.stderr, we will do that dynamically + # For more info see test case: tests/basics/test_context_creation/test_random_port_debug3 + # where we redirect the err channel in between different contexts + super().__init__(stream=None) + self.setLevel(level) + if formatter: + self.setFormatter(formatter) + + def emit(self, record): + # Always use the current sys.stderr when emitting + self.stream = sys.stderr + super().emit(record) diff --git a/src/main/python/tests/basics/test_context_creation.py b/src/main/python/tests/basics/test_context_creation.py index 920dace8be..3d80420d4e 100644 --- a/src/main/python/tests/basics/test_context_creation.py +++ b/src/main/python/tests/basics/test_context_creation.py @@ -21,12 +21,73 @@ import unittest import logging +import io +import sys +from contextlib import redirect_stdout, redirect_stderr from systemds.context import SystemDSContext class TestContextCreation(unittest.TestCase): + def test_random_port_debug(self): + SystemDSContext._logging_initialized = False + + stderr_buffer = io.StringIO() + + with redirect_stderr(stderr_buffer): + sds1 = SystemDSContext(logging_level=10) + sds1.close() + + err = stderr_buffer.getvalue() + print("Captured STDERR:\n", err) + print("END OF STDERR\n") + + self.assertIn("DEBUG SystemDSContext: Logging setup done", err) + + def test_random_port_debug2(self): + SystemDSContext._logging_initialized = False + + stderr_buffer = io.StringIO() + + with redirect_stderr(stderr_buffer): + sds1 = SystemDSContext() + sds1.close() + + err = stderr_buffer.getvalue() + print("\nCaptured STDERR (ctx1):\n", err) + print("END OF STDERR\n") + + # clear the buffer + stderr_buffer.seek(0) + stderr_buffer.truncate(0) + + sds2 = SystemDSContext(logging_level=10) + sds2.close() + + err = stderr_buffer.getvalue() + print("\nCaptured STDERR (ctx2):\n", err) + print("END OF STDERR\n") + + self.assertIn("DEBUG SystemDSContext: Logging setup done", err) + + def test_random_port_debug3(self): + SystemDSContext._logging_initialized = False + + sds1 = SystemDSContext() + sds1.close() + stderr_buffer = io.StringIO() + + with redirect_stderr(stderr_buffer): + sds2 = SystemDSContext(logging_level=10) + sds2.close() + + err = stderr_buffer.getvalue() + print("\nCaptured STDERR (ctx2):\n", err) + print("END OF STDERR\n") + + self.assertIn("DEBUG SystemDSContext: Logging setup done", err) + def test_random_port(self): sds1 = SystemDSContext() sds1.close()