100 lines
4.4 KiB
Python
Executable File
100 lines
4.4 KiB
Python
Executable File
"""Nice logging, with colors on Linux."""
|
|
|
|
from typing import Any, Union
|
|
import logging
|
|
import sys
|
|
|
|
class ColoredFormatter(logging.Formatter):
|
|
"""Logging Formatter to add colors"""
|
|
|
|
fg_bright_blue = "\x1b[94m"
|
|
fg_yellow = "\x1b[33m"
|
|
fg_red = "\x1b[31m"
|
|
fg_bright_red_bold = "\x1b[91;1m"
|
|
reset = "\x1b[0m"
|
|
|
|
def __init__(self, fmt, datefmt):
|
|
super().__init__()
|
|
self.messagefmt = fmt
|
|
self.datefmt = datefmt
|
|
|
|
self.formats = {
|
|
logging.DEBUG: "{}{}{}".format(self.fg_bright_blue, self.messagefmt, self.reset),
|
|
logging.INFO: "{}".format(self.messagefmt),
|
|
logging.WARNING: "{}{}{}".format(self.fg_yellow, self.messagefmt, self.reset),
|
|
logging.ERROR: "{}{}{}".format(self.fg_red, self.messagefmt, self.reset),
|
|
logging.CRITICAL: "{}{}{}".format(self.fg_bright_red_bold, self.messagefmt, self.reset)
|
|
}
|
|
|
|
self.formatters = {
|
|
logging.DEBUG: logging.Formatter(self.formats[logging.DEBUG], datefmt = self.datefmt),
|
|
logging.INFO: logging.Formatter(self.formats[logging.INFO], datefmt = self.datefmt),
|
|
logging.WARNING: logging.Formatter(self.formats[logging.WARNING], datefmt = self.datefmt),
|
|
logging.ERROR: logging.Formatter(self.formats[logging.ERROR], datefmt = self.datefmt),
|
|
logging.CRITICAL: logging.Formatter(self.formats[logging.CRITICAL], datefmt = self.datefmt)
|
|
}
|
|
|
|
def format(self, record):
|
|
formatter = self.formatters[record.levelno]
|
|
return formatter.format(record)
|
|
|
|
def setup_root_logger(
|
|
no_color: bool = False,
|
|
verbosity_level: int = 0,
|
|
quietness_level: int = 0,
|
|
messagefmt: str = "[%(asctime)s][%(levelname)s] %(message)s (%(filename)s:%(lineno)d)",
|
|
messagefmt_verbose: str = "[%(asctime)s][%(levelname)s] %(message)s (%(filename)s:%(lineno)d)",
|
|
datefmt: str = "%Y-%m-%d %H:%M:%S"
|
|
):
|
|
messagefmt_to_use = messagefmt_verbose if verbosity_level else messagefmt
|
|
logging_level = 10 * (2 + quietness_level - verbosity_level)
|
|
if not no_color and sys.platform == "linux":
|
|
formatter_class = ColoredFormatter
|
|
else:
|
|
formatter_class = logging.Formatter
|
|
|
|
root_logger = logging.getLogger()
|
|
root_logger.setLevel(logging_level)
|
|
console_handler = logging.StreamHandler()
|
|
console_handler.setFormatter(formatter_class(fmt = messagefmt_to_use, datefmt = datefmt))
|
|
root_logger.addHandler(console_handler)
|
|
|
|
def logging_fatal(message, log_stack_info: bool = True, exit_code: int = 1):
|
|
logging.critical(message)
|
|
logging.debug("Stack Trace", stack_info = log_stack_info)
|
|
logging.critical("Exiting")
|
|
raise SystemExit(exit_code)
|
|
|
|
def log_tree(title, tree, finals = None, log_leaves_types = True, logging_level = logging.INFO):
|
|
"""Log tree nicely if it is a dictionary.
|
|
log_leaves_types can be False to log no leaves, True to log all leaves, or a tuple of types for which to log."""
|
|
if finals is None:
|
|
finals = []
|
|
if not isinstance(tree, dict):
|
|
logging.log(msg = "{}{}{}".format(
|
|
"".join([" " if final else "│" for final in finals[:-1]] + ["└" if final else "├" for final in finals[-1:]]),
|
|
title,
|
|
": {}".format(tree) if log_leaves_types is not False and (log_leaves_types is True or isinstance(tree, log_leaves_types)) else ""
|
|
), level = logging_level)
|
|
else:
|
|
logging.log(msg = "{}{}".format(
|
|
"".join([" " if final else "│" for final in finals[:-1]] + ["└" if final else "├" for final in finals[-1:]]),
|
|
title
|
|
), level = logging_level)
|
|
tree_items = list(tree.items())
|
|
for key, value in tree_items[:-1]:
|
|
log_tree(key, value, finals = finals + [False], log_leaves_types = log_leaves_types, logging_level = logging_level)
|
|
for key, value in tree_items[-1:]:
|
|
log_tree(key, value, finals = finals + [True], log_leaves_types = log_leaves_types, logging_level = logging_level)
|
|
|
|
# like logging.CRITICAl, logging.DEBUG etc
|
|
FATAL = 60
|
|
|
|
def perror(s: Union[str, Any], e: Exception, logging_level: int = logging.ERROR):
|
|
strerror = e.strerror if (isinstance(e, OSError) and e.strerror is not None) else e.__class__.__name__
|
|
msg = f"{s}{': ' if s else ''}{strerror}"
|
|
if logging_level == FATAL:
|
|
logging_fatal(msg)
|
|
else:
|
|
logging.log(logging_level, msg)
|