508

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?

vvvvv
  • 25,404
  • 19
  • 49
  • 81
airmind
  • 7,361
  • 4
  • 18
  • 6
  • 1
    You should specify that you want a multiplatform solution - both Linux and Windows. – sorin Aug 25 '09 at 18:55
  • 1
    Related if you use Eclipse/PyDev: [Colorize logs in eclipse console](http://stackoverflow.com/q/233790/321973) – Tobias Kienzler Nov 16 '12 at 09:18
  • 9
    Perhaps you can also use [colorlog](https://pypi.python.org/pypi/colorlog/2.0.0) – Ehtesh Choudhury Feb 14 '14 at 21:19
  • run `pip install ipython` and add `alias python="ipython"` to your shell startup script (e.g. `~/.bashrc` for bash shell) – Alexandre Holden Daly Jul 14 '14 at 15:25
  • 8
    You may also try [chromalog](http://chromalog.readthedocs.org/en/latest/) which I wrote to support all operating systems and Python versions (2.7 and 3.*) – ereOn May 14 '15 at 22:08
  • 2
    Solutions which actually dump ANSI codes in the logfile are a bad idea, they will catch you out when you are grepping for something in six months time but forget to allow for the ANSI chars in your regex pattern. There are some solutions below which add the color as you view the log, rather than as the log is written... – Jonathan Hartley May 07 '16 at 13:10
  • FriendlyLog (https://github.com/SebiSebi/friendlylog) is another alternative. It works with Python 2 & 3 under Linux, Windows and MacOS. – SebiSebi Oct 06 '19 at 16:44
  • 1
    @JonathanHartley This is the reason you setup multiple logging handlers. You can setup a logging StreamHandler to send logging output to streams such as sys.stdout and/or sys.stderr The handler(s) emitting to stdout/stderr can be colorised, this output exists only in a terminal window. You then setup another logging handler which sends logging output to a logfile (and does not colorise messages). – Matt Conway Mar 10 '20 at 14:23
  • you don't even need packages to accomplish that, check out https://stackoverflow.com/a/56944256/9150146 – Sergey Pleshakov May 21 '20 at 18:34
  • 1
    if you want to enable color only when stdout is a terminal: https://stackoverflow.com/questions/1077113/how-do-i-detect-whether-sys-stdout-is-attached-to-terminal-or-not – Florian Castellane Mar 20 '21 at 07:20

44 Answers44

313

A Python 3 solution, with no additional packages required

Note to the community: please do not edit the answer. I know its not the most optimal way in term of coding, but the easiest to understand and most readable way to get the essence of the process

1. 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)

2. 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)

3. 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/

Sergey Pleshakov
  • 7,964
  • 2
  • 17
  • 40
  • 4
    I run the test (python 3.7, windows), but logging doesn't show colors: `←[38;21m2019-11-12 19:29:50,994 - My_app - DEBUG - debug message (test_colored_log.py:43)←[0m ←[38;21m2019-11-12 19:29:50,994 - My_app - INFO - info message (test_colored_log.py:44)←[0m ←[33;21m2019-11-12 19:29:50,994 - My_app - WARNING - warning message (test_colored_log.py:45)←[0m ←[31;21m2019-11-12 19:29:50,994 - My_app - ERROR - error message (test_colored_log.py:46)←[0m ←[31;1m2019-11-12 19:29:50,994 - My_app - CRITICAL - critical message (test_colored_log.py:47)←[0m` – constructor Nov 12 '19 at 17:30
  • 14
    I liked this answer so much that I made a [repo](https://github.com/t177398/best_python_logger) for it, with a few increments and a cheat sheet of ansi colors. – Teodoro Apr 30 '20 at 21:34
  • @constructor where do you run it? IDE console? windows terminal? – Sergey Pleshakov Jun 03 '20 at 13:44
  • @Joe what exactly doesn't work? what's your environment and what errors do you get? I'd like to revise the solution to make it work across platforms – Sergey Pleshakov Jun 03 '20 at 13:45
  • @SergeyPleshakov I tested again in terminal Visual Studio Code and now it works, but doesn't work in Windows command line: ```[38;21m2020-06-03 21:12:46,363 [INFO ] [algo] info[0m [33;21m2020-06-03 21:12:46,363 [WARNING] [algo] warning [algo.py:18][0m [31;21m2020-06-03 21:12:46,363 [ERROR ] [algo] error[0m [38;21m2020-06-03 21:12:46,364 [INFO ] [algo] result: None[0m ``` – constructor Jun 03 '20 at 18:13
  • @constructor glad it worked for you. I will try to revise the answer to support windows OS – Sergey Pleshakov Jun 03 '20 at 21:45
  • 1
    Hi, I ran this on Ubuntu and works. The only thing is that this add double underline in my terminal, any thoughts on this? – Darren Christopher Dec 03 '20 at 03:25
  • 7
    Oops, just played around with it and found the solution, just change the `...21m` to `20m` seems to work perfect in mine. Just in case anyone having same issue. – Darren Christopher Dec 03 '20 at 03:27
  • @DarrenChristopher is this solution you mentioned for windows? – Sergey Pleshakov Dec 03 '20 at 13:18
  • 1
    @SergeyPleshakov Tested on Ubuntu. Let me know if that doesn't work on Windows. – Darren Christopher Dec 04 '20 at 01:32
  • @SergeyPleshakov It's fun to see that you re-used some of my code (the "and count warning/errors" gave you out) :) Glad it could help the community! (and it was designed for Windows... but I was using `colorama.Fore` to get the color codes. – Jean-Francois T. Feb 25 '21 at 11:22
  • 1
    It works on Linux, but on Windows I suggest colorama other than \x1b[??;21m. – chariothy Dec 03 '21 at 01:23
  • Note that it is a bit inefficient to reconstruct the formatter on every format (because it performs validation of the (static) format string on every log message). It would be better to simply store the formatter instances themselves in the dict. – bluenote10 Mar 19 '22 at 18:55
  • 1
    DOESN'T WORK ON WINDOWS PLEASE MAKE THIS BOLD AND TOP – user7660047 Apr 17 '22 at 08:29
  • How to change color of logging.exception it doesn't work to change the color. – Kartikeyan Gupta Dec 01 '22 at 11:47
  • 1
    See here all colors codes: https://talyian.github.io/ansicolors/ – A.Casanova Apr 10 '23 at 20:11
224

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).

Guillaume Algis
  • 10,705
  • 6
  • 44
  • 72
airmind
  • 7,361
  • 4
  • 18
  • 6
  • Where is YELLOW, WHITE, BLUE, etc. defined? – Swaroop C H May 22 '09 at 16:20
  • 2
    @Swaroop - Those are ANSI escape codes, which you can read look up on Google, or find here: http://en.wikipedia.org/wiki/ANSI_escape_code, or alternatively http://pueblo.sourceforge.net/doc/manual/ansi_color_codes.html – Brian M. Hunt Aug 16 '09 at 20:56
  • 64
    I don't believe that you should create a logger subclass just for this - your answer is fine as far as creating a specialised `Formatter` and specifying its use on a `StreamHandler`. But there's no need for a logger subclass. In fact the use of a logger class adds a handler to every logger created, which is not what you typically want. – Vinay Sajip Aug 17 '09 at 12:17
  • @Vinay -- could you provide more details on how to implement this? – simon Mar 21 '12 at 00:28
  • 3
    @simon: http://plumberjack.blogspot.co.uk/2010/12/colorizing-logging-output-in-terminals.html – Vinay Sajip Mar 22 '12 at 23:08
  • 9
    One side note to `ColoredFormatter`. It's 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. – Michael Apr 20 '13 at 13:31
  • @airmind I would make 2 improvements. Instead of using `str.replace` you can use `{BOLD_SEQ}`, `{RESET_SEQ}` in `fmt` and then utilize `str.format(**SEQS)`. Also defining a logger is an overkill, you can have this formatter as it is and define a class of `logging.Handler` or `logging.StreamHandler` and implement `def emit(self, record)`. Also python's `logging` caches loggers, so if at some point you invoke `logging.getLogger(name).addHandler(hdlr)` with the same name you will have 2 Handlers so you either need to `logger.removeHandler(hdlr)` or cache logger instances and return them. – tchar Jul 26 '21 at 09:31
202

Years ago I wrote a colored stream handler for my own use. Then I came across this page and found a collection of code snippets that people are copy/pasting :-(. My stream handler currently only works on UNIX (Linux, Mac OS X) but the advantage is that it's available on PyPI (and GitHub) and it's dead simple to use. It also has a Vim syntax mode :-). In the future I might extend it to work on Windows.

To install the package:

$ pip install coloredlogs

To confirm that it works:

$ coloredlogs --demo

To get started with your own code:

$ python
> import coloredlogs, logging
> coloredlogs.install()
> logging.info("It works!")
2014-07-30 21:21:26 peter-macbook root[7471] INFO It works!

The default log format shown in the above example contains the date, time, hostname, the name of the logger, the PID, the log level and the log message. This is what it looks like in practice:

Screenshot of coloredlogs output

NOTE: When using Git Bash w/ MinTTY

Git Bash on windows has some documented quirks: Winpty and Git Bash

Which for ANSI escape codes and for ncurses style character rewriting and animations, you need to prefix commands with winpty.

$ winpty coloredlogs --demo
$ winpty python your_colored_logs_script.py
Josh Peak
  • 5,898
  • 4
  • 40
  • 52
xolox
  • 4,888
  • 3
  • 24
  • 15
  • 3
    funny enough, i was just going to add a link to "https://pypi.python.org/pypi/coloredlogs/0.4.7" in this thread! – Iosu S. Mar 04 '14 at 15:29
  • 1
    For some reason I keep getting [`AttributeError: 'module' object has no attribute 'install'`](https://github.com/xolox/python-coloredlogs/issues/10) when using `coloredlogs.install()`. Can you confirm that with the latest version. – con-f-use Dec 01 '15 at 16:23
  • 25
    This does look beautiful. Unfortunately, it breaks many things; in particular, it voids calls to logging.basicConfig. This makes it impossible to use a custom formatter, for example. – Clément Dec 14 '15 at 23:36
  • @Clément: Two (overlapping?) questions: (1) What do you mean exactly by "voids calls to logging.basicConfig" and (2) what would the alternative be? Both `logging.basicConfig()` and `coloredlogs.install()` install a stream handler that logs to the console, so without "voiding" you would get duplicate messages... – xolox Dec 15 '15 at 11:12
  • I expected either magic for (1), or (more reasonably) a way to tell `coloredlogs.install` which format to use, as in the `colorlog` package. – Clément Dec 15 '15 at 19:16
  • Thanks for the comment, @Clément; I'm afraid that's a deal-breaker for me. I encourage xolox or anyone else to fix this, though, because a library like this would be useful. Unfortunately, I'm not in a position to contribute to it myself at this time. – Michael Scheper Jul 27 '17 at 18:43
  • 2
    FYI: Newer versions of the coloredlogs package use a custom formatter to inject ANSI escape sequences. This custom formatter supports user defined log formats in the same way as Python's logging module. However I don't see how coloredlogs could be combined with a user defined formatter, this goes against the design of the package. – xolox Jul 27 '17 at 20:29
  • If you want to use it in conjunction with the tensorflow logger simply use `coloredlogs.install(logger=tf.logging._get_logger())` – Ido_f Jun 05 '18 at 08:15
  • easy to use.. but not able to save the logs to a file.. any workaround? – KawaiKx Nov 24 '19 at 15:00
  • Can we also write it into a file? – alper Jan 30 '20 at 22:39
  • 2
    Very well done!! and as of today it works in Windows like a dream :) – SkyWalker Apr 09 '20 at 14:20
  • 1
    Installed, tested, doesn't work, uninstalled. – Thomas Wana Apr 01 '21 at 11:19
  • In my case (on W10), running the app normally this works, but using `pytest` I have to install and import `colorama` and then go `colorama.init()`, otherwise some tests fail (possibly to do with `logging.basicConfig`) ... also another issue: the standard colours on a black background (W10) console are too dark: any simple way to switch this to the "bright" versions of the colours? – mike rodent Nov 10 '21 at 10:51
  • Ah, yes, and you sort of impose your own formatting in the message of the log. Is there a way to turn this off? I mean, I've already put all my time, level, file, line number information in my own formatter... – mike rodent Nov 10 '21 at 10:59
  • How do you change the colours? – user7660047 Apr 17 '22 at 08:32
109

Update: Because this is an itch that I've been meaning to scratch for so long, I went ahead and wrote a library for lazy people like me who just want simple ways to do things: zenlog

Colorlog is excellent for this. It's available on PyPI (and thus installable through pip install colorlog) and is actively maintained.

Here's a quick copy-and-pasteable snippet to set up logging and print decent-looking log messages:

import logging
LOG_LEVEL = logging.DEBUG
LOGFORMAT = "  %(log_color)s%(levelname)-8s%(reset)s | %(log_color)s%(message)s%(reset)s"
from colorlog import ColoredFormatter
logging.root.setLevel(LOG_LEVEL)
formatter = ColoredFormatter(LOGFORMAT)
stream = logging.StreamHandler()
stream.setLevel(LOG_LEVEL)
stream.setFormatter(formatter)
log = logging.getLogger('pythonConfig')
log.setLevel(LOG_LEVEL)
log.addHandler(stream)

log.debug("A quirky message only developers care about")
log.info("Curious users might want to know this")
log.warn("Something is wrong and any user should be informed")
log.error("Serious stuff, this is red for a reason")
log.critical("OH NO everything is on fire")

Output:

Colorlog output

Michael Schock
  • 575
  • 5
  • 8
rlafuente
  • 1,854
  • 2
  • 14
  • 14
93

Quick and dirty solution for predefined log levels and without defining a new class.

logging.addLevelName( logging.WARNING, "\033[1;31m%s\033[1;0m" % logging.getLevelName(logging.WARNING))
logging.addLevelName( logging.ERROR, "\033[1;41m%s\033[1;0m" % logging.getLevelName(logging.ERROR))
moka
  • 22,846
  • 4
  • 51
  • 67
ABC
  • 939
  • 6
  • 2
  • @spiderplant0 import logging; # paste the code from @ABC; try it with logging.warning('this is a test'). You will see the uppercase part of "WARNING: this is a test" coloured. It works on linux only btw – Riccardo Galli Jan 29 '14 at 23:39
  • 4
    Since only the loglevel name is coloured you have to make sure that the loglevel name is printed to console at all. This does not happen out of the box for me. Something along these lines will help: `logging.basicConfig(format='%(asctime)s [%(name)s] [%(levelname)s] %(message)s')` Where of course the `%(levelnames)s` is important. – Sebastian Apr 26 '16 at 14:23
  • 9
    Most simple and cleanest solution to apply and understand. – F. Santiago May 01 '17 at 12:20
  • 1
    Just try in in the Linux console. `echo -e "Normal texst \033[1;31mred bold text\033[0m normal text again"`. echo `-e` option interpret "\033" as octal form of Escape ASCII symbol. This special symbol makes some compatible terminals interpret subsequent characters (to char `m` inclusive) as special commands. https://en.wikipedia.org/wiki/ANSI_escape_code – eugene-bright Jul 11 '18 at 00:39
  • 7
    Minor improvement: put this code inside `if sys.sdterr.isatty():`. In this case if you redirect output to file, the file will not contain these escape characters. – lesnik Sep 20 '18 at 18:44
  • I'd say, the BEST! – Marek Gancarz Aug 13 '20 at 20:04
81

Here is a solution that should work on any platform. If it doesn't just tell me and I will update it.

How it works: on platform supporting ANSI escapes is using them (non-Windows) and on Windows it does use API calls to change the console colors.

The script does hack the logging.StreamHandler.emit method from standard library adding a wrapper to it.

TestColorer.py

# Usage: add Colorer.py near you script and import it.
import logging
import Colorer

logging.warn("a warning")
logging.error("some error")
logging.info("some info")

Colorer.py

#!/usr/bin/env python
# encoding: utf-8
import logging
# now we patch Python code to add color support to logging.StreamHandler
def add_coloring_to_emit_windows(fn):
        # add methods we need to the class
    def _out_handle(self):
        import ctypes
        return ctypes.windll.kernel32.GetStdHandle(self.STD_OUTPUT_HANDLE)
    out_handle = property(_out_handle)

    def _set_color(self, code):
        import ctypes
        # Constants from the Windows API
        self.STD_OUTPUT_HANDLE = -11
        hdl = ctypes.windll.kernel32.GetStdHandle(self.STD_OUTPUT_HANDLE)
        ctypes.windll.kernel32.SetConsoleTextAttribute(hdl, code)

    setattr(logging.StreamHandler, '_set_color', _set_color)

    def new(*args):
        FOREGROUND_BLUE      = 0x0001 # text color contains blue.
        FOREGROUND_GREEN     = 0x0002 # text color contains green.
        FOREGROUND_RED       = 0x0004 # text color contains red.
        FOREGROUND_INTENSITY = 0x0008 # text color is intensified.
        FOREGROUND_WHITE     = FOREGROUND_BLUE|FOREGROUND_GREEN |FOREGROUND_RED
       # winbase.h
        STD_INPUT_HANDLE = -10
        STD_OUTPUT_HANDLE = -11
        STD_ERROR_HANDLE = -12

        # wincon.h
        FOREGROUND_BLACK     = 0x0000
        FOREGROUND_BLUE      = 0x0001
        FOREGROUND_GREEN     = 0x0002
        FOREGROUND_CYAN      = 0x0003
        FOREGROUND_RED       = 0x0004
        FOREGROUND_MAGENTA   = 0x0005
        FOREGROUND_YELLOW    = 0x0006
        FOREGROUND_GREY      = 0x0007
        FOREGROUND_INTENSITY = 0x0008 # foreground color is intensified.

        BACKGROUND_BLACK     = 0x0000
        BACKGROUND_BLUE      = 0x0010
        BACKGROUND_GREEN     = 0x0020
        BACKGROUND_CYAN      = 0x0030
        BACKGROUND_RED       = 0x0040
        BACKGROUND_MAGENTA   = 0x0050
        BACKGROUND_YELLOW    = 0x0060
        BACKGROUND_GREY      = 0x0070
        BACKGROUND_INTENSITY = 0x0080 # background color is intensified.     

        levelno = args[1].levelno
        if(levelno>=50):
            color = BACKGROUND_YELLOW | FOREGROUND_RED | FOREGROUND_INTENSITY | BACKGROUND_INTENSITY 
        elif(levelno>=40):
            color = FOREGROUND_RED | FOREGROUND_INTENSITY
        elif(levelno>=30):
            color = FOREGROUND_YELLOW | FOREGROUND_INTENSITY
        elif(levelno>=20):
            color = FOREGROUND_GREEN
        elif(levelno>=10):
            color = FOREGROUND_MAGENTA
        else:
            color =  FOREGROUND_WHITE
        args[0]._set_color(color)

        ret = fn(*args)
        args[0]._set_color( FOREGROUND_WHITE )
        #print "after"
        return ret
    return new

def add_coloring_to_emit_ansi(fn):
    # add methods we need to the class
    def new(*args):
        levelno = args[1].levelno
        if(levelno>=50):
            color = '\x1b[31m' # red
        elif(levelno>=40):
            color = '\x1b[31m' # red
        elif(levelno>=30):
            color = '\x1b[33m' # yellow
        elif(levelno>=20):
            color = '\x1b[32m' # green 
        elif(levelno>=10):
            color = '\x1b[35m' # pink
        else:
            color = '\x1b[0m' # normal
        args[1].msg = color + args[1].msg +  '\x1b[0m'  # normal
        #print "after"
        return fn(*args)
    return new

import platform
if platform.system()=='Windows':
    # Windows does not support ANSI escapes and we are using API calls to set the console color
    logging.StreamHandler.emit = add_coloring_to_emit_windows(logging.StreamHandler.emit)
else:
    # all non-Windows platforms are supporting ANSI escapes so we use them
    logging.StreamHandler.emit = add_coloring_to_emit_ansi(logging.StreamHandler.emit)
    #log = logging.getLogger()
    #log.addFilter(log_filter())
    #//hdlr = logging.StreamHandler()
    #//hdlr.setFormatter(formatter())
Dave
  • 4,420
  • 1
  • 34
  • 39
sorin
  • 161,544
  • 178
  • 535
  • 806
  • 3
    I wrote a StreamHandler class based on this, see https://gist.github.com/mooware/a1ed40987b6cc9ab9c65. – mooware Jun 15 '14 at 00:06
  • 2
    this worked for me! line 90: should be `args[1].msg = color + str(args[1].msg) + '\x1b[0m' # normal`. – Govinnage Rasika Perera Apr 25 '15 at 17:25
  • I like this solution. using it currently. I see there is an attribute _set_color, is there a way to do this for a specific log message? [edit], oh see that is just a patch for windows machines. would be nice to add custom for different use cases. – brizz May 25 '15 at 23:15
  • +1 for ANSI color. In xterm you can even get 256 colors at a time and you can define the palette dynamically! Note, however, that all calls to logging functions should be *within a function definition* to avoid potential [import lock problems when logging outside of a function definition](https://stackoverflow.com/questions/46356672/). Your code looks mostly good; just that little bit in `TestColorer.py` concerns me. – personal_cloud Sep 23 '17 at 05:36
  • This results in color codes at the beginning and end of the log messages in actual log files. – MehmedB Jul 17 '19 at 07:26
  • Can I keep the message string as normal color and only color the `date, filename linenumber` section on the left hand side? – alper Jun 03 '20 at 12:59
24

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

KCJ
  • 372
  • 2
  • 8
18

You can import the colorlog module and use its ColoredFormatter for colorizing log messages.

Example

Boilerplate for main module:

import logging
import os
import sys
try:
    import colorlog
except ImportError:
    pass

def setup_logging():
    root = logging.getLogger()
    root.setLevel(logging.DEBUG)
    format      = '%(asctime)s - %(levelname)-8s - %(message)s'
    date_format = '%Y-%m-%d %H:%M:%S'
    if 'colorlog' in sys.modules and os.isatty(2):
        cformat = '%(log_color)s' + format
        f = colorlog.ColoredFormatter(cformat, date_format,
              log_colors = { 'DEBUG'   : 'reset',       'INFO' : 'reset',
                             'WARNING' : 'bold_yellow', 'ERROR': 'bold_red',
                             'CRITICAL': 'bold_red' })
    else:
        f = logging.Formatter(format, date_format)
    ch = logging.StreamHandler()
    ch.setFormatter(f)
    root.addHandler(ch)

setup_logging()
log = logging.getLogger(__name__)

The code only enables colors in log messages, if the colorlog module is installed and if the output actually goes to a terminal. This avoids escape sequences being written to a file when the log output is redirected.

Also, a custom color scheme is setup that is better suited for terminals with dark background.

Some example logging calls:

log.debug   ('Hello Debug')
log.info    ('Hello Info')
log.warn    ('Hello Warn')
log.error   ('Hello Error')
log.critical('Hello Critical')

Output:

enter image description here

maxschlepzig
  • 35,645
  • 14
  • 145
  • 182
  • 2
    Also can use `colorlog.basicConfig` instead of `logging.basicConfig` which has some good defaults – MarSoft Aug 19 '17 at 22:11
  • 1
    For the record, colorlog does not always work directly on Windows platforms (as specified, colorama dependency is required). Even with that, I had trouble to get it to work in Anaconda/Spyder env. You may need to specify colorama.init(strip=False) for instance in escape_code.py (as indicated in this thread https://github.com/spyder-ide/spyder/issues/1917) – Matt-Mac-Muffin Jan 02 '18 at 14:23
17

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

Cristian Ciupitu
  • 20,270
  • 7
  • 50
  • 76
mikey
  • 2,158
  • 1
  • 19
  • 24
15

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)
Cristian Ciupitu
  • 20,270
  • 7
  • 50
  • 76
Ramonster
  • 159
  • 1
  • 4
15

I updated the example from airmind supporting tags for foreground and background. Just use the color variables $BLACK - $WHITE in your log formatter string. To set the background just use $BG-BLACK - $BG-WHITE.

import logging

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

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

RESET_SEQ = "\033[0m"
COLOR_SEQ = "\033[1;%dm"
BOLD_SEQ  = "\033[1m"

class ColorFormatter(logging.Formatter):

    def __init__(self, *args, **kwargs):
        # can't do super(...) here because Formatter is an old school class
        logging.Formatter.__init__(self, *args, **kwargs)

    def format(self, record):
        levelname = record.levelname
        color     = COLOR_SEQ % (30 + COLORS[levelname])
        message   = logging.Formatter.format(self, record)
        message   = message.replace("$RESET", RESET_SEQ)\
                           .replace("$BOLD",  BOLD_SEQ)\
                           .replace("$COLOR", color)
        for k,v in COLORS.items():
            message = message.replace("$" + k,    COLOR_SEQ % (v+30))\
                             .replace("$BG" + k,  COLOR_SEQ % (v+40))\
                             .replace("$BG-" + k, COLOR_SEQ % (v+40))
        return message + RESET_SEQ

logging.ColorFormatter = ColorFormatter

So now you can simple do the following in your config file:

[formatter_colorFormatter]
class=logging.ColorFormatter
format= $COLOR%(levelname)s $RESET %(asctime)s $BOLD$COLOR%(name)s$RESET %(message)s
camillobruni
  • 2,298
  • 16
  • 26
  • Great improvement. However the comment about `super` only applies for some ancient Python version I guess? Since this answer is from 2010. It worked fine for me with Python 2.7 – Joakim Oct 12 '15 at 14:20
11

Now there is a released PyPi module for customizable colored logging output:

https://pypi.python.org/pypi/rainbow_logging_handler/

and

https://github.com/laysakura/rainbow_logging_handler

  • Supports Windows

  • Supports Django

  • Customizable colors

As this is distributed as a Python egg, it is very easy to install for any Python application.

Mikko Ohtamaa
  • 82,057
  • 50
  • 264
  • 435
  • Note that this answer is old as Rome and I recommend [ColoredLogs by Peter Odding instead](https://pypi.org/project/coloredlogs/) – Mikko Ohtamaa Jul 28 '22 at 14:48
11

Look at the following solution. The stream handler should be the thing doing the colouring, then you have the option of colouring words rather than just the whole line (with the Formatter).

http://plumberjack.blogspot.com/2010/12/colorizing-logging-output-in-terminals.html

Nick
  • 27,566
  • 12
  • 60
  • 72
  • You can find an updated implementation in this [gist](https://gist.github.com/758430) (maintained by the blog author). I'm using it and works just fine. Thanks for sharing. – noisebleed Apr 14 '12 at 22:52
10

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
dux2
  • 1,770
  • 1
  • 21
  • 27
8

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
Milovan Tomašević
  • 6,823
  • 1
  • 50
  • 42
8

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

ToTamire
  • 1,425
  • 1
  • 13
  • 23
  • I've been googling for at least and hour and can't figure out how to change the colours in Python. Is it passed as a parameter to .install()? It's frustrating there are not examples in documentation, where it's shown as environment variables, not code. – user7660047 Apr 17 '22 at 08:14
  • @user7660047 After installing coloredlogs, the code from minimal usage should show you colors. If not maybye your command line program don't support colors. – ToTamire Apr 17 '22 at 15:46
  • It shows colours ok, I just don't seem to be able to figure out how to change them. – user7660047 Apr 18 '22 at 13:54
  • To me this is the smartes and simplest solution. – gdm Sep 15 '22 at 12:33
7

Another minor remix of airmind's approach that keeps everything in one class:

class ColorFormatter(logging.Formatter):
  FORMAT = ("[$BOLD%(name)-20s$RESET][%(levelname)-18s]  "
            "%(message)s "
            "($BOLD%(filename)s$RESET:%(lineno)d)")

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

  RESET_SEQ = "\033[0m"
  COLOR_SEQ = "\033[1;%dm"
  BOLD_SEQ = "\033[1m"

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

  def formatter_msg(self, msg, use_color = True):
    if use_color:
      msg = msg.replace("$RESET", self.RESET_SEQ).replace("$BOLD", self.BOLD_SEQ)
    else:
      msg = msg.replace("$RESET", "").replace("$BOLD", "")
    return msg

  def __init__(self, use_color=True):
    msg = self.formatter_msg(self.FORMAT, use_color)
    logging.Formatter.__init__(self, msg)
    self.use_color = use_color

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

To use attach the formatter to a handler, something like:

handler.setFormatter(ColorFormatter())
logger.addHandler(handler)
gravitation
  • 1,939
  • 2
  • 21
  • 26
7

A simple but very flexible tool for coloring ANY terminal text is 'colout'.

pip install colout
myprocess | colout REGEX_WITH_GROUPS color1,color2...

Where any text in the output of 'myprocess' which matches group 1 of the regex will be colored with color1, group 2 with color2, etc.

For example:

tail -f /var/log/mylogfile | colout '^(\w+ \d+ [\d:]+)|(\w+\.py:\d+ .+\(\)): (.+)$' white,black,cyan bold,bold,normal

i.e. the first regex group (parens) matches the initial date in the logfile, the second group matches a python filename, line number and function name, and the third group matches the log message that comes after that. I also use a parallel sequence of 'bold/normals' as well as the sequence of colors. This looks like:

logfile with colored formatting

Note that lines or parts of lines which don't match any of my regex are still echoed, so this isn't like 'grep --color' - nothing is filtered out of the output.

Obviously this is flexible enough that you can use it with any process, not just tailing logfiles. I usually just whip up a new regex on the fly any time I want to colorize something. For this reason, I prefer colout to any custom logfile-coloring tool, because I only need to learn one tool, regardless of what I'm coloring: logging, test output, syntax highlighting snippets of code in the terminal, etc.

It also avoids actually dumping ANSI codes in the logfile itself, which IMHO is a bad idea, because it will break things like grepping for patterns in the logfile unless you always remember to match the ANSI codes in your grep regex.

Jonathan Hartley
  • 15,462
  • 9
  • 79
  • 80
7

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)
lain
  • 448
  • 4
  • 8
5

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 + ;

Mojtaba Hosseini
  • 95,414
  • 31
  • 268
  • 278
5

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

aidanmelen
  • 6,194
  • 1
  • 23
  • 24
5

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

Ronin
  • 1,811
  • 1
  • 16
  • 17
4
import logging
import sys

colors = {'pink': '\033[95m', 'blue': '\033[94m', 'green': '\033[92m', 'yellow': '\033[93m', 'red': '\033[91m',
      'ENDC': '\033[0m', 'bold': '\033[1m', 'underline': '\033[4m'}

logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)


def str_color(color, data):
    return colors[color] + str(data) + colors['ENDC']

params = {'param1': id1, 'param2': id2}

logging.info('\nParams:' + str_color("blue", str(params)))`
  • +1 Nice example with the `[9*m` codes for the "bright" ANSI colors! P.S. your last line concerns me a little because it is not yet known [whether logging outside of a function definition is safe in Python](https://stackoverflow.com/questions/46356672/). – personal_cloud Sep 23 '17 at 05:43
3

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

Scott
  • 1,648
  • 13
  • 21
2

Here's my solution:

class ColouredFormatter(logging.Formatter):
    RESET = '\x1B[0m'
    RED = '\x1B[31m'
    YELLOW = '\x1B[33m'
    BRGREEN = '\x1B[01;32m'  # grey in solarized for terminals

    def format(self, record, colour=False):
        message = super().format(record)

        if not colour:
            return message

        level_no = record.levelno
        if level_no >= logging.CRITICAL:
            colour = self.RED
        elif level_no >= logging.ERROR:
            colour = self.RED
        elif level_no >= logging.WARNING:
            colour = self.YELLOW
        elif level_no >= logging.INFO:
            colour = self.RESET
        elif level_no >= logging.DEBUG:
            colour = self.BRGREEN
        else:
            colour = self.RESET

        message = colour + message + self.RESET

        return message


class ColouredHandler(logging.StreamHandler):
    def __init__(self, stream=sys.stdout):
        super().__init__(stream)

    def format(self, record, colour=False):
        if not isinstance(self.formatter, ColouredFormatter):
            self.formatter = ColouredFormatter()

        return self.formatter.format(record, colour)

    def emit(self, record):
        stream = self.stream
        try:
            msg = self.format(record, stream.isatty())
            stream.write(msg)
            stream.write(self.terminator)
            self.flush()
        except Exception:
            self.handleError(record)


h = ColouredHandler()
h.formatter = ColouredFormatter('{asctime} {levelname:8} {message}', '%Y-%m-%d %H:%M:%S', '{')
logging.basicConfig(level=logging.DEBUG, handlers=[h])
veegee
  • 359
  • 5
  • 5
2

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

SebiSebi
  • 295
  • 8
  • 17
2

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)

lesnik
  • 2,507
  • 2
  • 25
  • 24
2

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]
)
shakfu
  • 41
  • 1
  • 5
  • You (at least I) need(ed) to change fmt.format to the following ```python fmt = "%(asctime)s - {}%(levelname)-8s{} - %(name)s.%(funcName)s - %(message)s" FORMATS = { logging.DEBUG: white + fmt + reset, logging.INFO: grey + fmt + reset, logging.WARNING: yellow + fmt + reset, logging.ERROR: red + fmt + reset, logging.CRITICAL: bold_red + fmt + reset, } ``` – jawalking Feb 11 '22 at 17:47
  • 1
    Not really, @jaywailking, I was intentionally trying not to color the whole line, but only the `levelname` part. – shakfu Feb 12 '22 at 18:23
  • Yes, it makes more sense to color only to the levels, thanks for the code. – Cyril Oct 18 '22 at 09:34
1

Just answered the same on similar question: Python | change text color in shell

The idea is to use the clint library. Which has support for MAC, Linux and Windows shells (CLI).

Community
  • 1
  • 1
Kostanos
  • 9,615
  • 4
  • 51
  • 65
1

While the other solutions seem fine they have some issues. Some do colour the whole lines which some times is not wanted and some omit any configuration you might have all together. The solution below doesn't affect anything but the message itself.

Code

class ColoredFormatter(logging.Formatter):
    def format(self, record):
        if record.levelno == logging.WARNING:
            record.msg = '\033[93m%s\033[0m' % record.msg
        elif record.levelno == logging.ERROR:
            record.msg = '\033[91m%s\033[0m' % record.msg
        return logging.Formatter.format(self, record)

Example

logger = logging.getLogger('mylogger')
handler = logging.StreamHandler()

log_format = '[%(asctime)s]:%(levelname)-7s:%(message)s'
time_format = '%H:%M:%S'
formatter = ColoredFormatter(log_format, datefmt=time_format)
handler.setFormatter(formatter)
logger.addHandler(handler)

logger.warn('this should be yellow')
logger.error('this should be red')

Output

[17:01:36]:WARNING:this should be yellow
[17:01:37]:ERROR  :this should be red

As you see, everything else still gets outputted and remain in their initial color. If you want to change anything else than the message you can simply pass the color codes to log_format in the example.

Pithikos
  • 18,827
  • 15
  • 113
  • 136
  • when i use it, messages are printed twice. do you know why? – Validus Oculus Oct 15 '16 at 08:28
  • @ could you elaborate? Namely you mean something like `[17:01:36]:WARNING:this should be yellowthis should be yellow` or a full line being printed twice? – Pithikos Oct 15 '16 at 10:05
  • Sorry for brevity of the comment. The former happened: [17:01:36]:WARNING:this should be yellow\nthis should be yellow. However, I only want the formatted one to be shown, otherwise it looks like a garbage due to redundant logs. – Validus Oculus Oct 15 '16 at 10:15
  • @MuratKarakuş not sure why this happens without having a full view on the implementation. If you are using a custom logger maybe you are interfering at some point? A fast fix could be to remove the `7s:%(message)s` from the `log_format`. – Pithikos Oct 15 '16 at 10:50
1

I have two submissions to add, one of which colorizes just the message (ColoredFormatter), and one of which colorizes the entire line (ColorizingStreamHandler). These also include more ANSI color codes than previous solutions.

Some content has been sourced (with modification) from: The post above, and http://plumberjack.blogspot.com/2010/12/colorizing-logging-output-in-terminals.html.

Colorizes the message only:

class ColoredFormatter(logging.Formatter):
    """Special custom formatter for colorizing log messages!"""

    BLACK = '\033[0;30m'
    RED = '\033[0;31m'
    GREEN = '\033[0;32m'
    BROWN = '\033[0;33m'
    BLUE = '\033[0;34m'
    PURPLE = '\033[0;35m'
    CYAN = '\033[0;36m'
    GREY = '\033[0;37m'

    DARK_GREY = '\033[1;30m'
    LIGHT_RED = '\033[1;31m'
    LIGHT_GREEN = '\033[1;32m'
    YELLOW = '\033[1;33m'
    LIGHT_BLUE = '\033[1;34m'
    LIGHT_PURPLE = '\033[1;35m'
    LIGHT_CYAN = '\033[1;36m'
    WHITE = '\033[1;37m'

    RESET = "\033[0m"

    def __init__(self, *args, **kwargs):
        self._colors = {logging.DEBUG: self.DARK_GREY,
                        logging.INFO: self.RESET,
                        logging.WARNING: self.BROWN,
                        logging.ERROR: self.RED,
                        logging.CRITICAL: self.LIGHT_RED}
        super(ColoredFormatter, self).__init__(*args, **kwargs)

    def format(self, record):
        """Applies the color formats"""
        record.msg = self._colors[record.levelno] + record.msg + self.RESET
        return logging.Formatter.format(self, record)

    def setLevelColor(self, logging_level, escaped_ansi_code):
        self._colors[logging_level] = escaped_ansi_code

Colorizes the whole line:

class ColorizingStreamHandler(logging.StreamHandler):

    BLACK = '\033[0;30m'
    RED = '\033[0;31m'
    GREEN = '\033[0;32m'
    BROWN = '\033[0;33m'
    BLUE = '\033[0;34m'
    PURPLE = '\033[0;35m'
    CYAN = '\033[0;36m'
    GREY = '\033[0;37m'

    DARK_GREY = '\033[1;30m'
    LIGHT_RED = '\033[1;31m'
    LIGHT_GREEN = '\033[1;32m'
    YELLOW = '\033[1;33m'
    LIGHT_BLUE = '\033[1;34m'
    LIGHT_PURPLE = '\033[1;35m'
    LIGHT_CYAN = '\033[1;36m'
    WHITE = '\033[1;37m'

    RESET = "\033[0m"

    def __init__(self, *args, **kwargs):
        self._colors = {logging.DEBUG: self.DARK_GREY,
                        logging.INFO: self.RESET,
                        logging.WARNING: self.BROWN,
                        logging.ERROR: self.RED,
                        logging.CRITICAL: self.LIGHT_RED}
        super(ColorizingStreamHandler, self).__init__(*args, **kwargs)

    @property
    def is_tty(self):
        isatty = getattr(self.stream, 'isatty', None)
        return isatty and isatty()

    def emit(self, record):
        try:
            message = self.format(record)
            stream = self.stream
            if not self.is_tty:
                stream.write(message)
            else:
                message = self._colors[record.levelno] + message + self.RESET
                stream.write(message)
            stream.write(getattr(self, 'terminator', '\n'))
            self.flush()
        except (KeyboardInterrupt, SystemExit):
            raise
        except:
            self.handleError(record)

    def setLevelColor(self, logging_level, escaped_ansi_code):
        self._colors[logging_level] = escaped_ansi_code
1
import logging

logging.basicConfig(filename="f.log" filemode='w', level=logging.INFO,
                    format = "%(logger_name)s %(color)s  %(message)s %(endColor)s")


class Logger(object):
    __GREEN = "\033[92m"
    __RED = '\033[91m'
    __ENDC = '\033[0m'

    def __init__(self, name):
        self.logger = logging.getLogger(name)
        self.extra={'logger_name': name, 'endColor': self.__ENDC, 'color': self.__GREEN}


    def info(self, msg):
        self.extra['color'] = self.__GREEN
        self.logger.info(msg, extra=self.extra)

    def error(self, msg):
        self.extra['color'] = self.__RED
        self.logger.error(msg, extra=self.extra)

Usage

Logger("File Name").info("This shows green text")

aristotll
  • 8,694
  • 6
  • 33
  • 53
  • For console you can leave out filename or simply filename='' should work. modify basicConfig to include other properties like file number, module .. – estifanos gebrehiwot Feb 28 '17 at 20:24
1

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
Nick
  • 27,566
  • 12
  • 60
  • 72
  • It was supposed to be pseudo code (as _set_colour missing as well), but have added something. The thing had most trouble with was knowing how to attach the formatter correctly. – Nick Jan 14 '11 at 13:55
  • See the "plumber jack" solution. I think this is a better way to solve the problem (i.e. the handler should do the colourisation). http://stackoverflow.com/questions/384076/how-can-i-make-the-python-logging-output-to-be-colored/4691726#4691726 – Nick Jan 14 '11 at 13:56
1

This is an Enum containing the colour codes:

class TerminalColour:
    """
    Terminal colour formatting codes
    """
    # https://stackoverflow.com/questions/287871/print-in-terminal-with-colors
    MAGENTA = '\033[95m'
    BLUE = '\033[94m'
    GREEN = '\033[92m'
    YELLOW = '\033[93m'
    RED = '\033[91m'
    GREY = '\033[0m'  # normal
    WHITE = '\033[1m'  # bright white
    UNDERLINE = '\033[4m'

This may be applied to the names of each log level. Be aware that this is a monstrous hack.

logging.addLevelName(logging.INFO, "{}{}{}".format(TerminalColour.WHITE, logging.getLevelName(logging.INFO), TerminalColour.GREY))
logging.addLevelName(logging.WARNING, "{}{}{}".format(TerminalColour.YELLOW, logging.getLevelName(logging.WARNING), TerminalColour.GREY))
logging.addLevelName(logging.ERROR, "{}{}{}".format(TerminalColour.RED, logging.getLevelName(logging.ERROR), TerminalColour.GREY))
logging.addLevelName(logging.CRITICAL, "{}{}{}".format(TerminalColour.MAGENTA, logging.getLevelName(logging.CRITICAL), .GREY))

Note that your log formatter must include the name of the log level

%(levelname)

for example:

    LOGGING = {
...
        'verbose': {
            'format': '%(asctime)s %(levelname)s %(name)s:%(lineno)s %(module)s %(process)d %(thread)d %(message)s'
        },
        'simple': {
            'format': '[%(asctime)s] %(levelname)s %(name)s %(message)s'
        },
Jérôme Pin
  • 1,363
  • 4
  • 18
  • 33
Joe Heffer
  • 755
  • 7
  • 9
1

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 
Akhil
  • 912
  • 12
  • 23
  • while this doesn't answer the question regarding Python, it is much better than the suggested alternatives because it at least uses the terminal capabilities database instead of hard-coding ANSI escape sequences and simply assuming they will work… – joki Apr 30 '21 at 08:52
1

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!

saner99
  • 11
  • 2
1

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")
Cristian Ciupitu
  • 20,270
  • 7
  • 50
  • 76
1

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")
user2240578
  • 87
  • 2
  • 6
0

Use pyfancy.

Example:

print(pyfancy.RED + "Hello Red!" + pyfancy.END)
hichris123
  • 10,145
  • 15
  • 56
  • 70
WebMaster
  • 117
  • 1
  • 9
0

Just another solution, with the colors of ZetaSyanthis:

def config_log(log_level):

    def set_color(level, code):
        level_fmt = "\033[1;" + str(code) + "m%s\033[1;0m" 
        logging.addLevelName( level, level_fmt % logging.getLevelName(level) )

    std_stream = sys.stdout
    isatty = getattr(std_stream, 'isatty', None)
    if isatty and isatty():
        levels = [logging.DEBUG, logging.CRITICAL, logging.WARNING, logging.ERROR]
        for idx, level in enumerate(levels):
            set_color(level, 30 + idx )
        set_color(logging.DEBUG, 0)
    logging.basicConfig(stream=std_stream, level=log_level)

call it once from your __main__ function. I have something like this there:

options, arguments = p.parse_args()
log_level = logging.DEBUG if options.verbose else logging.WARNING
config_log(log_level)

it also verifies that the output is a console, otherwise no colors are used.

yucer
  • 4,431
  • 3
  • 34
  • 42
0

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")})
vey
  • 101
  • 8
  • I think `yachalk` would have been nice to use but the output is not changing colour.. It's still just the default colour that the console outputs. – Eitel Dagnin Mar 14 '22 at 08:14
0

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()])
catwith
  • 875
  • 10
  • 13
0

If anyone is looking for a pretty coloring as well as custom logs level coloring, you may look at this adapted solution (it uses the moecolor library):

#Install moecolor
pip install moecolor
from moecolor import FormatText as ft

class ConsoleFormatter(logging.Formatter):
    default_format = f"[%(asctime)s | %(name)s | %(funcName)s | LN%(lineno)s | %(levelname)s]: %(message)s"
    time_portion =  ft('%(asctime)s', color='purple').text
    format_portion = ' | %(name)s | %(funcName)s | LN%(lineno)d | %(levelname)s]: '
    FORMATS = {
        'DEBUG': time_portion + ft(format_portion, color='yellow').text + ft('%(message)s', color='fff9ae').text,
        'INFO': time_portion + ft(format_portion, color='green').text + ft('%(message)s', color='#d3ffb3').text,
        'WARNING': time_portion + ft(format_portion, color='orange').text + ft('%(message)s', color='#ffc100').text,
        'TIMER': time_portion + ft(format_portion, color='blue').text + ft('%(message)s', color='#00b4d8').text, # Note, this is a custom log level
        'ERROR': time_portion + ft(format_portion, color='red').text + ft('%(message)s', color='#ba262b').text,
        'CRITICAL': time_portion + ft(format_portion, color='#8D0101').text + ft('%(message)s', color='#D5212E').text,
    }

    def format(self, record):
        _format = self.FORMATS.get(record.levelname, self.default_format)
        formatter = logging.Formatter(_format)
        return formatter.format(record)

You can use it as follows:

console_handler = logging.StreamHandler()
console_handler.setFormatter(ConsoleFormatter())
0

I've created a class that will just show the error level name in whatever color you choose. Most of the code is just defining the color patterns, then overriding some variables in the object for the logging library to use

class ColorLognameFormatter(logging.Formatter):
    _level_str_len = 8
    # Define the color codes
    _reset_str = '\x1b[0m'
    _grey_str = '\x1b[38;21m'
    _blue_str = '\x1b[38;5;39m'
    _yllw_str = '\x1b[38;5;226m'
    _sred_str = '\x1b[38;5;196m'
    _bred_str = '\x1b[31;1m'
    # Make the basic strings
    _debug_color_str = f"{_grey_str}DEBUG{_reset_str}".ljust(_level_str_len + len(_reset_str) + len(_grey_str), ' ')
    _info_color_str = f"{_blue_str}INFO{_reset_str}".ljust(_level_str_len + len(_reset_str) + len(_blue_str), ' ')
    _warn_color_str = f"{_yllw_str}WARNING{_reset_str}".ljust(_level_str_len + len(_reset_str) + len(_yllw_str), ' ')
    _error_color_str = f"{_sred_str}ERROR{_reset_str}".ljust(_level_str_len + len(_reset_str) + len(_sred_str), ' ')
    _crit_color_str = f"{_bred_str}CRITICAL{_reset_str}".ljust(_level_str_len + len(_reset_str) + len(_bred_str), ' ')
    # Format into a dict
    _color_levelname = {'DEBUG': _debug_color_str,
                        'INFO': _info_color_str,
                        'WARNING': _warn_color_str,
                        'ERROR': _error_color_str,
                        'CRITICAL': _error_color_str}

    def __init__(self, fmt='%(levelname)s | %(message)s', *args, **kwargs):
        super().__init__(fmt, *args, **kwargs)

    def format(self, record):
        # When calling format, replace the levelname with a colored version
        # Note: the string size is greatly increased because of the color codes
        record.levelname = self._color_levelname[record.levelname]
        return super().format(record)

The main change is it sets the default fmt string to %(levelname)s | %(message)s and substitutes the levelname before calling format

Usage:

from ColorLognameFormatter import ColorLognameFormatter
import logging

log_level = logging.INFO

logger = logging.getLogger(__name__)
logger.setLevel(log_level)

stdout_handler = logging.StreamHandler()

stdout_handler.setLevel(log_level)
stdout_handler.setFormatter(ColorLognameFormatter())

logger.addHandler(stdout_handler)

logger.propagate = False

logger.info("Test")
Desultory
  • 53
  • 1
  • 11