Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
Colorful Python logger
# -*- coding: utf-8 -*-
Python logging tuned to extreme.
__author__ = "Mikko Ohtamaa <>"
__license__ = "MIT"
import logging
from logutils.colorize import ColorizingStreamHandler
class RainbowLoggingHandler(ColorizingStreamHandler):
""" A colorful logging handler optimized for terminal debugging aestetichs.
- Designed for diagnosis and debug mode output - not for disk logs
- Highlight the content of logging message in more readable manner
- Show function and line, so you can trace where your logging messages
are coming from
- Keep timestamp compact
- Extra module/function output for traceability
The class provide few options as member variables you
would might want to customize after instiating the handler.
# Define color for message payload
level_map = {
logging.DEBUG: (None, 'cyan', False),
logging.INFO: (None, 'white', False),
logging.WARNING: (None, 'yellow', True),
logging.ERROR: (None, 'red', True),
logging.CRITICAL: ('red', 'white', True),
date_format = "%H:%m:%S"
#: How many characters reserve to function name logging
who_padding = 22
#: Show logger name
show_name = True
def get_color(self, fg=None, bg=None, bold=False):
Construct a terminal color code
:param fg: Symbolic name of foreground color
:param bg: Symbolic name of background color
:param bold: Brightness bit
params = []
if bg in self.color_map:
params.append(str(self.color_map[bg] + 40))
if fg in self.color_map:
params.append(str(self.color_map[fg] + 30))
if bold:
color_code = ''.join((self.csi, ';'.join(params), 'm'))
return color_code
def colorize(self, record):
Get a special format string with ASCII color codes.
# Dynamic message color based on logging level
if record.levelno in self.level_map:
fg, bg, bold = self.level_map[record.levelno]
# Defaults
bg = None
fg = "white"
bold = False
# Magician's hat
template = [
self.get_color("black", None, True),
"] ",
self.get_color("white", None, True) if self.show_name else "",
"%(name)s " if self.show_name else "",
" ",
self.get_color(bg, fg, bold),
format = "".join(template)
who = [self.get_color("green"),
getattr(record, "funcName", ""),
self.get_color("black", None, True),
str(getattr(record, "lineno", 0))]
who = "".join(who)
# We need to calculate padding length manualy
# as color codes mess up string length based calcs
unformatted_who = getattr(record, "funcName", "") + "()" + \
":" + str(getattr(record, "lineno", 0))
if len(unformatted_who) < self.who_padding:
spaces = " " * (self.who_padding - len(unformatted_who))
spaces = ""
record.padded_who = who + spaces
formatter = logging.Formatter(format, self.date_format)
self.colorize_traceback(formatter, record)
output = formatter.format(record)
# Clean cache so the color codes of traceback don't leak to other formatters
record.ext_text = None
return output
def colorize_traceback(self, formatter, record):
Turn traceback text to red.
if record.exc_info:
# Cache the traceback text to avoid converting it multiple times
# (it's constant anyway)
record.exc_text = "".join([
def format(self, record):
Formats a record for output.
Takes a custom formatting path on a terminal.
if self.is_tty:
message = self.colorize(record)
message = logging.StreamHandler.format(self, record)
return message
if __name__ == "__main__":
# Run test output on stdout
import sys
root_logger = logging.getLogger()
handler = RainbowLoggingHandler(sys.stdout)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
logger = logging.getLogger("test")
def test_func():
logger.debug("debug msg")"info msg")
logger.warn("warn msg")
def test_func_2():
logger.error("error msg")
logger.critical("critical msg")
raise RuntimeError("Opa!")
except Exception as e:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment