Skip to content

Instantly share code, notes, and snippets.

@FabianBartl
Last active February 7, 2024 11:18
Show Gist options
  • Save FabianBartl/b3d3d2923137346d902d708d03248be0 to your computer and use it in GitHub Desktop.
Save FabianBartl/b3d3d2923137346d902d708d03248be0 to your computer and use it in GitHub Desktop.
Advanced custom logger with colored messages and color-highlighted progressbar
# Extension of the standard Python logging library with handlers for tqdm progress bars,
# colored logging messages and support for any UTF-8 characters like emojies.
import logging
from datetime import datetime
from os.path import abspath, basename
from os.path import join as joinpath
# https://alexandra-zaharia.github.io/posts/make-your-own-custom-color-formatter-with-python-logging/
# https://stackoverflow.com/a/56944256/3638629
class CustomFormatter(logging.Formatter):
def __init__(self, *, fmt:str, datefmt:str, colored:bool):
# init inherited class
super().__init__()
self.fmt = fmt
self.datefmt = datefmt
self.colored = colored
# use colored output if configured and colorama is installed
try:
if not self.colored:
raise Exception(f"{self.colored=}")
# try import and init colorama
from colorama import Fore, Back, Style, init
init(autoreset=True)
# set colored formats
reset_color = Fore.RESET + Back.RESET + Style.RESET_ALL
self.FORMATS = {
logging.DEBUG: Style.DIM + self.fmt + reset_color,
logging.INFO: Fore.BLUE + self.fmt + reset_color,
logging.WARNING: Fore.YELLOW + self.fmt + reset_color,
logging.ERROR: Fore.RED + Style.BRIGHT + self.fmt + reset_color,
logging.CRITICAL: Fore.WHITE + Back.RED + self.fmt + reset_color,
}
# use not colored output
except:
self.FORMATS = {
logging.DEBUG: self.fmt,
logging.INFO: self.fmt,
logging.WARNING: self.fmt,
logging.ERROR: self.fmt,
logging.CRITICAL: self.fmt
}
def format(self, record):
format = self.FORMATS.get(record.levelno, self.fmt)
formatter = logging.Formatter(fmt=format, datefmt=self.datefmt)
return formatter.format(record)
# return a logging handler for a colored tqdm progress bar
# def getTqdmHandler( tqdm_progressBar: tqdm, level: int ) -> TqdmLoggingHandler
def getTqdmHandler(tqdm_progressBar, level:int):
try:
import tqdm
class TqdmLoggingHandler(logging.Handler):
def __init__(self, progress_bar:tqdm, level:int):
super().__init__(level)
self.progress_bar = progress_bar
self.counter = {
logging.DEBUG: 0,
logging.INFO: 0,
logging.WARNING: 0,
logging.ERROR: 0,
logging.CRITICAL: 0
}
def emit(self, record):
if record.levelno in self.counter:
# count logs
self.counter[record.levelno] += 1
# skip most debug and info colorings to show more important colors longer
if (self.counter[record.levelno] % 20 != 0 and logging.DEBUG == record.levelno) or \
(self.counter[record.levelno] % 5 != 0 and logging.INFO == record.levelno):
return
# set color and refresh bar
self.progress_bar.colour = level_to_color(record.levelno)
self.progress_bar.refresh()
return TqdmLoggingHandler(tqdm_progressBar, level)
except Exception as error:
logging.error("failed to create handler for colored tqdm progress bar")
logging.error(error)
return None
# returns configured logging handler object
def init(name, *, name_is_path:bool=True, log_to_file:bool=True, log_to_console:bool=False, log_level:int=logging.DEBUG, colored_console:bool=True) -> logging.Logger:
# extract filename from path
if name_is_path:
name = basename(name)
# logging parameter
level = log_level
timestamp = datetime.today().strftime("%d-%m-%Y_%H-%M-%S")
path = abspath(joinpath("logs", f"{name}_{timestamp}.log"))
encoding = "utf-8"
format = "[ %(levelname)8s ] [ %(asctime)s ] %(message)s"
datefmt = "%d/%m/%Y %H:%M:%S"
# logging handlers
handlers = []
# file handler
if log_to_file:
file_handler = logging.FileHandler(path, encoding=encoding)
file_handler.setLevel(level)
file_handler.setFormatter(logging.Formatter(fmt=format, datefmt=datefmt))
handlers.append(file_handler)
# console handler
if log_to_console:
console_handler = logging.StreamHandler()
console_handler.setLevel(level)
console_handler.setFormatter(CustomFormatter(fmt=format, datefmt=datefmt, colored=colored_console))
handlers.append(console_handler)
# logging config
logging.basicConfig(level=level, encoding=encoding, format=format, datefmt=datefmt, handlers=handlers)
return logging.getLogger(name)
# converts logging level string to corresponding level integer
def level_to_int(level:str) -> int:
levels = {
"DEBUG": logging.DEBUG,
"INFO": logging.INFO,
"WARNING": logging.WARNING,
"ERROR": logging.ERROR,
"CRITICAL": logging.CRITICAL,
}
return levels.get(level, logging.DEBUG)
# converts logging level integer to corresponding hex color of level
def level_to_color(level:int, default:str=None) -> str:
levels = {
logging.DEBUG: "#7F7D7A",
logging.INFO: "#008DF8",
logging.WARNING: "#FFB900",
logging.ERROR: "#FF2740",
logging.CRITICAL: "#008DF8"
}
return levels.get(level, default)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment