How can I color Python logging output?

Viewed 269518

Some time ago, I saw a Mono application with colored output, presumably because of its log system (because all the messages were standardized).

Now, Python has the logging module, which lets you specify a lot of options to customize output. So, I'm imagining something similar would be possible with Python, but I can’t find out how to do this anywhere.

Is there any way to make the Python logging module output in color?

What I want (for instance) errors in red, debug messages in blue or yellow, and so on.

Of course this would probably require a compatible terminal (most modern terminals are); but I could fallback to the original logging output if color isn't supported.

Any ideas how I can get colored output with the logging module?

42 Answers

Python 3 solution, no additional packages required

Define a class

import logging

class CustomFormatter(logging.Formatter):

    grey = "\x1b[38;20m"
    yellow = "\x1b[33;20m"
    red = "\x1b[31;20m"
    bold_red = "\x1b[31;1m"
    reset = "\x1b[0m"
    format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)"

    FORMATS = {
        logging.DEBUG: grey + format + reset,
        logging.INFO: grey + format + reset,
        logging.WARNING: yellow + format + reset,
        logging.ERROR: red + format + reset,
        logging.CRITICAL: bold_red + format + reset
    }

    def format(self, record):
        log_fmt = self.FORMATS.get(record.levelno)
        formatter = logging.Formatter(log_fmt)
        return formatter.format(record)

Instantiate logger

# create logger with 'spam_application'
logger = logging.getLogger("My_app")
logger.setLevel(logging.DEBUG)

# create console handler with a higher log level
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

ch.setFormatter(CustomFormatter())

logger.addHandler(ch)

And use!

logger.debug("debug message")
logger.info("info message")
logger.warning("warning message")
logger.error("error message")
logger.critical("critical message")

Result enter image description here

The full color scheme enter image description here

For windows

This solution works on Mac OS, IDE terminals. Looks like the Windows command prompt doesn't have colors at all by default. Here are instructions on how to enable them, which I haven't try https://www.howtogeek.com/322432/how-to-customize-your-command-prompts-color-scheme-with-microsofts-colortool/

I already knew about the color escapes, I used them in my bash prompt a while ago. Thanks anyway.
What I wanted was to integrate it with the logging module, which I eventually did after a couple of tries and errors.
Here is what I end up with:

BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)

#The background is set with 40 plus the number of the color, and the foreground with 30

#These are the sequences need to get colored ouput
RESET_SEQ = "\033[0m"
COLOR_SEQ = "\033[1;%dm"
BOLD_SEQ = "\033[1m"

def formatter_message(message, use_color = True):
    if use_color:
        message = message.replace("$RESET", RESET_SEQ).replace("$BOLD", BOLD_SEQ)
    else:
        message = message.replace("$RESET", "").replace("$BOLD", "")
    return message

COLORS = {
    'WARNING': YELLOW,
    'INFO': WHITE,
    'DEBUG': BLUE,
    'CRITICAL': YELLOW,
    'ERROR': RED
}

class ColoredFormatter(logging.Formatter):
    def __init__(self, msg, use_color = True):
        logging.Formatter.__init__(self, msg)
        self.use_color = use_color

    def format(self, record):
        levelname = record.levelname
        if self.use_color and levelname in COLORS:
            levelname_color = COLOR_SEQ % (30 + COLORS[levelname]) + levelname + RESET_SEQ
            record.levelname = levelname_color
        return logging.Formatter.format(self, record)

And to use it, create your own Logger:

# Custom logger class with multiple destinations
class ColoredLogger(logging.Logger):
    FORMAT = "[$BOLD%(name)-20s$RESET][%(levelname)-18s]  %(message)s ($BOLD%(filename)s$RESET:%(lineno)d)"
    COLOR_FORMAT = formatter_message(FORMAT, True)
    def __init__(self, name):
        logging.Logger.__init__(self, name, logging.DEBUG)                

        color_formatter = ColoredFormatter(self.COLOR_FORMAT)

        console = logging.StreamHandler()
        console.setFormatter(color_formatter)

        self.addHandler(console)
        return


logging.setLoggerClass(ColoredLogger)

Just in case anyone else needs it.

Be careful if you're using more than one logger or handler: ColoredFormatter is changing the record object, which is passed further to other handlers or propagated to other loggers. If you have configured file loggers etc. you probably don't want to have the colors in the log files. To avoid that, it's probably best to simply create a copy of record with copy.copy() before manipulating the levelname attribute, or to reset the levelname to the previous value, before returning the formatted string (credit to Michael in the comments).

Well, I guess I might as well add my variation of the colored logger.

This is nothing fancy, but it is very simple to use and does not change the record object, thereby avoids logging the ANSI escape sequences to a log file if a file handler is used. It does not effect the log message formatting.

If you are already using the logging module's Formatter, all you have to do to get colored level names is to replace your counsel handlers Formatter with the ColoredFormatter. If you are logging an entire app you only need to do this for the top level logger.

colored_log.py

#!/usr/bin/env python

from copy import copy
from logging import Formatter

MAPPING = {
    'DEBUG'   : 37, # white
    'INFO'    : 36, # cyan
    'WARNING' : 33, # yellow
    'ERROR'   : 31, # red
    'CRITICAL': 41, # white on red bg
}

PREFIX = '\033['
SUFFIX = '\033[0m'

class ColoredFormatter(Formatter):

    def __init__(self, patern):
        Formatter.__init__(self, patern)

    def format(self, record):
        colored_record = copy(record)
        levelname = colored_record.levelname
        seq = MAPPING.get(levelname, 37) # default white
        colored_levelname = ('{0}{1}m{2}{3}') \
            .format(PREFIX, seq, levelname, SUFFIX)
        colored_record.levelname = colored_levelname
        return Formatter.format(self, colored_record)

Example usage

app.py

#!/usr/bin/env python

import logging
from colored_log import ColoredFormatter

# Create top level logger
log = logging.getLogger("main")

# Add console handler using our custom ColoredFormatter
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
cf = ColoredFormatter("[%(name)s][%(levelname)s]  %(message)s (%(filename)s:%(lineno)d)")
ch.setFormatter(cf)
log.addHandler(ch)

# Add file handler
fh = logging.FileHandler('app.log')
fh.setLevel(logging.DEBUG)
ff = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(ff)
log.addHandler(fh)

# Set log level
log.setLevel(logging.DEBUG)

# Log some stuff
log.debug("app has started")
log.info("Logging to 'app.log' in the script dir")
log.warning("This is my last warning, take heed")
log.error("This is an error")
log.critical("He's dead, Jim")

# Import a sub-module 
import sub_module

sub_module.py

#!/usr/bin/env python

import logging
log = logging.getLogger('main.sub_module')

log.debug("Hello from the sub module")

Results

Terminal output

Terminal output

app.log content

2017-09-29 00:32:23,434 - main - DEBUG - app has started
2017-09-29 00:32:23,434 - main - INFO - Logging to 'app.log' in the script dir
2017-09-29 00:32:23,435 - main - WARNING - This is my last warning, take heed
2017-09-29 00:32:23,435 - main - ERROR - This is an error
2017-09-29 00:32:23,435 - main - CRITICAL - He's dead, Jim
2017-09-29 00:32:23,435 - main.sub_module - DEBUG - Hello from the sub module

Of course you can get as fancy as you want with formatting the terminal and log file outputs. Only the log level will be colorized.

I hope somebody finds this useful and it is not just too much more of the same. :)

The Python example files can be downloaded from this GitHub Gist: https://gist.github.com/KurtJacobson/48e750701acec40c7161b5a2f79e6bfd

I modified the original example provided by Sorin and subclassed StreamHandler to a ColorizedConsoleHandler.

The downside of their solution is that it modifies the message, and because that is modifying the actual logmessage any other handlers will get the modified message as well.

This resulted in logfiles with colorcodes in them in our case because we use multiple loggers.

The class below only works on platforms that support ANSI, but it should be trivial to add the Windows colorcodes to it.

import copy
import logging


class ColoredConsoleHandler(logging.StreamHandler):
    def emit(self, record):
        # Need to make a actual copy of the record
        # to prevent altering the message for other loggers
        myrecord = copy.copy(record)
        levelno = myrecord.levelno
        if(levelno >= 50):  # CRITICAL / FATAL
            color = '\x1b[31m'  # red
        elif(levelno >= 40):  # ERROR
            color = '\x1b[31m'  # red
        elif(levelno >= 30):  # WARNING
            color = '\x1b[33m'  # yellow
        elif(levelno >= 20):  # INFO
            color = '\x1b[32m'  # green
        elif(levelno >= 10):  # DEBUG
            color = '\x1b[35m'  # pink
        else:  # NOTSET and anything else
            color = '\x1b[0m'  # normal
        myrecord.msg = color + str(myrecord.msg) + '\x1b[0m'  # normal
        logging.StreamHandler.emit(self, myrecord)

What about highlighting also log message arguments with alternating colors, in addition to coloring by level? I recently wrote simple code for that. Another advantage is that log call is made with Python 3 brace-style formatting. ("{}").

See latest code and examples here: https://github.com/davidohana/colargulog

Sample Logging code:

root_logger = logging.getLogger()
console_handler = logging.StreamHandler(stream=sys.stdout)
console_format = "%(asctime)s - %(levelname)-8s - %(name)-25s - %(message)s"
colored_formatter = ColorizedArgsFormatter(console_format)
console_handler.setFormatter(colored_formatter)
root_logger.addHandler(console_handler)

logger = logging.getLogger(__name__)
logger.info("Hello World")
logger.info("Request from {} handled in {:.3f} ms", socket.gethostname(), 11)
logger.info("Request from {} handled in {:.3f} ms", "127.0.0.1", 33.1)
logger.info("My favorite drinks are {}, {}, {}, {}", "milk", "wine", "tea", "beer")
logger.debug("this is a {} message", logging.getLevelName(logging.DEBUG))
logger.info("this is a {} message", logging.getLevelName(logging.INFO))
logger.warning("this is a {} message", logging.getLevelName(logging.WARNING))
logger.error("this is a {} message", logging.getLevelName(logging.ERROR))
logger.critical("this is a {} message", logging.getLevelName(logging.CRITICAL))
logger.info("Does old-style formatting also work? %s it is, but no colors (yet)", True)

Output:

enter image description here

Implementation:

"""
colargulog - Python3 Logging with Colored Arguments and new string formatting style

Written by david.ohana@ibm.com
License: Apache-2.0
"""

import logging
import logging.handlers
import re


class ColorCodes:
    grey = "\x1b[38;21m"
    green = "\x1b[1;32m"
    yellow = "\x1b[33;21m"
    red = "\x1b[31;21m"
    bold_red = "\x1b[31;1m"
    blue = "\x1b[1;34m"
    light_blue = "\x1b[1;36m"
    purple = "\x1b[1;35m"
    reset = "\x1b[0m"


class ColorizedArgsFormatter(logging.Formatter):
    arg_colors = [ColorCodes.purple, ColorCodes.light_blue]
    level_fields = ["levelname", "levelno"]
    level_to_color = {
        logging.DEBUG: ColorCodes.grey,
        logging.INFO: ColorCodes.green,
        logging.WARNING: ColorCodes.yellow,
        logging.ERROR: ColorCodes.red,
        logging.CRITICAL: ColorCodes.bold_red,
    }

    def __init__(self, fmt: str):
        super().__init__()
        self.level_to_formatter = {}

        def add_color_format(level: int):
            color = ColorizedArgsFormatter.level_to_color[level]
            _format = fmt
            for fld in ColorizedArgsFormatter.level_fields:
                search = "(%\(" + fld + "\).*?s)"
                _format = re.sub(search, f"{color}\\1{ColorCodes.reset}", _format)
            formatter = logging.Formatter(_format)
            self.level_to_formatter[level] = formatter

        add_color_format(logging.DEBUG)
        add_color_format(logging.INFO)
        add_color_format(logging.WARNING)
        add_color_format(logging.ERROR)
        add_color_format(logging.CRITICAL)

    @staticmethod
    def rewrite_record(record: logging.LogRecord):
        if not BraceFormatStyleFormatter.is_brace_format_style(record):
            return

        msg = record.msg
        msg = msg.replace("{", "_{{")
        msg = msg.replace("}", "_}}")
        placeholder_count = 0
        # add ANSI escape code for next alternating color before each formatting parameter
        # and reset color after it.
        while True:
            if "_{{" not in msg:
                break
            color_index = placeholder_count % len(ColorizedArgsFormatter.arg_colors)
            color = ColorizedArgsFormatter.arg_colors[color_index]
            msg = msg.replace("_{{", color + "{", 1)
            msg = msg.replace("_}}", "}" + ColorCodes.reset, 1)
            placeholder_count += 1

        record.msg = msg.format(*record.args)
        record.args = []

    def format(self, record):
        orig_msg = record.msg
        orig_args = record.args
        formatter = self.level_to_formatter.get(record.levelno)
        self.rewrite_record(record)
        formatted = formatter.format(record)

        # restore log record to original state for other handlers
        record.msg = orig_msg
        record.args = orig_args
        return formatted


class BraceFormatStyleFormatter(logging.Formatter):
    def __init__(self, fmt: str):
        super().__init__()
        self.formatter = logging.Formatter(fmt)

    @staticmethod
    def is_brace_format_style(record: logging.LogRecord):
        if len(record.args) == 0:
            return False

        msg = record.msg
        if '%' in msg:
            return False

        count_of_start_param = msg.count("{")
        count_of_end_param = msg.count("}")

        if count_of_start_param != count_of_end_param:
            return False

        if count_of_start_param != len(record.args):
            return False

        return True

    @staticmethod
    def rewrite_record(record: logging.LogRecord):
        if not BraceFormatStyleFormatter.is_brace_format_style(record):
            return

        record.msg = record.msg.format(*record.args)
        record.args = []

    def format(self, record):
        orig_msg = record.msg
        orig_args = record.args
        self.rewrite_record(record)
        formatted = self.formatter.format(record)

        # restore log record to original state for other handlers
        record.msg = orig_msg
        record.args = orig_args
        return formatted

Use the rich library

Rich supplies a logging handler which will format and colorize text written by Python's logging module.

It is easy to use and customizable + works in cmd.exe, Windows Terminal, ConEmu and Jupyter Notebook! (I tried many packages I tell ya, only rich's color works in the notebook.).

Rich also comes with many other fancy features.

Installation

pip install rich

Minimal example:

import logging
from rich.logging import RichHandler

FORMAT = "%(message)s"
logging.basicConfig(
    level="NOTSET", format=FORMAT, datefmt="[%X]", handlers=[RichHandler()]
)  # set level=20 or logging.INFO to turn of debug
logger = logging.getLogger("rich")

logger.debug("debug...")
logger.info("info...")
logger.warning("warning...")
logger.error("error...")
logger.fatal("fatal...")

terminal screenshot

Install the colorlog package, you can use colors in your log messages immediately:

  • Obtain a logger instance, exactly as you would normally do.
  • Set the logging level. You can also use the constants like DEBUG and INFO from the logging module directly.
  • Set the message formatter to be the ColoredFormatter provided by the colorlog library.
import colorlog

logger = colorlog.getLogger()
logger.setLevel(colorlog.colorlog.logging.DEBUG)

handler = colorlog.StreamHandler()
handler.setFormatter(colorlog.ColoredFormatter())
logger.addHandler(handler)

logger.debug("Debug message")
logger.info("Information message")
logger.warning("Warning message")
logger.error("Error message")
logger.critical("Critical message")

output: enter image description here


UPDATE: extra info

Just update ColoredFormatter:

handler.setFormatter(colorlog.ColoredFormatter('%(log_color)s [%(asctime)s] %(levelname)s [%(filename)s.%(funcName)s:%(lineno)d] %(message)s', datefmt='%a, %d %b %Y %H:%M:%S'))

output: enter image description here


Package:

pip install colorlog

output:

Collecting colorlog
  Downloading colorlog-4.6.2-py2.py3-none-any.whl (10.0 kB)
Installing collected packages: colorlog
Successfully installed colorlog-4.6.2

There are tons of responses. But none is talking about decorators. So here's mine.

Because it is a lot more simple.

There's no need to import anything, nor to write any subclass:

#!/usr/bin/env python
# -*- coding: utf-8 -*-


import logging


NO_COLOR = "\33[m"
RED, GREEN, ORANGE, BLUE, PURPLE, LBLUE, GREY = \
    map("\33[%dm".__mod__, range(31, 38))

logging.basicConfig(format="%(message)s", level=logging.DEBUG)
logger = logging.getLogger(__name__)

# the decorator to apply on the logger methods info, warn, ...
def add_color(logger_method, color):
  def wrapper(message, *args, **kwargs):
    return logger_method(
      # the coloring is applied here.
      color+message+NO_COLOR,
      *args, **kwargs
    )
  return wrapper

for level, color in zip((
  "info", "warn", "error", "debug"), (
  GREEN, ORANGE, RED, BLUE
)):
  setattr(logger, level, add_color(getattr(logger, level), color))

# this is displayed in red.
logger.error("Launching %s." % __file__)

This set the errors in red, debug messages in blue, and so on. Like asked in the question.

We could even adapt the wrapper to take a color argument to dynamicaly set the message's color using logger.debug("message", color=GREY)

EDIT: So here's the adapted decorator to set colors at runtime:

def add_color(logger_method, _color):
  def wrapper(message, *args, **kwargs):
    color = kwargs.pop("color", _color)
    if isinstance(color, int):
      color = "\33[%dm" % color
    return logger_method(
      # the coloring is applied here.
      color+message+NO_COLOR,
      *args, **kwargs
    )
  return wrapper

# blah blah, apply the decorator...

# this is displayed in red.
logger.error("Launching %s." % __file__)
# this is displayed in blue
logger.error("Launching %s." % __file__, color=34)
# and this, in grey
logger.error("Launching %s." % __file__, color=GREY)

coloredlogs

Instalation

pip install coloredlogs

Usage

Minimal usage:
import logging
import coloredlogs

coloredlogs.install()  # install a handler on the root logger

logging.debug('message with level debug')
logging.info('message with level info')
logging.warning('message with level warning')
logging.error('message with level error')
logging.critical('message with level critical')

Results with: minimal usage

Start from message level debug:
import logging
import coloredlogs

coloredlogs.install(level='DEBUG')  # install a handler on the root logger with level debug

logging.debug('message with level debug')
logging.info('message with level info')
logging.warning('message with level warning')
logging.error('message with level error')
logging.critical('message with level critical')

Results with: debug level

Hide messages from libraries:
import logging
import coloredlogs

logger = logging.getLogger(__name__)  # get a specific logger object
coloredlogs.install(level='DEBUG')  # install a handler on the root logger with level debug
coloredlogs.install(level='DEBUG', logger=logger)  # pass a specific logger object

logging.debug('message with level debug')
logging.info('message with level info')
logging.warning('message with level warning')
logging.error('message with level error')
logging.critical('message with level critical')

Results with: debug level

Format log messages:
import logging
import coloredlogs

logger = logging.getLogger(__name__)  # get a specific logger object
coloredlogs.install(level='DEBUG')  # install a handler on the root logger with level debug
coloredlogs.install(level='DEBUG', logger=logger)  # pass a specific logger object
coloredlogs.install(
    level='DEBUG', logger=logger,
    fmt='%(asctime)s.%(msecs)03d %(filename)s:%(lineno)d %(levelname)s %(message)s'
)

logging.debug('message with level debug')
logging.info('message with level info')
logging.warning('message with level warning')
logging.error('message with level error')
logging.critical('message with level critical')

Results with: format log messages

Available format attributes:
  • %(asctime)s - Time as human-readable string, when logging call was issued
  • %(created)f - Time as float when logging call was issued
  • %(filename)s - File name
  • %(funcName)s - Name of function containing the logging call
  • %(hostname)s - System hostname
  • %(levelname)s - Text logging level
  • %(levelno)s - Integer logging level
  • %(lineno)d - Line number where the logging call was issued
  • %(message)s - Message passed to logging call (same as %(msg)s)
  • %(module)s - File name without extension where the logging call was issued
  • %(msecs)d - Millisecond part of the time when logging call was issued
  • %(msg)s - Message passed to logging call (same as %(message)s)
  • %(name)s - Logger name
  • %(pathname)s - Full pathname to file containing the logging call
  • %(process)d - Process ID
  • %(processName)s - Process name
  • %(programname)s - System programname
  • %(relativeCreated)d - Time as integer in milliseconds when logging call was issued, relative to the time when logging module was loaded
  • %(thread)d - Thread ID
  • %(threadName)s - Thread name
  • %(username)s - System username

Sources:

Coloredlogs package

Logging library

Solution using standard Python3 logging library

I am pretty excited to share this flexible solution for log coloring. I think it is an improvement to this solution by @SergeyPleshakov. I leveraged the log record's extra kwargs to set a log prefix and suffix. Then we just default the prefix and suffix to start and end with terminal color codes that correspond with the log level.

bonus feature ✨ ✨

The extra prefix and suffix can be overridden by the log call to be whatever. You want your debug log to be prefixed with a , why not. You want a one of the info logs to be Green instead of the default, go for it!

Define the terminal Color and ColorLogFormatter classes

import logging


class Color:
    """A class for terminal color codes."""

    BOLD = "\033[1m"
    BLUE = "\033[94m"
    WHITE = "\033[97m"
    GREEN = "\033[92m"
    YELLOW = "\033[93m"
    RED = "\033[91m"
    BOLD_WHITE = BOLD + WHITE
    BOLD_BLUE = BOLD + BLUE
    BOLD_GREEN = BOLD + GREEN
    BOLD_YELLOW = BOLD + YELLOW
    BOLD_RED = BOLD + RED
    END = "\033[0m"


class ColorLogFormatter(logging.Formatter):
    """A class for formatting colored logs."""

    FORMAT = "%(prefix)s%(msg)s%(suffix)s"

    LOG_LEVEL_COLOR = {
        "DEBUG": {'prefix': '', 'suffix': ''},
        "INFO": {'prefix': '', 'suffix': ''},
        "WARNING": {'prefix': Color.BOLD_YELLOW, 'suffix': Color.END},
        "ERROR": {'prefix': Color.BOLD_RED, 'suffix': Color.END},
        "CRITICAL": {'prefix': Color.BOLD_RED, 'suffix': Color.END},
    }

    def format(self, record):
        """Format log records with a default prefix and suffix to terminal color codes that corresponds to the log level name."""
        if not hasattr(record, 'prefix'):
            record.prefix = self.LOG_LEVEL_COLOR.get(record.levelname.upper()).get('prefix')
        
        if not hasattr(record, 'suffix'):
            record.suffix = self.LOG_LEVEL_COLOR.get(record.levelname.upper()).get('suffix')

        formatter = logging.Formatter(self.FORMAT)
        return formatter.format(record)

Instantiate logger

logger = logging.getLogger('bobcat')
logger.setLevel('DEBUG')

stream_handler = logging.StreamHandler()
stream_handler.setFormatter(ColorLogFormatter())
logger.addHandler(stream_handler)

And use!

    logger.debug("This is debug", extra={'prefix': ' '})
    logger.info("This is info")
    logger.info("This is a green info", extra={'prefix': Color.GREEN, 'suffix': Color.END})
    logger.warning("This is warning")
    logger.error("This is error")
    logger.critical("This is critical")

and Voilà!

screenshot

FriendlyLog is another alternative. It works with Python 2 & 3 under Linux, Windows and MacOS.

The following solution works with python 3 only, but for me it looks most clear.

The idea is to use log record factory to add 'colored' attributes to log record objects and than use these 'colored' attributes in log format.

import logging
logger = logging.getLogger(__name__)

def configure_logging(level):

    # add 'levelname_c' attribute to log resords
    orig_record_factory = logging.getLogRecordFactory()
    log_colors = {
        logging.DEBUG:     "\033[1;34m",  # blue
        logging.INFO:      "\033[1;32m",  # green
        logging.WARNING:   "\033[1;35m",  # magenta
        logging.ERROR:     "\033[1;31m",  # red
        logging.CRITICAL:  "\033[1;41m",  # red reverted
    }
    def record_factory(*args, **kwargs):
        record = orig_record_factory(*args, **kwargs)
        record.levelname_c = "{}{}{}".format(
            log_colors[record.levelno], record.levelname, "\033[0m")
        return record

    logging.setLogRecordFactory(record_factory)

    # now each log record object would contain 'levelname_c' attribute
    # and you can use this attribute when configuring logging using your favorite
    # method.
    # for demo purposes I configure stderr log right here

    formatter_c = logging.Formatter("[%(asctime)s] %(levelname_c)s:%(name)s:%(message)s")

    stderr_handler = logging.StreamHandler()
    stderr_handler.setLevel(level)
    stderr_handler.setFormatter(formatter_c)

    root_logger = logging.getLogger('')
    root_logger.setLevel(logging.DEBUG)
    root_logger.addHandler(stderr_handler)


def main():
    configure_logging(logging.DEBUG)

    logger.debug("debug message")
    logger.info("info message")
    logger.critical("something unusual happened")


if __name__ == '__main__':
    main()

You can easily modify this example to create other colored attributes (f.e. message_c) and then use these attributes to get colored text (only) where you want.

(handy trick I discovered recently: I have a file with colored debug logs and whenever I want temporary increase the log level of my application I just tail -f the log file in different terminal and see debug logs on screen w/o changing any configuration and restarting application)

This is another Python3 variant of airmind's example. I wanted some specific features I didn't see in the other examples

  • use colors for the terminal but do not write non-printable characters in the file handlers (I defined 2 formatters for this)
  • ability to override the color for a specific log message
  • configure the logger from a file (yaml in this case)

Notes: I used colorama but you could modify this so it is not required. Also for my testing I was just running python file so my class is in module __main__ You would have to change (): __main__.ColoredFormatter to whatever your module is.

pip install colorama pyyaml

logging.yaml

---
version: 1
disable_existing_loggers: False
formatters:
  simple:
    format: "%(threadName)s - %(name)s - %(levelname)s - %(message)s"
  color:
    format: "%(threadName)s - %(name)s - %(levelname)s - %(message)s"
    (): __main__.ColoredFormatter
    use_color: true

handlers:
  console:
    class: logging.StreamHandler
    level: DEBUG
    formatter: color
    stream: ext://sys.stdout

  info_file_handler:
    class: logging.handlers.RotatingFileHandler
    level: INFO
    formatter: simple
    filename: app.log
    maxBytes: 20971520 
    backupCount: 20
    encoding: utf8

  error_file_handler:
    class: logging.handlers.RotatingFileHandler
    level: ERROR
    formatter: simple
    filename: errors.log
    maxBytes: 10485760 
    backupCount: 20
    encoding: utf8

root:
  level: DEBUG
  handlers: [console, info_file_handler, error_file_handler]

main.py

import logging
import logging.config
import os
from logging import Logger

import colorama
import yaml
from colorama import Back, Fore, Style

COLORS = {
    "WARNING": Fore.YELLOW,
    "INFO": Fore.CYAN,
    "DEBUG": Fore.BLUE,
    "CRITICAL": Fore.YELLOW,
    "ERROR": Fore.RED,
}


class ColoredFormatter(logging.Formatter):
    def __init__(self, *, format, use_color):
        logging.Formatter.__init__(self, fmt=format)
        self.use_color = use_color

    def format(self, record):
        msg = super().format(record)
        if self.use_color:
            levelname = record.levelname
            if hasattr(record, "color"):
                return f"{record.color}{msg}{Style.RESET_ALL}"
            if levelname in COLORS:
                return f"{COLORS[levelname]}{msg}{Style.RESET_ALL}"
        return msg


with open("logging.yaml", "rt") as f:
    config = yaml.safe_load(f.read())
    logging.config.dictConfig(config)

logger: Logger = logging.getLogger(__name__)
logger.info("Test INFO", extra={"color": Back.RED})
logger.info("Test INFO", extra={"color": f"{Style.BRIGHT}{Back.RED}"})
logger.info("Test INFO")
logger.debug("Test DEBUG")
logger.warning("Test WARN")

output:

output

Emoji

You can use colors for text as others mentioned in their answers to have colorful text with a background or foreground color.

But you can use emojis instead! for example, you can use ⚠️ for warning messages and for error messages.

Or simply use these notebooks as a color:

print(": error message")
print(": warning message")
print(": ok status message")
print(": action message")
print(": canceled status message")
print(": Or anything you like and want to recognize immediately by color")

Bonus:

This method also helps you to quickly scan and find logs directly in the source code.


How to open emoji picker?

mac os: control + command + space

windows: win + .

linux: control + . or control + ;

The bit I had trouble with was setting up the formatter properly:

class ColouredFormatter(logging.Formatter):    
    def __init__(self, msg):
        logging.Formatter.__init__(self, msg)
        self._init_colour = _get_colour()

    def close(self):
        # restore the colour information to what it was
        _set_colour(self._init_colour)

    def format(self, record):        
        # Add your own colourer based on the other examples
        _set_colour( LOG_LEVEL_COLOUR[record.levelno] )
        return logging.Formatter.format(self, record)         

def init():
    # Set up the formatter. Needs to be first thing done.
    rootLogger = logging.getLogger()
    hdlr = logging.StreamHandler()
    fmt = ColouredFormatter('%(message)s')
    hdlr.setFormatter(fmt)
    rootLogger.addHandler(hdlr)

And then to use:

import coloured_log
import logging

coloured_log.init()
logging.info("info")    
logging.debug("debug")    

coloured_log.close()    # restore colours

A handy bash script with tput colors

# Simple using tput
bold=$(tput bold)
reset=$(tput sgr0)

fblack=$(tput setaf 0)
fred=$(tput setaf 1)
fgreen=$(tput setaf 2)
fyellow=$(tput setaf 3)
fblue=$(tput setaf 4)
fmagenta=$(tput setaf 5)
fcyan=$(tput setaf 6)
fwhite=$(tput setaf 7)
fnotused=$(tput setaf 8)
freset=$(tput setaf 9)

bblack=$(tput setab 0)
bred=$(tput setab 1)
bgreen=$(tput setab 2)
byellow=$(tput setab 3)
bblue=$(tput setab 4)
bmagenta=$(tput setab 5)
bcyan=$(tput setab 6)
bwhite=$(tput setab 7)
bnotused=$(tput setab 8)
breset=$(tput setab 9)

# 0 - Emergency (emerg)       $fred       # something is wrong... go red
# 1 - Alerts (alert)          $fred       # something is wrong... go red
# 2 - Critical (crit)         $fred       # something is wrong... go red
# 3 - Errors (err)            $fred       # something is wrong... go red
# 4 - Warnings (warn)         $fyellow    # yellow yellow dirty logs
# 5 - Notification (notice)   $fwhite     # common stuff
# 6 - Information (info)      $fblue      # sky is blue
# 7 - Debug (debug)           $fgreen     # lot of stuff to read... go green 

This is a slight variation on @Sergey Pleshakov's excellent answer which applies color only to the levels and uses basicConfig as expected:

class CustomFormatter(logging.Formatter):

    white = "\x1b[97;20m"
    grey = "\x1b[38;20m"
    green = "\x1b[32;20m"
    cyan = "\x1b[36;20m"
    yellow = "\x1b[33;20m"
    red = "\x1b[31;20m"
    bold_red = "\x1b[31;1m"
    reset = "\x1b[0m"
    fmt = "%(asctime)s - {}%(levelname)-8s{} - %(name)s.%(funcName)s - %(message)s"

    FORMATS = {
        logging.DEBUG: fmt.format(grey, reset),
        logging.INFO: fmt.format(green, reset),
        logging.WARNING: fmt.format(yellow, reset),
        logging.ERROR: fmt.format(red, reset),
        logging.CRITICAL: fmt.format(bold_red, reset),
    }

    def format(self, record):
        log_fmt = self.FORMATS.get(record.levelno)
        formatter = logging.Formatter(log_fmt, datefmt="%H:%M:%S")
        return formatter.format(record)


handler = logging.StreamHandler()
handler.setFormatter(CustomFormatter())
logging.basicConfig(
    level=logging.DEBUG,
    handlers=[handler]
)

to anyone who feels the same need, i recommend my own package TCPrint. it's based on Colorama (which provides cross-platform compatibility), but unlike it uses familiar <tags> to mark up text colors and includes tags for logging

installation:

pip install ctprint

Coloring:

from ctprint inport ctp, ctdecode, cterr, ctlog

# print colored text
ctp('<bw> black text on white background /> default formating')

Error handling:

# print error message
try:
    1/0  # any broken line
except Exception as _ex:
    cterr(_ex)

Variables logging:

var0 = var1 = 0

# print varName-varValue pairs
def example_ctlog():

    var2 = 'string val'
    var3 = {'ctp_string': '<bg_red><red>red text on red background (NO) >'}

    # out of the function, var0=var2 - nothing problems.
    ctlog(var0=var0, var1=var1, var2=var2, var3=var3)

And more:

ctp.help() # print help dialog with all supported tags and functions

in addition to logging functions and color tags, there are <error> and <log> quick tags designed for marking up user outputs

if u need to speed up logging and improve readability in the terminal (vscode/pycharm/cmd/bash etc), and not customize the command-line interface in the style of , or , u know what to do. CTPrint was created for the first

luck!

If only you don't want to invent the wheel.

Just pip install loguru and then:

from loguru import logger

if __name__ == '__main__':
    message = "Message text"

    logger.info(message)
    logger.debug(message)
    logger.warning(message)
    logger.success(message)
    logger.error(message)
    logger.critical(message)

output: enter image description here

You can change format, colors, write to file from the box ... here is documentation

The easiest solution is probably Colorama.

Install Colorama onto your computer:

pip install colorama

Then add it to your Python program:

import colorama
print(Fore.GREEN + "test123")

If you need to multi-color things, use YAChalk.

Install YAChalk onto your computer:

pip install yachalk

Add it to your Python program:

from yachalk import chalk
print(chalk.blue("This is blue and {chalk.red("this is red")})

Simplified, logging lib only:

class handler(logging.StreamHandler):
    colors = {
        logging.DEBUG: '\033[37m',
        logging.INFO: '\033[36m',
        logging.WARNING: '\033[33m',
        logging.ERROR: '\033[31m',
        logging.CRITICAL: '\033[101m',
    }
    reset = '\033[0m'
    fmtr = logging.Formatter('%(levelname)s %(message)s')

    def format(self, record):
        color = self.colors[record.levelno]
        log = self.fmtr.format(record)
        reset = self.reset
        return color + log + reset


logging.basicConfig(level=logging.DEBUG, handlers=[handler()])

Use the tornado library

The Tornado web framework provides some utilities, including the tornado.log.LogFormatter formatter which can be used without the rest of the framework.

Color support on Windows versions that do not support ANSI color codes is enabled by use of the colorama library. Applications that wish to use this must first initialize colorama with a call to colorama.init.

import logging
import tornado.log
# import colorama  # uncomment on some Windows versions
# colorama.init()

consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(tornado.log.LogFormatter())
logging.basicConfig(level=logging.DEBUG, handlers=[consoleHandler])
logger = logging.getLogger("test")
logger.info("hello world")

I prefer use this snippet:

import logging
from enum import Enum

CSI = '\033['

Color = Enum(
    'Color', 'BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE', start=30
)


class AnsiColorHandler(logging.StreamHandler):
    LOGLEVEL_COLORS = {
        'DEBUG': Color.BLUE,
        'INFO': Color.GREEN,
        'WARNING': Color.RED,
        'ERROR': Color.RED,
        'CRITICAL': Color.RED,
    }

    def __init__(self) -> None:
        super().__init__()
        self.formatter = logging.Formatter("%(levelname)-8s - %(message)s")

    def format(self, record: logging.LogRecord) -> str:
        message: str = super().format(record)
        # use colors in tty
        if self.stream.isatty() and (
            color := self.LOGLEVEL_COLORS.get(record.levelname)
        ):
            message = f'{CSI}{color.value}m{message}{CSI}0m'
        return message


# setup logger
# logger = logging.getLogger(__package__)
logger = logging.getLogger(__name__)
logger.addHandler(AnsiColorHandler())

Usage:

import logging

from .log import logger

logger.setLevel(logging.DEBUG)
logger.debug("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
Related