Source code for selfisys.utils.logger

#!/usr/bin/env python3
# ----------------------------------------------------------------------
# Copyright (C) 2024 Tristan Hoellinger
# Distributed under the GNU General Public License v3.0 (GPLv3).
# See the LICENSE file in the root directory for details.
# SPDX-License-Identifier: GPL-3.0-or-later
# ----------------------------------------------------------------------

__author__ = "Tristan Hoellinger"
__version__ = "0.1.0"
__date__ = "2024"
__license__ = "GPLv3"

"""
Logger routines for the SelfiSys package.

The printing routines and colours are adapted from the Simbelmynë
comological solver (https://simbelmyne.readthedocs.io/en/latest).
"""

import sys
from typing import cast
import logging
from selfisys import DEFAULT_VERBOSE_LEVEL

# Global variables for fonts
FONT_BOLDRED = "\033[1;31m"
FONT_BOLDGREEN = "\033[1;32m"
FONT_BOLDYELLOW = "\033[1;33m"
FONT_BOLDCYAN = "\033[1;36m"
FONT_BOLDGREY = "\033[1;37m"
FONT_LIGHTPURPLE = "\033[38;5;147m"

FONT_NORMAL = "\033[00m"

# Global variables for verbosity
ERROR_VERBOSITY = 0
INFO_VERBOSITY = 1
WARNING_VERBOSITY = 2
DIAGNOSTIC_VERBOSITY = 3
DEBUG_VERBOSITY = 4
DIAGNOSTIC_LEVEL = 15
logging.addLevelName(DIAGNOSTIC_LEVEL, "DIAGNOSTIC")

G__ind__ = 0  # Global variable for logger indentation


[docs] def INDENT(): """Indents the current level of outputs.""" global G__ind__ G__ind__ += 1 return G__ind__
[docs] def UNINDENT(): """Unindents the current level of outputs.""" global G__ind__ G__ind__ -= 1 return G__ind__
[docs] def PrintLeftType(message_type, FONT_COLOR): """Prints the type of output to screen. Parameters ---------- message_type (string) : type of message FONT_COLOR (string) : font color for this type of message """ from time import localtime, strftime sys.stdout.write( "[" + strftime("%H:%M:%S", localtime()) + "|" + FONT_COLOR + message_type + FONT_NORMAL + "]" ) sys.stdout.write("==" * G__ind__) sys.stdout.write("|")
[docs] def PrintInfo(message): """Prints an information to screen. Parameters ---------- message (string) : message """ if DEFAULT_VERBOSE_LEVEL >= INFO_VERBOSITY: PrintLeftType("INFO ", FONT_BOLDCYAN) sys.stdout.write("{}\n".format(message)) sys.stdout.flush()
[docs] def PrintDiagnostic(verbosity, message): """Prints a diagnostic to screen. Parameters ---------- verbosity (int) : verbosity of the message message (string) : message """ if DEFAULT_VERBOSE_LEVEL >= verbosity: PrintLeftType("DIAGNOSTIC", FONT_BOLDGREY) sys.stdout.write("{}\n".format(message))
[docs] def PrintWarning(message): """Prints a warning to screen. Parameters ---------- message (string) : message """ if DEFAULT_VERBOSE_LEVEL >= WARNING_VERBOSITY: PrintLeftType("WARNING ", FONT_BOLDYELLOW) sys.stdout.write(FONT_BOLDYELLOW + message + FONT_NORMAL + "\n")
[docs] def PrintError(message): """Prints an error to screen. Parameters ---------- message (string) : message """ if DEFAULT_VERBOSE_LEVEL >= ERROR_VERBOSITY: PrintLeftType("ERROR ", FONT_BOLDRED) sys.stdout.write(FONT_BOLDRED + message + FONT_NORMAL + "\n")
[docs] class CustomLoggerHandler(logging.Handler): """ Custom logging handler to redirect Python logger messages to custom print functions, with support for verbosity levels in debug messages. """
[docs] def emit(self, record): """ Emit a log record. """ try: log_message = self.format(record) log_level = record.levelno if log_level >= logging.ERROR: PrintError(log_message) elif log_level >= logging.WARNING: PrintWarning(log_message) elif log_level >= logging.INFO: PrintInfo(log_message) elif log_level == DIAGNOSTIC_LEVEL: # Retrieve verbosity level from the record verbosity = getattr(record, "verbosity", DIAGNOSTIC_VERBOSITY) PrintDiagnostic(verbosity=verbosity, message=log_message) elif log_level >= logging.DEBUG: PrintDiagnostic(verbosity=DEBUG_VERBOSITY, message=log_message) else: # Fallback for other levels PrintInfo(log_message) except Exception: self.handleError(record)
[docs] class CustomLogger(logging.Logger): """ Custom logger class supporting custom verbosity levels in diagnostic messages. """
[docs] def diagnostic(self, msg, *args, verbosity=DIAGNOSTIC_VERBOSITY, **kwargs) -> None: """ Log a message with DIAGNOSTIC level. Parameters ---------- msg : str The message to log. verbosity : int, optional The verbosity level required to log this message. """ if self.isEnabledFor(DIAGNOSTIC_LEVEL): # Pass verbosity as part of the extra argument extra = kwargs.get("extra", {}) extra["verbosity"] = verbosity kwargs["extra"] = extra self.log(DIAGNOSTIC_LEVEL, msg, *args, **kwargs)
logging.setLoggerClass(CustomLogger)
[docs] def getCustomLogger(name: str) -> CustomLogger: """ Get as CustomLogger instance to use the custom printing routines. Parameters ---------- name : str The name of the logger. Returns ------- logger : logging.Logger The custom logger instance. """ logging.setLoggerClass(CustomLogger) logger = cast(CustomLogger, logging.getLogger(name)) # cast for type checkers and PyLance logger.setLevel(logging.DEBUG) # Set the desired base logging level handler = CustomLoggerHandler() formatter = logging.Formatter(f"{FONT_LIGHTPURPLE}(%(name)s){FONT_NORMAL} %(message)s") handler.setFormatter(formatter) # Attach the handler to the logger if not already present if not logger.handlers: logger.addHandler(handler) return logger