Question or problem about Python programming:
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?
How to solve the problem:
Solution 1:
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).
Solution 2:
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:
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
Solution 3:
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:
Solution 4:
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())
Solution 5:
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))