583

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?

11
  • 1
    You should specify that you want a multiplatform solution - both Linux and Windows. Commented Aug 25, 2009 at 18:55
  • 1
    Related if you use Eclipse/PyDev: Colorize logs in eclipse console Commented Nov 16, 2012 at 9:18
  • 9
    Perhaps you can also use colorlog Commented Feb 14, 2014 at 21:19
  • 8
    You may also try chromalog which I wrote to support all operating systems and Python versions (2.7 and 3.*) Commented May 14, 2015 at 22:08
  • 3
    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... Commented May 7, 2016 at 13:10

49 Answers 49

419

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/

Sign up to request clarification or add additional context in comments.

21 Comments

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
I liked this answer so much that I made a repo for it, with a few increments and a cheat sheet of ansi colors.
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.
I like this example, but you got the logging.setLevel() call wrong. With the code as shown, calling logger.info() will not output anything. setLevel() needs to be called on the logger object, not on the ch handler.
Sergey, @user7660047 and any Windows users: you might need to put os.system('color') somewhere in your code - just once, and almost anywhere between the console being created and the output being sent to it.
|
231

I already knew about the color escapes, I used them in my bash prompt a while ago.
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) 

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

7 Comments

Where is YELLOW, WHITE, BLUE, etc. defined?
@Swaroop - Those are ANSI escape codes, which you can read look up on Google, or find here: en.wikipedia.org/wiki/ANSI_escape_code, or alternatively pueblo.sourceforge.net/doc/manual/ansi_color_codes.html
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.
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.
|
222

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 

19 Comments

funny enough, i was just going to add a link to "pypi.python.org/pypi/coloredlogs/0.4.7" in this thread!
For some reason I keep getting AttributeError: 'module' object has no attribute 'install' when using coloredlogs.install(). Can you confirm that with the latest version.
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.
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.
Very well done!! and as of today it works in Windows like a dream :)
|
122

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

5 Comments

Great answer; +1. The code example could be trimmed though (are three calls to setLevel really needed?)
I was hoping I'd find an answer like this if I waded through the answers long enough. ☺ I hope @airmind will consider making this the accepted answer, so future work-smart people can find what seems to be the best library with optimal laziness. 😉
I just upvoted this for the messages examples of the OUTPUT ^^
Thanks! Really useful and worked for me like a charm!
@Clément See Milovan Tomašević's answer for simpler usage stackoverflow.com/a/65281492/3163618
108

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

9 Comments

@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
This works on Windows in some cases. It does not work in the command prompt or Powershell, but it does work in the VS Code terminal and Git Bash.
Update: another comment says that calling os.system('color') fixes the problem on Windows. This fixes it for the command prompt and Powershell. stackoverflow.com/questions/384076/…
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.
Most simple and cleanest solution to apply and understand.
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. en.wikipedia.org/wiki/ANSI_escape_code
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.
|
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()) 

6 Comments

I wrote a StreamHandler class based on this, see gist.github.com/mooware/a1ed40987b6cc9ab9c65.
this worked for me! line 90: should be args[1].msg = color + str(args[1].msg) + '\x1b[0m' # normal.
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.
+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. Your code looks mostly good; just that little bit in TestColorer.py concerns me.
This results in color codes at the beginning and end of the log messages in actual log files.
|
35

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 off debug logger = logging.getLogger("rich") logger.debug("debug...") logger.info("info...") logger.warning("warning...") logger.error("error...") logger.fatal("fatal...") 

terminal screenshot

7 Comments

Or just do python -m rich.logging to see examples for different use cases :-)
I tried, colored on DOS box only, all pink on jupyternotebook or jupyterlab.
@H.C.Chen You probably need to do a importlib.reload: import logging; import importlib; importlib.reload(logging); from rich.logging import RichHandler... since logging may have already been set. I try it in colab and kaggle and I got five colors, not that bright colors but colors nevertheless.
Oh, this is great. Have used rich but had no idea about this logging integration, thanks 🙏
Can it do file logging too?
|
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

2 Comments

BTW to add colors to the message itself just add this line before return: colored_record.msg = ('{0}{1}m{2}{3}').format(self.PREFIX, seq, colored_record.getMessage(), self.SUFFIX)
How can I make the print() statement colorful?
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

2 Comments

Also can use colorlog.basicConfig instead of logging.basicConfig which has some good defaults
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 github.com/spyder-ide/spyder/issues/1917)
16

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) 

1 Comment

Does this need any other code to use the ColoredConsoleHandler? Or does just defining a subclass of logging.StreamHandler magically register it somehow?
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 

1 Comment

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

1 Comment

You can find an updated implementation in this gist (maintained by the blog author). I'm using it and works just fine. Thanks for sharing.
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.

1 Comment

Note that this answer is old as Rome and I recommend ColoredLogs by Peter Odding instead
11

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

3 Comments

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 After installing coloredlogs, the code from minimal usage should show you colors. If not maybye your command line program don't support colors.
It shows colours ok, I just don't seem to be able to figure out how to change them.
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 [email protected] 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 

Comments

10

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 

Comments

10

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

Comments

9

Emoji

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

1 Comment

Fun idea with notebooks, although the warning and stop symbols make more sense
8

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

1 Comment

It's probably the only one that works in Python 2.7 too!
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) 

Comments

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.

Comments

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) 

Comments

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 Comment

+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.
4

Yet another solution, with emphasis on a configuration section.

You can also define which part of the message will be colored using the #color and #reset tags within the format string.

import logging GREY = "\x1b[38;20m" YELLOW = "\x1b[33;20m" RED = "\x1b[31;20m" BOLD_RED = "\x1b[31;1m" RESET = "\x1b[0m" # --- BEGIN CONFIGURATION # Notice "#color" and "#reset" tags inside format format = "%(asctime)s - %(name)s - #color%(levelname)s#reset - %(message)s (%(filename)s:%(lineno)d)" colors = { logging.DEBUG: GREY, logging.INFO: GREY, logging.WARNING: YELLOW, logging.ERROR: RED, logging.CRITICAL: BOLD_RED, } # --- END CONFIGURATION class ColorFormatter(logging.Formatter): def __init__(self, *args, colors, **kwargs): super().__init__(*args, **kwargs) replace_tags = lambda level: (self._style._fmt .replace("#color", colors.get(level, "")) .replace("#reset", RESET)) levels = set(logging.getLevelNamesMapping().values()) self._fmts = {level: replace_tags(level) for level in levels} def format(self, record): self._style._fmt = self._fmts.get(record.levelno) return super().format(record) logger = logging.getLogger("My_app") logger.setLevel(logging.DEBUG) ch = logging.StreamHandler() ch.setFormatter(ColorFormatter(fmt=format, colors=colors)) logger.addHandler(ch) logger.debug("debug message") logger.info("info message") logger.warning("warning message") logger.error("error message") logger.critical("critical message") 

Output from code

Comments

3

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

1 Comment

Looking forward to the new PR to reduce module path clutter
3

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)

Comments

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

Comments

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

Comments

2

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 

Comments

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

3 Comments

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, }
Not really, @jaywailking, I was intentionally trying not to color the whole line, but only the levelname part.
Yes, it makes more sense to color only to the levels, thanks for the code.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.