diff --git a/bidscoin/__init__.py b/bidscoin/__init__.py index 437c79ed..8756efba 100644 --- a/bidscoin/__init__.py +++ b/bidscoin/__init__.py @@ -28,7 +28,7 @@ from pathlib import Path from importlib import metadata from typing import Tuple, Union, List -from logging import getLogger +import logging from .due import due, Doi try: import tomllib @@ -42,8 +42,6 @@ with open(Path(__file__).parents[1]/'pyproject.toml', 'rb') as fid: __version__ = tomllib.load(fid)['project']['version'] -LOGGER = getLogger(__name__) - # Add license metadata __license__ = 'GNU General Public License v3.0 or later (GPLv3+)' __copyright__ = f"2018-{datetime.date.today().year}, Marcel Zwiers" @@ -101,6 +99,40 @@ path='bidscoin', version=__version__, cite_module=True, tags=['reference-implementation']) +class CustomLogger(logging.Logger): + """Extend the Logger class to add custom methods for the new levels""" + + # Define custom logging levels + BCDEBUG, BCDEBUG_LEVEL = 'BCDEBUG', 11 # NB: using the standard debug mode will generate may debug messages from imports + VERBOSE, VERBOSE_LEVEL = 'VERBOSE', 15 + SUCCESS, SUCCESS_LEVEL = 'SUCCESS', 25 + + # Add custom log levels to logging + logging.addLevelName(BCDEBUG_LEVEL, BCDEBUG) + logging.addLevelName(VERBOSE_LEVEL, VERBOSE) + logging.addLevelName(SUCCESS_LEVEL, SUCCESS) + + def bcdebug(self, message, *args, **kwargs): + """Custom BIDSCOIN DEBUG messages""" + if self.isEnabledFor(self.BCDEBUG_LEVEL): + self._log(self.BCDEBUG_LEVEL, message, args, **kwargs) + + def verbose(self, message, *args, **kwargs): + """Custom BIDSCOIN VERBOSE messages""" + if self.isEnabledFor(self.VERBOSE_LEVEL): + self._log(self.VERBOSE_LEVEL, message, args, **kwargs) + + def success(self, message, *args, **kwargs): + """Custom BIDSCOIN SUCCESS messages""" + if self.isEnabledFor(self.SUCCESS_LEVEL): + self._log(self.SUCCESS_LEVEL, message, args, **kwargs) + + +# Get a logger from the custom logger class +logging.setLoggerClass(CustomLogger) +LOGGER = logging.getLogger(__name__) + + def check_version() -> Tuple[str, Union[bool, None], str]: """ Compares the BIDSCOIN version from the local metadata to the remote pypi repository diff --git a/bidscoin/bcoin.py b/bidscoin/bcoin.py index 26be81bc..f0d1ff84 100755 --- a/bidscoin/bcoin.py +++ b/bidscoin/bcoin.py @@ -113,13 +113,12 @@ def synchronize(pbatch, jobids: list, wait: int=15): def setup_logging(logfile: Path=Path()): """ Set up the logging framework: - 1) Add a 'bcdebug', 'verbose' and a 'success' logging level - 2) Add a console streamhandler - 3) If logfile then add a normal log and a warning/error filehandler + 1) Add custom logging levels: 'bcdebug', 'verbose', and 'success'. + 2) Add a console stream handler for generating terminal output. + 3) Optionally add file handlers for normal log and warning/error log if logfile is provided. - :param logfile: Name of the logfile - :return: - """ + :param logfile: Path to the logfile. If none, logging is console-only + """ # Set the default formats if DEBUG: @@ -130,31 +129,7 @@ def setup_logging(logfile: Path=Path()): cfmt = '%(levelname)s | %(message)s' datefmt = '%Y-%m-%d %H:%M:%S' - # Add a BIDScoin debug logging level = 11 (NB: using the standard debug mode will generate may debug messages from imports) - logging.BCDEBUG = 11 - logging.addLevelName(logging.BCDEBUG, 'BCDEBUG') - logging.__all__ += ['BCDEBUG'] if 'BCDEBUG' not in logging.__all__ else [] - def bcdebug(self, message, *args, **kws): - if self.isEnabledFor(logging.BCDEBUG): self._log(logging.BCDEBUG, message, args, **kws) - logging.Logger.bcdebug = bcdebug - - # Add a verbose logging level = 15 - logging.VERBOSE = 15 - logging.addLevelName(logging.VERBOSE, 'VERBOSE') - logging.__all__ += ['VERBOSE'] if 'VERBOSE' not in logging.__all__ else [] - def verbose(self, message, *args, **kws): - if self.isEnabledFor(logging.VERBOSE): self._log(logging.VERBOSE, message, args, **kws) - logging.Logger.verbose = verbose - - # Add a success logging level = 25 - logging.SUCCESS = 25 - logging.addLevelName(logging.SUCCESS, 'SUCCESS') - logging.__all__ += ['SUCCESS'] if 'SUCCESS' not in logging.__all__ else [] - def success(self, message, *args, **kws): - if self.isEnabledFor(logging.SUCCESS): self._log(logging.SUCCESS, message, args, **kws) - logging.Logger.success = success - - # Set the root logging level + # Get the root logger and set the appropriate level logger = logging.getLogger() logger.setLevel('BCDEBUG' if DEBUG else 'VERBOSE') @@ -194,7 +169,7 @@ def reporterrors() -> str: # Find the filehandlers and report the errors and warnings errors = '' - for handler in logging.getLogger().handlers: + for handler in LOGGER.handlers: if handler.name == 'errorhandler': errorfile = Path(handler.baseFilename)