From 33a8005f711434b7291f16c570199d9288f6d64b Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Wed, 22 Apr 2026 22:10:45 -0700 Subject: [PATCH 01/29] intial pseudocode --- package/MDAnalysis/lib/log.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index d63ec547828..64edef04198 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -93,15 +93,15 @@ from .. import version -def start_logging(logfile="MDAnalysis.log", version=version.__version__): +def start_logging(stream="MDAnalysis.log", version=version.__version__): """Start logging of messages to file and console. The default logfile is named `MDAnalysis.log` and messages are logged with the tag *MDAnalysis*. """ - create("MDAnalysis", logfile=logfile) + create("MDAnalysis", stream=stream) logging.getLogger("MDAnalysis").info( - "MDAnalysis %s STARTED logging to %r", version, logfile + "MDAnalysis %s STARTED logging to %r", version, stream ) @@ -134,6 +134,14 @@ def create(logger_name="MDAnalysis", logfile="MDAnalysis.log"): logger.setLevel(logging.DEBUG) + ## TODO need logic for multiple handlers + # Pseudocode for create arguments and behavior + # 1. input : str -> create new log_file + # 2. File objects (like file.open() and sys.stdout/sys.stderr) -> new file + # 3. Iterable (like [file, sys.stdout]) -> create file and print to console + ## + + # handler that writes to logfile logfile_handler = logging.FileHandler(logfile) logfile_formatter = logging.Formatter( From 1bf0ee1e17fcd1665e7955d5683ee3dcf4389a58 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Wed, 22 Apr 2026 22:58:25 -0700 Subject: [PATCH 02/29] inital thought code (hadn't run it yet) --- package/MDAnalysis/lib/log.py | 37 +++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index 64edef04198..f63d36e18b5 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -87,6 +87,7 @@ import sys import logging import re +from collections.abc import Iterable from tqdm.auto import tqdm @@ -112,7 +113,7 @@ def stop_logging(): clear_handlers(logger) # this _should_ do the job... -def create(logger_name="MDAnalysis", logfile="MDAnalysis.log"): +def create(logger_name="MDAnalysis", stream="MDAnalysis.log", level='DEBUG'): """Create a top level logger. - The file logger logs everything (including DEBUG). @@ -132,7 +133,8 @@ def create(logger_name="MDAnalysis", logfile="MDAnalysis.log"): logger = logging.getLogger(logger_name) - logger.setLevel(logging.DEBUG) + #level parameter is from https://docs.python.org/3/library/logging.html#logging-levels + logger.setLevel(level.upper()) ## TODO need logic for multiple handlers # Pseudocode for create arguments and behavior @@ -141,6 +143,37 @@ def create(logger_name="MDAnalysis", logfile="MDAnalysis.log"): # 3. Iterable (like [file, sys.stdout]) -> create file and print to console ## + # create a master logger, and attach handles to it? + # See https://docs.python.org/3/library/logging.handlers.html# + # + + # am borowwing this pattern from fetch module + if isinstance(stream, str): + streams = tuple(stream,) + # Want to allow ndarrays, tuple, and list but exclude strings + elif isinstance(stream, Iterable): + streams = stream + else: + raise Exception('foobar') + + for stream in streams: + # https://docs.python.org/3/library/logging.handlers.html#streamhandler + # The StreamHandler class, located in the core logging package, + #sends logging output to streams such as sys.stdout, sys.stderr or + # any file-like object (or, more precisely, any object which supports write() and flush() methods). + + # note this is looks gross and probably need to be written - invert the if and else case? + if (hasattr(streams, 'flush') and callable(getattr(streams, 'flush'))) and \ + (hasattr(streams, 'write') and callable(getattr(streams, 'write'))): + handler = logging.StreamHandler(stream) + else: + handler = logging.FileHandler(stream) + + logger.addHandler(handler) + + return logger + + ## Old code # handler that writes to logfile logfile_handler = logging.FileHandler(logfile) From 24a6c313592ffc41b2314909b685df595261a392 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 07:28:36 -0700 Subject: [PATCH 03/29] more comments --- package/MDAnalysis/lib/log.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index f63d36e18b5..5871e3e5ec7 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -87,6 +87,7 @@ import sys import logging import re +import io from collections.abc import Iterable from tqdm.auto import tqdm @@ -138,19 +139,19 @@ def create(logger_name="MDAnalysis", stream="MDAnalysis.log", level='DEBUG'): ## TODO need logic for multiple handlers # Pseudocode for create arguments and behavior - # 1. input : str -> create new log_file - # 2. File objects (like file.open() and sys.stdout/sys.stderr) -> new file - # 3. Iterable (like [file, sys.stdout]) -> create file and print to console + # 1. input : file.log -> create file.log + # 2. File objects (like f = file.open() and sys.stdout/sys.stderr) -> log to stream + # 3. Iterable (like [file.log, sys.stdout]) -> create file.log and print to console ## - # create a master logger, and attach handles to it? + # create/call a master logger, and attach handles to it? # See https://docs.python.org/3/library/logging.handlers.html# # # am borowwing this pattern from fetch module - if isinstance(stream, str): - streams = tuple(stream,) # Want to allow ndarrays, tuple, and list but exclude strings + if isinstance(stream, str) or isinstance(stream, io.IOBase): + streams = (stream,) elif isinstance(stream, Iterable): streams = stream else: @@ -163,8 +164,9 @@ def create(logger_name="MDAnalysis", stream="MDAnalysis.log", level='DEBUG'): # any file-like object (or, more precisely, any object which supports write() and flush() methods). # note this is looks gross and probably need to be written - invert the if and else case? - if (hasattr(streams, 'flush') and callable(getattr(streams, 'flush'))) and \ - (hasattr(streams, 'write') and callable(getattr(streams, 'write'))): + # Doesn't work since FileHandler inherits from Steam handler + if (hasattr(stream, 'flush') and callable(getattr(stream, 'flush'))) and \ + (hasattr(stream, 'write') and callable(getattr(stream, 'write'))): handler = logging.StreamHandler(stream) else: handler = logging.FileHandler(stream) From 7a5c0b3614af02571dc1c716e2a3fc65a9e6bf2d Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 07:49:33 -0700 Subject: [PATCH 04/29] general cleanup --- package/MDAnalysis/lib/log.py | 37 ++++++++--------------------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index 5871e3e5ec7..6c57530e26e 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -87,7 +87,7 @@ import sys import logging import re -import io +import io, os from collections.abc import Iterable from tqdm.auto import tqdm @@ -149,8 +149,7 @@ def create(logger_name="MDAnalysis", stream="MDAnalysis.log", level='DEBUG'): # # am borowwing this pattern from fetch module - # Want to allow ndarrays, tuple, and list but exclude strings - if isinstance(stream, str) or isinstance(stream, io.IOBase): + if isinstance(stream, (str, os.PathLike)) or isinstance(stream, io.IOBase): streams = (stream,) elif isinstance(stream, Iterable): streams = stream @@ -163,38 +162,18 @@ def create(logger_name="MDAnalysis", stream="MDAnalysis.log", level='DEBUG'): #sends logging output to streams such as sys.stdout, sys.stderr or # any file-like object (or, more precisely, any object which supports write() and flush() methods). - # note this is looks gross and probably need to be written - invert the if and else case? - # Doesn't work since FileHandler inherits from Steam handler - if (hasattr(stream, 'flush') and callable(getattr(stream, 'flush'))) and \ - (hasattr(stream, 'write') and callable(getattr(stream, 'write'))): + # This only check the existance and not the functionality. Should be ok? + if hasattr(stream, "write") and hasattr(stream, "flush"): handler = logging.StreamHandler(stream) - else: + elif isinstance(stream, (str, os.PathLike)): handler = logging.FileHandler(stream) - + else: + raise Exception('foobar') + logger.addHandler(handler) return logger - ## Old code - - # handler that writes to logfile - logfile_handler = logging.FileHandler(logfile) - logfile_formatter = logging.Formatter( - "%(asctime)s %(name)-12s %(levelname)-8s %(message)s" - ) - logfile_handler.setFormatter(logfile_formatter) - logger.addHandler(logfile_handler) - - # define a Handler which writes INFO messages or higher to the sys.stderr - console_handler = logging.StreamHandler() - console_handler.setLevel(logging.INFO) - # set a format which is simpler for console use - formatter = logging.Formatter("%(name)-12s: %(levelname)-8s %(message)s") - console_handler.setFormatter(formatter) - logger.addHandler(console_handler) - - return logger - def clear_handlers(logger): """clean out handlers in the library top level logger From f1be0df87a98430e2aeb675b78e00bd68f797340 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 07:52:04 -0700 Subject: [PATCH 05/29] Post black Need to add a fmt_string parameter if want to replicate earlier behavior --- package/MDAnalysis/lib/log.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index 6c57530e26e..3b368052834 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -114,7 +114,7 @@ def stop_logging(): clear_handlers(logger) # this _should_ do the job... -def create(logger_name="MDAnalysis", stream="MDAnalysis.log", level='DEBUG'): +def create(logger_name="MDAnalysis", stream="MDAnalysis.log", level="DEBUG"): """Create a top level logger. - The file logger logs everything (including DEBUG). @@ -134,11 +134,11 @@ def create(logger_name="MDAnalysis", stream="MDAnalysis.log", level='DEBUG'): logger = logging.getLogger(logger_name) - #level parameter is from https://docs.python.org/3/library/logging.html#logging-levels + # level parameter is from https://docs.python.org/3/library/logging.html#logging-levels logger.setLevel(level.upper()) ## TODO need logic for multiple handlers - # Pseudocode for create arguments and behavior + # Pseudocode for create arguments and behavior # 1. input : file.log -> create file.log # 2. File objects (like f = file.open() and sys.stdout/sys.stderr) -> log to stream # 3. Iterable (like [file.log, sys.stdout]) -> create file.log and print to console @@ -147,31 +147,31 @@ def create(logger_name="MDAnalysis", stream="MDAnalysis.log", level='DEBUG'): # create/call a master logger, and attach handles to it? # See https://docs.python.org/3/library/logging.handlers.html# # - + # am borowwing this pattern from fetch module if isinstance(stream, (str, os.PathLike)) or isinstance(stream, io.IOBase): streams = (stream,) elif isinstance(stream, Iterable): streams = stream else: - raise Exception('foobar') - + raise Exception("foobar") + for stream in streams: # https://docs.python.org/3/library/logging.handlers.html#streamhandler # The StreamHandler class, located in the core logging package, - #sends logging output to streams such as sys.stdout, sys.stderr or + # sends logging output to streams such as sys.stdout, sys.stderr or # any file-like object (or, more precisely, any object which supports write() and flush() methods). - + # This only check the existance and not the functionality. Should be ok? if hasattr(stream, "write") and hasattr(stream, "flush"): handler = logging.StreamHandler(stream) elif isinstance(stream, (str, os.PathLike)): handler = logging.FileHandler(stream) else: - raise Exception('foobar') - + raise Exception("foobar") + logger.addHandler(handler) - + return logger From dbcfb1c5287b5ba4de9c75a491767f017b92f6f2 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 11:28:07 -0700 Subject: [PATCH 06/29] removed loop based control --- package/MDAnalysis/lib/log.py | 50 +++++++++++------------------------ 1 file changed, 15 insertions(+), 35 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index 3b368052834..273338dbf10 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -87,7 +87,8 @@ import sys import logging import re -import io, os +import io +import os from collections.abc import Iterable from tqdm.auto import tqdm @@ -134,43 +135,22 @@ def create(logger_name="MDAnalysis", stream="MDAnalysis.log", level="DEBUG"): logger = logging.getLogger(logger_name) - # level parameter is from https://docs.python.org/3/library/logging.html#logging-levels logger.setLevel(level.upper()) - ## TODO need logic for multiple handlers - # Pseudocode for create arguments and behavior - # 1. input : file.log -> create file.log - # 2. File objects (like f = file.open() and sys.stdout/sys.stderr) -> log to stream - # 3. Iterable (like [file.log, sys.stdout]) -> create file.log and print to console - ## - - # create/call a master logger, and attach handles to it? - # See https://docs.python.org/3/library/logging.handlers.html# - # - - # am borowwing this pattern from fetch module - if isinstance(stream, (str, os.PathLike)) or isinstance(stream, io.IOBase): - streams = (stream,) - elif isinstance(stream, Iterable): - streams = stream + # https://docs.python.org/3/library/logging.handlers.html#streamhandler + # The StreamHandler class, located in the core logging package, + # sends logging output to streams such as sys.stdout, sys.stderr or + # any file-like object (or, more precisely, any object which supports write() and flush() methods). + + # This only check the existance and not the functionality. Should be ok? + if hasattr(stream, "write") and hasattr(stream, "flush"): + handler = logging.StreamHandler(stream) + elif isinstance(stream, (str, os.PathLike)): + handler = logging.FileHandler(stream) else: - raise Exception("foobar") - - for stream in streams: - # https://docs.python.org/3/library/logging.handlers.html#streamhandler - # The StreamHandler class, located in the core logging package, - # sends logging output to streams such as sys.stdout, sys.stderr or - # any file-like object (or, more precisely, any object which supports write() and flush() methods). - - # This only check the existance and not the functionality. Should be ok? - if hasattr(stream, "write") and hasattr(stream, "flush"): - handler = logging.StreamHandler(stream) - elif isinstance(stream, (str, os.PathLike)): - handler = logging.FileHandler(stream) - else: - raise Exception("foobar") - - logger.addHandler(handler) + raise TypeError("Input Stream is neither a file or a steam") + + logger.addHandler(handler) return logger From 93966fbb56f87deeb2e9999b06aa2e73a96150f7 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 11:34:01 -0700 Subject: [PATCH 07/29] Added formatter to handler --- package/MDAnalysis/lib/log.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index 273338dbf10..63d44fed314 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -115,7 +115,7 @@ def stop_logging(): clear_handlers(logger) # this _should_ do the job... -def create(logger_name="MDAnalysis", stream="MDAnalysis.log", level="DEBUG"): +def create(logger_name="MDAnalysis", stream="MDAnalysis.log", level="DEBUG", fmt=None): """Create a top level logger. - The file logger logs everything (including DEBUG). @@ -148,8 +148,9 @@ def create(logger_name="MDAnalysis", stream="MDAnalysis.log", level="DEBUG"): elif isinstance(stream, (str, os.PathLike)): handler = logging.FileHandler(stream) else: - raise TypeError("Input Stream is neither a file or a steam") - + raise TypeError("Input Stream is neither a file or a stream") + + handler.setFormatter(logging.Formatter(fmt)) logger.addHandler(handler) return logger From c5f6133887499f557ecedd201acaf38ade5589c4 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 11:39:10 -0700 Subject: [PATCH 08/29] Added format to start_logging() to replicate old behavior --- package/MDAnalysis/lib/log.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index 63d44fed314..9da468c3e6a 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -102,7 +102,18 @@ def start_logging(stream="MDAnalysis.log", version=version.__version__): The default logfile is named `MDAnalysis.log` and messages are logged with the tag *MDAnalysis*. """ - create("MDAnalysis", stream=stream) + create( + "MDAnalysis", + stream=stream, + level="DEBUG", + fmt="%(asctime)s %(name)-12s %(levelname)-8s %(message)s", + ) + create( + "MDAnalysis", + stream=sys.stdout, + level="INFO", + fmt="%(name)-12s: %(levelname)-8s %(message)s", + ) logging.getLogger("MDAnalysis").info( "MDAnalysis %s STARTED logging to %r", version, stream ) @@ -115,7 +126,9 @@ def stop_logging(): clear_handlers(logger) # this _should_ do the job... -def create(logger_name="MDAnalysis", stream="MDAnalysis.log", level="DEBUG", fmt=None): +def create( + logger_name="MDAnalysis", stream="MDAnalysis.log", level="DEBUG", fmt=None +): """Create a top level logger. - The file logger logs everything (including DEBUG). @@ -149,7 +162,7 @@ def create(logger_name="MDAnalysis", stream="MDAnalysis.log", level="DEBUG", fmt handler = logging.FileHandler(stream) else: raise TypeError("Input Stream is neither a file or a stream") - + handler.setFormatter(logging.Formatter(fmt)) logger.addHandler(handler) From e30d0ab9351d4c73667e586c2e34b3dac500df0a Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 11:41:20 -0700 Subject: [PATCH 09/29] Applied flake8 --- package/MDAnalysis/lib/log.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index 9da468c3e6a..1e4fdff7228 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -86,10 +86,7 @@ """ import sys import logging -import re -import io import os -from collections.abc import Iterable from tqdm.auto import tqdm @@ -151,9 +148,11 @@ def create( logger.setLevel(level.upper()) # https://docs.python.org/3/library/logging.handlers.html#streamhandler + # # The StreamHandler class, located in the core logging package, # sends logging output to streams such as sys.stdout, sys.stderr or - # any file-like object (or, more precisely, any object which supports write() and flush() methods). + # any file-like object (or, more precisely, any object which supports + # write() and flush() methods). # This only check the existance and not the functionality. Should be ok? if hasattr(stream, "write") and hasattr(stream, "flush"): From 5a5906d0a84ed6bd719dd0f663aab33caa369ce7 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 12:11:55 -0700 Subject: [PATCH 10/29] notes for test later --- package/MDAnalysis/lib/log.py | 5 ++--- testsuite/MDAnalysisTests/utils/test_log.py | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index 1e4fdff7228..eef30ee1769 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -111,9 +111,6 @@ def start_logging(stream="MDAnalysis.log", version=version.__version__): level="INFO", fmt="%(name)-12s: %(levelname)-8s %(message)s", ) - logging.getLogger("MDAnalysis").info( - "MDAnalysis %s STARTED logging to %r", version, stream - ) def stop_logging(): @@ -165,6 +162,8 @@ def create( handler.setFormatter(logging.Formatter(fmt)) logger.addHandler(handler) + logger.info(f"MDAnalysis {version} STARTED logging to {stream!r}") + return logger diff --git a/testsuite/MDAnalysisTests/utils/test_log.py b/testsuite/MDAnalysisTests/utils/test_log.py index 0abaed2795d..cfb2194e7bd 100644 --- a/testsuite/MDAnalysisTests/utils/test_log.py +++ b/testsuite/MDAnalysisTests/utils/test_log.py @@ -40,6 +40,8 @@ def test_start_stop_logging(): finally: MDAnalysis.log.stop_logging() +# Write Tests here for later +# Idk why there are two seperate tests files: lib/test_log.py and utils/test_log.py class RedirectedStderr(object): """Temporarily replaces sys.stderr with *stream*. From f679251e7bfc401c760f51e88151d97ed3b3ce31 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 12:19:06 -0700 Subject: [PATCH 11/29] fix linter --- testsuite/MDAnalysisTests/utils/test_log.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/testsuite/MDAnalysisTests/utils/test_log.py b/testsuite/MDAnalysisTests/utils/test_log.py index cfb2194e7bd..9c9f99153dc 100644 --- a/testsuite/MDAnalysisTests/utils/test_log.py +++ b/testsuite/MDAnalysisTests/utils/test_log.py @@ -40,9 +40,11 @@ def test_start_stop_logging(): finally: MDAnalysis.log.stop_logging() -# Write Tests here for later + +# Write Tests here for later # Idk why there are two seperate tests files: lib/test_log.py and utils/test_log.py + class RedirectedStderr(object): """Temporarily replaces sys.stderr with *stream*. From 2ba1c2e40a5e72582e7c81883597904be0f410b9 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 20:30:58 -0700 Subject: [PATCH 12/29] put logger back to start_logging() --- package/MDAnalysis/lib/log.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index eef30ee1769..04fd49c61fc 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -90,6 +90,8 @@ from tqdm.auto import tqdm +# from MDAnalysis.lib.util import deprecate + from .. import version @@ -111,6 +113,9 @@ def start_logging(stream="MDAnalysis.log", version=version.__version__): level="INFO", fmt="%(name)-12s: %(levelname)-8s %(message)s", ) + logging.getLogger("MDAnalysis").info( + f"MDAnalysis {version} STARTED logging to {stream!r}" + ) def stop_logging(): @@ -162,8 +167,6 @@ def create( handler.setFormatter(logging.Formatter(fmt)) logger.addHandler(handler) - logger.info(f"MDAnalysis {version} STARTED logging to {stream!r}") - return logger From cca9258e7f8a5ac6938064c703ead801ac062fba Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 20:40:39 -0700 Subject: [PATCH 13/29] cleaned up create() --- package/MDAnalysis/lib/log.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index 04fd49c61fc..a3f099d6a54 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -126,7 +126,11 @@ def stop_logging(): def create( - logger_name="MDAnalysis", stream="MDAnalysis.log", level="DEBUG", fmt=None + logger_name="MDAnalysis", + stream="MDAnalysis.log", + level="DEBUG", + fmt=None, + mode="a", ): """Create a top level logger. @@ -146,23 +150,18 @@ def create( """ logger = logging.getLogger(logger_name) - logger.setLevel(level.upper()) + # This checks for file-like object per duck typing # https://docs.python.org/3/library/logging.handlers.html#streamhandler - # - # The StreamHandler class, located in the core logging package, - # sends logging output to streams such as sys.stdout, sys.stderr or - # any file-like object (or, more precisely, any object which supports - # write() and flush() methods). - - # This only check the existance and not the functionality. Should be ok? if hasattr(stream, "write") and hasattr(stream, "flush"): handler = logging.StreamHandler(stream) elif isinstance(stream, (str, os.PathLike)): - handler = logging.FileHandler(stream) + handler = logging.FileHandler(stream, mode=mode) else: - raise TypeError("Input Stream is neither a file or a stream") + raise TypeError( + "Input Stream is neither a string, PathLike object or a stream" + ) handler.setFormatter(logging.Formatter(fmt)) logger.addHandler(handler) From 94f056f5ef168f814f12c6469d3407ef6cd48309 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 21:15:54 -0700 Subject: [PATCH 14/29] removed MDAnalysis NullHandler --- package/MDAnalysis/__init__.py | 3 +-- package/MDAnalysis/lib/log.py | 17 ----------------- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/package/MDAnalysis/__init__.py b/package/MDAnalysis/__init__.py index 6843c3738ab..54388565846 100644 --- a/package/MDAnalysis/__init__.py +++ b/package/MDAnalysis/__init__.py @@ -197,10 +197,9 @@ StreamWarning, ) -from .lib import log from .lib.log import start_logging, stop_logging -logging.getLogger("MDAnalysis").addHandler(log.NullHandler()) +logging.getLogger("MDAnalysis").addHandler(logging.NullHandler()) del logging # only MDAnalysis DeprecationWarnings are loud by default diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index a3f099d6a54..4ccb74dc272 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -179,23 +179,6 @@ def clear_handlers(logger): logger.removeHandler(h) -class NullHandler(logging.Handler): - """Silent Handler. - - Useful as a default:: - - h = NullHandler() - logging.getLogger("MDAnalysis").addHandler(h) - del h - - see the advice on logging and libraries in - http://docs.python.org/library/logging.html?#configuring-logging-for-a-library - """ - - def emit(self, record): - pass - - class ProgressBar(tqdm): r"""Display a visual progress bar and time estimate. From 7f5812fa92f9ccb87cefc0b263bddf9a7790bd1b Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 21:36:06 -0700 Subject: [PATCH 15/29] readded top level import -- it broke some tests --- package/MDAnalysis/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/package/MDAnalysis/__init__.py b/package/MDAnalysis/__init__.py index 54388565846..8b5db410db5 100644 --- a/package/MDAnalysis/__init__.py +++ b/package/MDAnalysis/__init__.py @@ -197,6 +197,7 @@ StreamWarning, ) +from .lib import log from .lib.log import start_logging, stop_logging logging.getLogger("MDAnalysis").addHandler(logging.NullHandler()) From ba9315e379988e0a256791e184f62d2ab1c30a91 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 21:43:19 -0700 Subject: [PATCH 16/29] ported over tests --- testsuite/MDAnalysisTests/lib/test_log.py | 14 +++- testsuite/MDAnalysisTests/utils/test_log.py | 79 --------------------- 2 files changed, 12 insertions(+), 81 deletions(-) delete mode 100644 testsuite/MDAnalysisTests/utils/test_log.py diff --git a/testsuite/MDAnalysisTests/lib/test_log.py b/testsuite/MDAnalysisTests/lib/test_log.py index 541660ca4c7..6cd4ecffd26 100644 --- a/testsuite/MDAnalysisTests/lib/test_log.py +++ b/testsuite/MDAnalysisTests/lib/test_log.py @@ -20,11 +20,21 @@ # MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # -import warnings -import pytest +import logging + +import MDAnalysis from MDAnalysis.lib.log import ProgressBar +def test_start_stop_logging(): + try: + MDAnalysis.log.start_logging() + logger = logging.getLogger("MDAnalysis") + logger.info("Using the MDAnalysis logger works") + except Exception as err: + raise AssertionError("Problem with logger: {0}".format(err)) + finally: + MDAnalysis.log.stop_logging() class TestProgressBar(object): diff --git a/testsuite/MDAnalysisTests/utils/test_log.py b/testsuite/MDAnalysisTests/utils/test_log.py deleted file mode 100644 index 9c9f99153dc..00000000000 --- a/testsuite/MDAnalysisTests/utils/test_log.py +++ /dev/null @@ -1,79 +0,0 @@ -# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*- -# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 fileencoding=utf-8 -# -# MDAnalysis --- https://www.mdanalysis.org -# Copyright (c) 2006-2017 The MDAnalysis Development Team and contributors -# (see the file AUTHORS for the full list of names) -# -# Released under the Lesser GNU Public Licence, v2.1 or any higher version -# -# Please cite your use of MDAnalysis in published work: -# -# R. J. Gowers, M. Linke, J. Barnoud, T. J. E. Reddy, M. N. Melo, S. L. Seyler, -# D. L. Dotson, J. Domanski, S. Buchoux, I. M. Kenney, and O. Beckstein. -# MDAnalysis: A Python package for the rapid analysis of molecular dynamics -# simulations. In S. Benthall and S. Rostrup editors, Proceedings of the 15th -# Python in Science Conference, pages 102-109, Austin, TX, 2016. SciPy. -# doi: 10.25080/majora-629e541a-00e -# -# N. Michaud-Agrawal, E. J. Denning, T. B. Woolf, and O. Beckstein. -# MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. -# J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 -# -from io import StringIO - -import logging -import sys - -import MDAnalysis -import MDAnalysis.lib.log -import pytest - - -def test_start_stop_logging(): - try: - MDAnalysis.log.start_logging() - logger = logging.getLogger("MDAnalysis") - logger.info("Using the MDAnalysis logger works") - except Exception as err: - raise AssertionError("Problem with logger: {0}".format(err)) - finally: - MDAnalysis.log.stop_logging() - - -# Write Tests here for later -# Idk why there are two seperate tests files: lib/test_log.py and utils/test_log.py - - -class RedirectedStderr(object): - """Temporarily replaces sys.stderr with *stream*. - - Deals with cached stderr, see - http://stackoverflow.com/questions/6796492/temporarily-redirect-stdout-stderr - """ - - def __init__(self, stream=None): - self._stderr = sys.stderr - self.stream = stream or sys.stdout - - def __enter__(self): - self.old_stderr = sys.stderr - self.old_stderr.flush() - sys.stderr = self.stream - - def __exit__(self, exc_type, exc_value, traceback): - self._stderr.flush() - sys.stderr = self.old_stderr - - -@pytest.fixture() -def buffer(): - return StringIO() - - -def _assert_in(output, string): - assert ( - string in output - ), "Output '{0}' does not match required format '{1}'.".format( - output.replace("\r", "\\r"), string.replace("\r", "\\r") - ) From 0e3623177a46c67384d4ff6ffcd29d4d0eaf78c3 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 21:45:56 -0700 Subject: [PATCH 17/29] Used black --- testsuite/MDAnalysisTests/lib/test_log.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/testsuite/MDAnalysisTests/lib/test_log.py b/testsuite/MDAnalysisTests/lib/test_log.py index 6cd4ecffd26..4efd69e1af0 100644 --- a/testsuite/MDAnalysisTests/lib/test_log.py +++ b/testsuite/MDAnalysisTests/lib/test_log.py @@ -21,11 +21,12 @@ # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # -import logging +import logging import MDAnalysis from MDAnalysis.lib.log import ProgressBar + def test_start_stop_logging(): try: MDAnalysis.log.start_logging() @@ -36,6 +37,7 @@ def test_start_stop_logging(): finally: MDAnalysis.log.stop_logging() + class TestProgressBar(object): def test_output(self, capsys): From e63f5ef6a399dd06fc65656d314976851cf024d3 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 23:19:10 -0700 Subject: [PATCH 18/29] Added Tests for start_logging() and stop_logging() --- package/MDAnalysis/lib/log.py | 4 +- testsuite/MDAnalysisTests/lib/test_log.py | 50 +++++++++++++++++++---- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index 4ccb74dc272..ad3acaff484 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -150,7 +150,6 @@ def create( """ logger = logging.getLogger(logger_name) - logger.setLevel(level.upper()) # This checks for file-like object per duck typing # https://docs.python.org/3/library/logging.handlers.html#streamhandler @@ -163,6 +162,7 @@ def create( "Input Stream is neither a string, PathLike object or a stream" ) + handler.setLevel(level.upper()) handler.setFormatter(logging.Formatter(fmt)) logger.addHandler(handler) @@ -175,7 +175,7 @@ def clear_handlers(logger): (only important for reload/debug cycles...) """ - for h in logger.handlers: + for h in list(logger.handlers): logger.removeHandler(h) diff --git a/testsuite/MDAnalysisTests/lib/test_log.py b/testsuite/MDAnalysisTests/lib/test_log.py index 4efd69e1af0..672ed3ddd6e 100644 --- a/testsuite/MDAnalysisTests/lib/test_log.py +++ b/testsuite/MDAnalysisTests/lib/test_log.py @@ -21,21 +21,53 @@ # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # +import sys import logging +import MDAnalysis as mda -import MDAnalysis +from os.path import basename from MDAnalysis.lib.log import ProgressBar -def test_start_stop_logging(): - try: - MDAnalysis.log.start_logging() +class TestConvenienceFunctions: + def test_start_logging(self, tmp_path): + mda.start_logging(tmp_path / "MDAnalysis.log") logger = logging.getLogger("MDAnalysis") - logger.info("Using the MDAnalysis logger works") - except Exception as err: - raise AssertionError("Problem with logger: {0}".format(err)) - finally: - MDAnalysis.log.stop_logging() + + # Test Handlers' presence and behavior + assert any(isinstance(h, logging.NullHandler) for h in logger.handlers) + any( + isinstance(h, logging.FileHandler) + and basename(h.stream.name) == "MDAnalysis.log" + and h.level == logging.DEBUG + for h in logger.handlers + ) + assert any( + isinstance(h, logging.StreamHandler) + and h.stream is sys.stdout + and h.level == logging.INFO + for h in logger.handlers + ) + + def test_stop_logging(self, tmp_path): + mda.lib.log.start_logging(tmp_path / "MDAnalysis.log") + logger = logging.getLogger("MDAnalysis") + mda.lib.log.stop_logging() + + assert len(logger.handlers) == 0 + + +# This doesn't test functionality at all +# Need rewrite +# def test_start_stop_logging(): +# try: +# MDAnalysis.log.start_logging() +# logger = logging.getLogger("MDAnalysis") +# logger.info("Using the MDAnalysis logger works") +# except Exception as err: +# raise AssertionError("Problem with logger: {0}".format(err)) +# finally: +# MDAnalysis.log.stop_logging() class TestProgressBar(object): From a81e4adedfb217bd2ceae00f62ff0070fc2e5c9a Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 23:25:23 -0700 Subject: [PATCH 19/29] Added comment --- testsuite/MDAnalysisTests/lib/test_log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testsuite/MDAnalysisTests/lib/test_log.py b/testsuite/MDAnalysisTests/lib/test_log.py index 672ed3ddd6e..8b8c114e12b 100644 --- a/testsuite/MDAnalysisTests/lib/test_log.py +++ b/testsuite/MDAnalysisTests/lib/test_log.py @@ -34,7 +34,7 @@ def test_start_logging(self, tmp_path): mda.start_logging(tmp_path / "MDAnalysis.log") logger = logging.getLogger("MDAnalysis") - # Test Handlers' presence and behavior + # Test expected handlers' presence and behavior assert any(isinstance(h, logging.NullHandler) for h in logger.handlers) any( isinstance(h, logging.FileHandler) From f25aec7440165fd0f0169d9d8c01dc6ef274db18 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 23:25:23 -0700 Subject: [PATCH 20/29] Added comment --- testsuite/MDAnalysisTests/lib/test_log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testsuite/MDAnalysisTests/lib/test_log.py b/testsuite/MDAnalysisTests/lib/test_log.py index 672ed3ddd6e..8b8c114e12b 100644 --- a/testsuite/MDAnalysisTests/lib/test_log.py +++ b/testsuite/MDAnalysisTests/lib/test_log.py @@ -34,7 +34,7 @@ def test_start_logging(self, tmp_path): mda.start_logging(tmp_path / "MDAnalysis.log") logger = logging.getLogger("MDAnalysis") - # Test Handlers' presence and behavior + # Test expected handlers' presence and behavior assert any(isinstance(h, logging.NullHandler) for h in logger.handlers) any( isinstance(h, logging.FileHandler) From bdaf241e7ae621a4e71797e2b6c2f78440752e5a Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Thu, 23 Apr 2026 23:28:30 -0700 Subject: [PATCH 21/29] post black --- testsuite/MDAnalysisTests/lib/test_log.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/testsuite/MDAnalysisTests/lib/test_log.py b/testsuite/MDAnalysisTests/lib/test_log.py index 8b8c114e12b..d8a0a6e9e21 100644 --- a/testsuite/MDAnalysisTests/lib/test_log.py +++ b/testsuite/MDAnalysisTests/lib/test_log.py @@ -57,19 +57,6 @@ def test_stop_logging(self, tmp_path): assert len(logger.handlers) == 0 -# This doesn't test functionality at all -# Need rewrite -# def test_start_stop_logging(): -# try: -# MDAnalysis.log.start_logging() -# logger = logging.getLogger("MDAnalysis") -# logger.info("Using the MDAnalysis logger works") -# except Exception as err: -# raise AssertionError("Problem with logger: {0}".format(err)) -# finally: -# MDAnalysis.log.stop_logging() - - class TestProgressBar(object): def test_output(self, capsys): From d0b732f3ba78a9b8deaf2043a55f151d0b5fc749 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Fri, 24 Apr 2026 08:14:20 -0700 Subject: [PATCH 22/29] Added comments about deprecations --- package/MDAnalysis/__init__.py | 2 +- package/MDAnalysis/lib/log.py | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/package/MDAnalysis/__init__.py b/package/MDAnalysis/__init__.py index 8b5db410db5..fe9023f694a 100644 --- a/package/MDAnalysis/__init__.py +++ b/package/MDAnalysis/__init__.py @@ -200,7 +200,7 @@ from .lib import log from .lib.log import start_logging, stop_logging -logging.getLogger("MDAnalysis").addHandler(logging.NullHandler()) +logging.getLogger(__name__).addHandler(logging.NullHandler()) del logging # only MDAnalysis DeprecationWarnings are loud by default diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index ad3acaff484..a50399c83dc 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -94,6 +94,11 @@ from .. import version +# Things to deprecated: +# logfile -> stream (bc it could be any stream like object) +# +# version? Why would any user want to modify version in the first place? +# Just have the message logged it directly def start_logging(stream="MDAnalysis.log", version=version.__version__): """Start logging of messages to file and console. @@ -124,7 +129,22 @@ def stop_logging(): logger.info("MDAnalysis STOPPED logging") clear_handlers(logger) # this _should_ do the job... +# Things to deprecated overall: +# log.NullHandler -> replace with logging.NullHandler() warning +# create() -> add_handler() (confusing bc standard library has the same method) +# or create_handler()? +# +# For create(): +# logger_name -> None (very dangerous to have user choose since all loggers share the same namespace) +# logfile -> stream (bc it could be any stream like object, not just logfiles) + +# logger_name should be deprecated. +# Standard library recommends constructing through +# logging.getLogger(__name__) because ll loggers share the same namespace +# __name__ is MDAnalysis +# +# 2nd paragraph: https://docs.python.org/3/library/logging.html#logger-objects def create( logger_name="MDAnalysis", stream="MDAnalysis.log", From ec0ed2549c226d07606f2b81444393e6f1b5afb3 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Fri, 24 Apr 2026 08:22:59 -0700 Subject: [PATCH 23/29] Replaced logging.getLogger(MDAnalysis) with logging.getLogger(__name__) --- package/MDAnalysis/lib/log.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index a50399c83dc..2dbb17e3f24 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -118,14 +118,14 @@ def start_logging(stream="MDAnalysis.log", version=version.__version__): level="INFO", fmt="%(name)-12s: %(levelname)-8s %(message)s", ) - logging.getLogger("MDAnalysis").info( + logging.getLogger(__name__).info( f"MDAnalysis {version} STARTED logging to {stream!r}" ) def stop_logging(): """Stop logging to logfile and console.""" - logger = logging.getLogger("MDAnalysis") + logger = logging.getLogger(__name__) logger.info("MDAnalysis STOPPED logging") clear_handlers(logger) # this _should_ do the job... From 6d1a675884e34b3338b8f10fc045786ab976d4fd Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Fri, 24 Apr 2026 08:33:35 -0700 Subject: [PATCH 24/29] refined comments --- package/MDAnalysis/lib/log.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index 2dbb17e3f24..ab6e2c41288 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -100,6 +100,7 @@ # version? Why would any user want to modify version in the first place? # Just have the message logged it directly + def start_logging(stream="MDAnalysis.log", version=version.__version__): """Start logging of messages to file and console. @@ -129,22 +130,24 @@ def stop_logging(): logger.info("MDAnalysis STOPPED logging") clear_handlers(logger) # this _should_ do the job... + # Things to deprecated overall: # log.NullHandler -> replace with logging.NullHandler() warning -# create() -> add_handler() (confusing bc standard library has the same method) -# or create_handler()? +# create() -> add_handler() (potentially confusing bc standard library +# has the same method but in camelcase weirdly?) +# or create_handler()? # # For create(): -# logger_name -> None (very dangerous to have user choose since all loggers share the same namespace) # logfile -> stream (bc it could be any stream like object, not just logfiles) - - # logger_name should be deprecated. +# # Standard library recommends constructing through -# logging.getLogger(__name__) because ll loggers share the same namespace +# logging.getLogger(__name__) because all loggers share the same namespace +# and this is a systemmatic way of defining loggers +# # __name__ is MDAnalysis +# See 2nd paragraph: https://docs.python.org/3/library/logging.html#logger-objects # -# 2nd paragraph: https://docs.python.org/3/library/logging.html#logger-objects def create( logger_name="MDAnalysis", stream="MDAnalysis.log", @@ -169,6 +172,7 @@ def create( http://docs.python.org/library/logging.html?#logging-to-multiple-destinations """ + # replaced with logging.getLogger(__name__) logger = logging.getLogger(logger_name) # This checks for file-like object per duck typing From 74bcc0300d5da5f2f5922f76a9549905ab49b7d9 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Fri, 24 Apr 2026 08:40:41 -0700 Subject: [PATCH 25/29] Updated tests --- testsuite/MDAnalysisTests/lib/test_log.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testsuite/MDAnalysisTests/lib/test_log.py b/testsuite/MDAnalysisTests/lib/test_log.py index d8a0a6e9e21..8d6eecac032 100644 --- a/testsuite/MDAnalysisTests/lib/test_log.py +++ b/testsuite/MDAnalysisTests/lib/test_log.py @@ -32,7 +32,7 @@ class TestConvenienceFunctions: def test_start_logging(self, tmp_path): mda.start_logging(tmp_path / "MDAnalysis.log") - logger = logging.getLogger("MDAnalysis") + logger = logging.getLogger(__name__) # Test expected handlers' presence and behavior assert any(isinstance(h, logging.NullHandler) for h in logger.handlers) @@ -51,7 +51,7 @@ def test_start_logging(self, tmp_path): def test_stop_logging(self, tmp_path): mda.lib.log.start_logging(tmp_path / "MDAnalysis.log") - logger = logging.getLogger("MDAnalysis") + logger = logging.getLogger(__name__) mda.lib.log.stop_logging() assert len(logger.handlers) == 0 From 0fdd02e17ab067fc003d976098a5ef61280fe661 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Fri, 24 Apr 2026 09:23:22 -0700 Subject: [PATCH 26/29] Change __name__ to MDAnalysis fix tests --- package/MDAnalysis/__init__.py | 2 +- package/MDAnalysis/lib/log.py | 2 +- testsuite/MDAnalysisTests/lib/test_log.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package/MDAnalysis/__init__.py b/package/MDAnalysis/__init__.py index fe9023f694a..8b5db410db5 100644 --- a/package/MDAnalysis/__init__.py +++ b/package/MDAnalysis/__init__.py @@ -200,7 +200,7 @@ from .lib import log from .lib.log import start_logging, stop_logging -logging.getLogger(__name__).addHandler(logging.NullHandler()) +logging.getLogger("MDAnalysis").addHandler(logging.NullHandler()) del logging # only MDAnalysis DeprecationWarnings are loud by default diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index ab6e2c41288..a060d7bcdd1 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -126,7 +126,7 @@ def start_logging(stream="MDAnalysis.log", version=version.__version__): def stop_logging(): """Stop logging to logfile and console.""" - logger = logging.getLogger(__name__) + logger = logging.getLogger("MDAnalysis") logger.info("MDAnalysis STOPPED logging") clear_handlers(logger) # this _should_ do the job... diff --git a/testsuite/MDAnalysisTests/lib/test_log.py b/testsuite/MDAnalysisTests/lib/test_log.py index 8d6eecac032..d8a0a6e9e21 100644 --- a/testsuite/MDAnalysisTests/lib/test_log.py +++ b/testsuite/MDAnalysisTests/lib/test_log.py @@ -32,7 +32,7 @@ class TestConvenienceFunctions: def test_start_logging(self, tmp_path): mda.start_logging(tmp_path / "MDAnalysis.log") - logger = logging.getLogger(__name__) + logger = logging.getLogger("MDAnalysis") # Test expected handlers' presence and behavior assert any(isinstance(h, logging.NullHandler) for h in logger.handlers) @@ -51,7 +51,7 @@ def test_start_logging(self, tmp_path): def test_stop_logging(self, tmp_path): mda.lib.log.start_logging(tmp_path / "MDAnalysis.log") - logger = logging.getLogger(__name__) + logger = logging.getLogger("MDAnalysis") mda.lib.log.stop_logging() assert len(logger.handlers) == 0 From d952902bb45788f590d22358e971a656686cf522 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Fri, 24 Apr 2026 10:06:34 -0700 Subject: [PATCH 27/29] Updated Tests --- testsuite/MDAnalysisTests/lib/test_log.py | 32 +++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/testsuite/MDAnalysisTests/lib/test_log.py b/testsuite/MDAnalysisTests/lib/test_log.py index d8a0a6e9e21..8b2faefea3c 100644 --- a/testsuite/MDAnalysisTests/lib/test_log.py +++ b/testsuite/MDAnalysisTests/lib/test_log.py @@ -21,6 +21,7 @@ # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # +import pytest import sys import logging import MDAnalysis as mda @@ -57,6 +58,37 @@ def test_stop_logging(self, tmp_path): assert len(logger.handlers) == 0 +# TODO need to make a fixture that can clear all handlers per test +class TestCreateBehaviors: + + def test_input_path(self, tmp_path): + mda.lib.log.create(stream=tmp_path / "foo.log") + + assert (tmp_path / "foo.log").exists() + + def test_input_string(self, tmp_path): + mda.lib.log.create(stream=str(tmp_path / "foo.log")) + + assert (tmp_path / "foo.log").exists() + + # NOTE Assert state could be cleaned up after clear_handlers() fixture is implemented + def test_input_stream(self): + mda.lib.log.create(stream=sys.stdout) + + logger = logging.getLogger("MDAnalysis") + assert any( + isinstance(h, logging.StreamHandler) and h.stream is sys.stdout + for h in logger.handlers + ) + + def test_exception(tmp_path): + with pytest.raises( + TypeError, + match="Input Stream is neither a string, PathLike object or a stream", + ): + mda.lib.log.create(stream=2) + + class TestProgressBar(object): def test_output(self, capsys): From c34eca5bcf67cd6a5245f8a561f57ba392434d7a Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Fri, 24 Apr 2026 12:52:20 -0700 Subject: [PATCH 28/29] Added future tests --- testsuite/MDAnalysisTests/lib/test_log.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/testsuite/MDAnalysisTests/lib/test_log.py b/testsuite/MDAnalysisTests/lib/test_log.py index 8b2faefea3c..4bb434d9756 100644 --- a/testsuite/MDAnalysisTests/lib/test_log.py +++ b/testsuite/MDAnalysisTests/lib/test_log.py @@ -57,6 +57,11 @@ def test_stop_logging(self, tmp_path): assert len(logger.handlers) == 0 +def test_message_console(tmp_path): + pass + +def test_message_file(tmp_path): + pass # TODO need to make a fixture that can clear all handlers per test class TestCreateBehaviors: @@ -88,6 +93,14 @@ def test_exception(tmp_path): ): mda.lib.log.create(stream=2) +def test_level_parameter(): + pass + +def test_fmt_parameter(): + pass + +def test_mode_parameter(): + pass class TestProgressBar(object): From f9a4961adc4fc4cfbd189edccaec130d83ff37a6 Mon Sep 17 00:00:00 2001 From: Joshua Raphael Uy Date: Fri, 1 May 2026 14:20:03 -0700 Subject: [PATCH 29/29] Finished tests for mda.start_logging() and mda.stop_logging() --- package/MDAnalysis/lib/log.py | 5 +-- testsuite/MDAnalysisTests/lib/test_log.py | 37 ++++++++++++++++++++--- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/package/MDAnalysis/lib/log.py b/package/MDAnalysis/lib/log.py index a060d7bcdd1..d93b9d4d74f 100644 --- a/package/MDAnalysis/lib/log.py +++ b/package/MDAnalysis/lib/log.py @@ -119,8 +119,8 @@ def start_logging(stream="MDAnalysis.log", version=version.__version__): level="INFO", fmt="%(name)-12s: %(levelname)-8s %(message)s", ) - logging.getLogger(__name__).info( - f"MDAnalysis {version} STARTED logging to {stream!r}" + logging.getLogger("MDAnalysis").info( + f"MDAnalysis {version} STARTED logging to {stream}" ) @@ -174,6 +174,7 @@ def create( # replaced with logging.getLogger(__name__) logger = logging.getLogger(logger_name) + logger.setLevel(logging.DEBUG) # This checks for file-like object per duck typing # https://docs.python.org/3/library/logging.handlers.html#streamhandler diff --git a/testsuite/MDAnalysisTests/lib/test_log.py b/testsuite/MDAnalysisTests/lib/test_log.py index 4bb434d9756..f7eb16c58c7 100644 --- a/testsuite/MDAnalysisTests/lib/test_log.py +++ b/testsuite/MDAnalysisTests/lib/test_log.py @@ -24,12 +24,22 @@ import pytest import sys import logging +import time import MDAnalysis as mda from os.path import basename from MDAnalysis.lib.log import ProgressBar +@pytest.fixture +def fixed_log_time(monkeypatch): + """Pytest fixture which temporarily set the logging's t0 to the UNIX epoc""" + epoch = 0 + + monkeypatch.setattr(logging.time, "time", lambda: epoch) + monkeypatch.setattr(logging.Formatter, "converter", time.gmtime) + + class TestConvenienceFunctions: def test_start_logging(self, tmp_path): mda.start_logging(tmp_path / "MDAnalysis.log") @@ -57,11 +67,26 @@ def test_stop_logging(self, tmp_path): assert len(logger.handlers) == 0 -def test_message_console(tmp_path): - pass + def test_message_file(self, tmp_path, fixed_log_time): + + mda.start_logging(tmp_path / "MDAnalysis.log") + + with open(tmp_path / "MDAnalysis.log") as f: + + assert ( + "1970-01-01 00:00:00,000 MDAnalysis INFO " + + f"MDAnalysis {mda.version.__version__} STARTED logging to {str(tmp_path)}" + ) + + def test_message_console(self, tmp_path, capsys): + mda.start_logging(tmp_path / "MDAnalysis.log") + + assert ( + "MDAnalysis : INFO " + + f"MDAnalysis {mda.version.__version__} STARTED logging to {str(tmp_path)}" + in capsys.readouterr().out + ) -def test_message_file(tmp_path): - pass # TODO need to make a fixture that can clear all handlers per test class TestCreateBehaviors: @@ -93,15 +118,19 @@ def test_exception(tmp_path): ): mda.lib.log.create(stream=2) + def test_level_parameter(): pass + def test_fmt_parameter(): pass + def test_mode_parameter(): pass + class TestProgressBar(object): def test_output(self, capsys):