Source code for matpy.log_util
#!/usr/bin/env python
"""
This module includes a custom logger to make it easier to identify errors and
debug etc.
The module adds both another level to the logger and the corresponding
formatter. If you want to remove or add any logging level make sure to edit
both the CustomLogger and the CustomFormatter to accommodate your changes!
:author:
Lucas Sawade (lsawade@princeton.edu, 2019)
:license:
GNU Lesser General Public License, Version 3
(http://www.gnu.org/copyleft/lgpl.html)
"""
import logging
from logging import getLoggerClass
from logging import NOTSET
VERBOSE = 15
[docs]def addLoggingLevel(levelName, levelNum, methodName=None):
"""
Comprehensively adds a new logging level to the `logging` module and the
currently configured logging class.
`levelName` becomes an attribute of the `logging` module with the value
`levelNum`. `methodName` becomes a convenience method for both `logging`
itself and the class returned by `logging.getLoggerClass()` (usually just
`logging.Logger`). If `methodName` is not specified, `levelName.lower()` is
used.
To avoid accidental clobberings of existing attributes, this method will
raise an `AttributeError` if the level name is already an attribute of the
`logging` module or if the method name is already present
.. rubric:: Example
.. code-block:: python
addLoggingLevel('TRACE', logging.DEBUG - 5)
logging.getLogger(__name__).setLevel("TRACE")
logging.getLogger(__name__).trace('that worked')
logging.trace('so did this')
logging.TRACE
5
Taken from StackOverflow because the code was beautifully simple.
Author: Mad Physicist (Mar 4, 2016)
"""
if not methodName:
methodName = levelName.lower()
if hasattr(logging, levelName):
raise AttributeError(
'{} already defined in logging module'.format(levelName))
if hasattr(logging, methodName):
raise AttributeError(
'{} already defined in logging module'.format(methodName))
if hasattr(logging.getLoggerClass(), methodName):
raise AttributeError(
'{} already defined in logger class'.format(methodName))
# This method was inspired by the answers to Stack Overflow post
# http://stackoverflow.com/q/2183233/2988730, especially
# http://stackoverflow.com/a/13638084/2988730
def logForLevel(self, message, *args, **kwargs):
if self.isEnabledFor(levelNum):
self._log(levelNum, message, args, **kwargs)
def logToRoot(message, *args, **kwargs):
logging.log(levelNum, message, *args, **kwargs)
logging.addLevelName(levelNum, levelName)
setattr(logging, levelName, levelNum)
setattr(logging.getLoggerClass(), methodName, logForLevel)
setattr(logging, methodName, logToRoot)
[docs]class CustomLogger(getLoggerClass()):
"""
This class is just created ot add the VERBOSE level. More
level could be added to this class to accommodate other levels.
The variable VERBOSE is given at the top of the module. That way it can be
changed for all depending function
The class makes it possible to add extra levels to the classic logger
The line `addLoggingLevel("VERBOSE", VERBOSE)` in the initalization is
an example on how to add a level using the `addLoggingLevel` function
located in this module.
Don't forget to edit the `CustomFormatter` to accommodate for your
introduced levels if you are using the `CustomFormatter`.
An example is given in the class under `# EXTRA LEVELS` for the
`VERBOSE` level.
"""
def __init__(self, name, level=NOTSET):
super().__init__(name, level)
addLoggingLevel("VERBOSE", VERBOSE)
[docs]class CustomFormatter(logging.Formatter):
"""
Logging Formatter to add colors and count warning / errors
This class organizes the customization of the logging output.
The formatter as of now outputs the logs in the following manner in
order of Loglevel:
.. rubric:: Example Output
.. code-block:: python
[2020-04-03 14:17:18] -- matpy.matrixmultiplication ----- [INFO]: Initializing matrices...
[2020-04-03 14:17:18] -- matpy.matrixmultiplication ---- [ERROR]: Test Error Level (matrixmultiplication.py:60)
[2020-04-03 14:17:18] -- matpy.matrixmultiplication - [CRITICAL]: Test Critical Level (matrixmultiplication.py:61)
[2020-04-03 14:17:18] -- matpy.matrixmultiplication -- [VERBOSE]: Test Verbose Level
[2020-04-03 14:17:18] -- matpy.matrixmultiplication -- [VERBOSE]: A:
[2020-04-03 14:17:18] -- matpy.matrixmultiplication -- [VERBOSE]: [1 2]
[2020-04-03 14:17:18] -- matpy.matrixmultiplication -- [VERBOSE]: [3 4]
[2020-04-03 14:17:18] -- matpy.matrixmultiplication -- [VERBOSE]: B:
[2020-04-03 14:17:18] -- matpy.matrixmultiplication -- [VERBOSE]: [2 3 5]
[2020-04-03 14:17:18] -- matpy.matrixmultiplication -- [VERBOSE]: [4 5 6]
[2020-04-03 14:17:18] -- matpy.matrixmultiplication -- [WARNING]: Matrix size exceeds 4 elements.
These outputs are colored in the actual output but the formatting is just
as shown above. VERBOSE is an extra added LogLevel formatting. More can be
added below the comment `EXTRA LEVELS` in the same way the VERBOSE is added.
The variable VERBOSE is given at the top of the module. That way it can be
changed for all depending function
"""
# Setting up the different ANSI color escape sequences for color terminals:
# Codes are 3/4-bit codes from here:
# https://en.wikipedia.org/wiki/ANSI_escape_code
# For 8-colors: use "\x1b[38;5;<n>m" where <n> is the number of the color.
grey = "\x1b[38;21m"
green = "\x1b[38;5;64m"
light_grey = "\x1b[38;5;240m"
dark_blue = "\x1b[38;5;25m"
light_blue = "\x1b[38;5;69m"
yellow = "\x1b[33;21m"
red = "\x1b[31;21m"
dark_red = "\x1b[38;5;97m"
bold_red = "\x1b[31;1m"
reset = "\x1b[0m"
# Formats The spaces accommodate the different length of the words and
# amount of detail wanted in the message:
time_fmt = light_grey + "[%(asctime)s]" + reset
name_fmt = "-- %(name)s -"
pre_fmt = time_fmt + " " + name_fmt
debug_fmt = "--- [" + light_blue + "%(levelname)s" + reset + "]:" \
+ light_blue + " %(message)s (%(filename)s:%(lineno)d)" + reset
info_fmt = "---- [%(levelname)s]: %(message)s"
warning_fmt = "- [" + yellow + "%(levelname)s" + reset + "]:" \
+ yellow + " %(message)s" + reset
error_fmt = "--- [" + red + "%(levelname)s" + reset + "]:" \
+ red + " %(message)s (%(filename)s:%(lineno)d)" + reset
critical_fmt = " [" + bold_red + "%(levelname)s" + reset + "]:" \
+ bold_red + " %(message)s (%(filename)s:%(lineno)d)" + reset
# Create format dictionary
FORMATS = {
logging.DEBUG: pre_fmt + debug_fmt,
logging.INFO: pre_fmt + info_fmt,
logging.WARNING: pre_fmt + warning_fmt,
logging.ERROR: pre_fmt + error_fmt,
logging.CRITICAL: pre_fmt + critical_fmt
}
# EXTRA LEVELS
# Verbose
addLoggingLevel('VERBOSE', VERBOSE)
verbose_fmt = "- [%(levelname)s]: %(message)s"
FORMATS[logging.VERBOSE] = pre_fmt + verbose_fmt # add to format dictionary
# Initialize with a default logging.Formatter
def __init__(self):
super().__init__(fmt="-- %(name)s ---- [%(levelname)s]: %(message)s",
datefmt=None, style='%')
[docs] def format(self, record):
# Use the logging.LEVEL to get the right formatting
log_fmt = self.FORMATS.get(record.levelno)
# Create new formatter with modified timestamp formatting.
formatter = logging.Formatter(log_fmt, "%Y-%m-%d %H:%M:%S")
# Return
return formatter.format(record)
def modify_logger(logger):
# Make sure only this module prints the logger information.
logger.propagate = 0
# Add formatter
ch = logging.StreamHandler()
ch.setFormatter(CustomFormatter())
logger.addHandler(ch)
return logger