Source code for hymd.logger

"""Handles event logging for simulation information, warnings, and errors.
"""
import sys
import os
import logging
from mpi4py import MPI
from .version import __version__


[docs]class MPIFilterRoot(logging.Filter): """Log output Filter wrapper class for the root MPI rank log""" comm = None def __init__(self, comm=MPI.COMM_WORLD): self.comm = comm
[docs] def filter(self, record): """Log event message filter Parameters ---------- record : logging.LogRecord LogRecord object corresponding to the log event. """ if record.funcName == "<module>": record.funcName = "main" if self.comm.Get_rank() == 0: record.rank = self.comm.Get_rank() record.size = self.comm.Get_size() return True else: return False
[docs]class MPIFilterAll(logging.Filter): """Log output Filter wrapper class for the all-MPI-ranks log""" comm = None def __init__(self, comm=MPI.COMM_WORLD): self.comm = comm
[docs] def filter(self, record): """Log event message filter Parameters ---------- record : logging.LogRecord LogRecord object corresponding to the log event. """ if record.funcName == "<module>": record.funcName = "main" record.rank = self.comm.Get_rank() record.size = self.comm.Get_size() return True
class MPIFilterStdout(logging.Filter): """Log output Filter wrapper class for filtering STDOUT log""" def filter(self, record): """Log event message filter Parameters ---------- record : logging.LogRecord LogRecord object corresponding to the log event. """ if record.funcName == "<module>": record.funcName = "main" record.funcName = "replica 0 " + record.funcName if MPI.COMM_WORLD.Get_rank() == 0: record.rank = MPI.COMM_WORLD.Get_rank() record.size = MPI.COMM_WORLD.Get_size() return True else: return False
[docs]class Logger: """Log output handler class Notes ----- This wraps the default python library :code:`logging`, see `docs.python.org/3/library/logging.html`_. .. _`docs.python.org/3/library/logging.html`: https://docs.python.org/3/library/logging.html Attributes ---------- level : int Determines the verbosity level of the log, corresponding to logging.level. Numerical values :code:`50` (:code:`logging.CRITICAL`), :code:`40` (:code:`logging.ERROR`), :code:`30` (:code:`logging.WARNING`), :code:`20` (:code:`logging.INFO`), :code:`10` (:code:`logging.DEBUG`), and :code:`0` (:code:`logging.UNSET`) are supported values. Any log event message less severe than the specified level is ignored. All other event messages are emitted. log_file : str Path to output log file. format : str Prepended dump string for each log event. Specifies the log event level, the module emitting the event, the code line, the enclosing function name, and the MPI rank writing the message. date_format : str Prepends the date before all log event messages. formatter : logging.Formatter Formatter handling the prepending of the information in `format` and `date_format` to each log event message. Used by default for all loggers. rank0 : logging.Logger Default logger object for the root MPI rank. all_ranks : logging.Logger Default logger object for messages being emitted from all MPI ranks simultaneously. """ level = None log_file = None format = " %(levelname)-8s [%(filename)s:%(lineno)d] <%(funcName)s> {rank %(rank)d/%(size)d} %(message)s" # noqa: E501 date_format = "%(asctime)s" formatter = logging.Formatter(fmt=date_format + format) rank0 = logging.getLogger("HyMD.rank_0") all_ranks = logging.getLogger("HyMD.all_ranks")
[docs] @classmethod def setup(cls, default_level=logging.INFO, log_file=None, verbose=False, comm=MPI.COMM_WORLD): """Sets up the logger object. If a :code:`log_file` path is provided, log event messages are output to it. Otherwise, the logging messages are emitted to stdout. Parameters ---------- default_level : int, optional Default verbosity level of the logger. Unless specified, it is :code:`10` (:code:`logging.INFO`). log_file : str, optional Path to output log file. If `None` or not priovided, no log file is used and all logging is done to stdout. verbose : bool, optional Increases the logging level to :code:`30` (:code:`logging.WARNING`) if True, otherwise leaves the logging level unchanged. """ cls.level = default_level cls.log_file = log_file log_to_stdout = False if verbose: log_to_stdout = True level = default_level if not verbose: level = logging.WARNING cls.rank0.setLevel(level) cls.all_ranks.setLevel(level) root_filter = MPIFilterRoot(comm) all_filter = MPIFilterAll(comm) cls.rank0.addFilter(root_filter) cls.all_ranks.addFilter(all_filter) if (not log_file) and (not log_to_stdout): return if log_file: cls.log_file_handler = logging.FileHandler(log_file) cls.log_file_handler.setLevel(level) cls.log_file_handler.setFormatter(cls.formatter) cls.rank0.addHandler(cls.log_file_handler) cls.all_ranks.addHandler(cls.log_file_handler) if log_to_stdout: cls.log_to_stdout = True cls.stdout_handler = logging.StreamHandler() cls.stdout_handler.setLevel(level) cls.stdout_handler.setStream(sys.stdout) cls.stdout_handler.setFormatter(cls.formatter) cls.stdout_handler.addFilter(MPIFilterStdout()) cls.rank0.addHandler(cls.stdout_handler) cls.all_ranks.addHandler(cls.stdout_handler)
def format_timedelta(timedelta): days = timedelta.days hours, rem = divmod(timedelta.seconds, 3600) minutes, seconds = divmod(rem, 60) microseconds = timedelta.microseconds ret_str = "" if days != 0: ret_str += f"{days} days " ret_str += f"{hours:02d}:{minutes:02d}:{seconds:02d}.{microseconds:06d}" return ret_str def get_version(): # Check if we are in a git repo and grab the commit hash and the branch if # we are, append it to the version number in the output specification. try: import git try: repo_dir = os.path.abspath( os.path.join(os.path.dirname(__file__), os.pardir) ) repo = git.Repo(repo_dir) commit = str(repo.head.commit)[:7] branch = repo.active_branch version = f"{__version__} [{branch} branch commit {commit}]" except git.exc.InvalidGitRepositoryError: version = f"{__version__}" except ModuleNotFoundError: version = f"{__version__}" return version def print_header(): banner = r""" __ __ ____ __ _______ / / / /_ __/ / /__ _________ _____ ______ / |/ / __ \ / /_/ / / / / / / _ \/ ___/ __ `/ __ `/ ___/ / /|_/ / / / / / __ / /_/ / / / __/ / / /_/ / /_/ (__ ) / / / / /_/ / /_/ /_/\__, /_/_/\___/_/ \__,_/\__,_/____/ /_/ /_/_____/ /____/ """ refs_set = """ [1] Ledum, M.; Sen, S.; Li, X.; Carrer, M.; Feng Y.; Cascella, M.; Bore, S. L. HylleraasMD: A Domain Decomposition-Based Hybrid Particle-Field Software for Multi-Scale Simulations of Soft Matter. J. Chem. Theory Comput. 2023. [2] Ledum, M.; Carrer, M.; Sen, S.; Li, X.; Cascella, M.; Bore, S. L. HyMD: Massively parallel hybrid particle-field molecular dynamics in Python. J. Open Source Softw. 8(84), 4149, 2023. [3] Sen, S.; Ledum, M.; Bore, S. L.; Cascella, M. Soft Matter under Pressure: Pushing Particle–Field Molecular Dynamics to the Isobaric Ensemble. J Chem Inf Model 2023, 63(7), 1549-9596. [4] Bore, S. L.; Cascella, M. Hamiltonian and alias-free hybrid particle–field molecular dynamics. J. Chem. Phys. 2020, 153, 094106. [5] Pippig, M. PFFT: An extension of FFTW to massively parallel architectures. SIAM J. Sci. Comput. 2013, 35, C213–C236. """ version = f"Version {get_version()}" header = banner header += version.center(56) + "\n\n" #header += " Please read and cite the references below:" header += " PLEASE READ AND CITE THE REFERENCES BELOW:" header += refs_set return header