Skip to content

Instantly share code, notes, and snippets.

@mooware
Last active August 19, 2024 13:35
Show Gist options
  • Save mooware/a1ed40987b6cc9ab9c65 to your computer and use it in GitHub Desktop.
Save mooware/a1ed40987b6cc9ab9c65 to your computer and use it in GitHub Desktop.
Colored log output for Python logging framework. Works on Windows, Linux, and probably Mac as well.
# colored stream handler for python logging framework (use the ColorStreamHandler class).
#
# based on:
# http://stackoverflow.com/questions/384076/how-can-i-color-python-logging-output/1336640#1336640
# how to use:
# i used a dict-based logging configuration, not sure what else would work.
#
# import logging, logging.config, colorstreamhandler
#
# _LOGCONFIG = {
# "version": 1,
# "disable_existing_loggers": False,
#
# "handlers": {
# "console": {
# "class": "colorstreamhandler.ColorStreamHandler",
# "stream": "ext://sys.stderr",
# "level": "INFO"
# }
# },
#
# "root": {
# "level": "INFO",
# "handlers": ["console"]
# }
# }
#
# logging.config.dictConfig(_LOGCONFIG)
# mylogger = logging.getLogger("mylogger")
# mylogger.warning("foobar")
# Copyright (c) 2014 Markus Pointner
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import logging
class _AnsiColorStreamHandler(logging.StreamHandler):
DEFAULT = '\x1b[0m'
RED = '\x1b[31m'
GREEN = '\x1b[32m'
YELLOW = '\x1b[33m'
CYAN = '\x1b[36m'
CRITICAL = RED
ERROR = RED
WARNING = YELLOW
INFO = GREEN
DEBUG = CYAN
@classmethod
def _get_color(cls, level):
if level >= logging.CRITICAL: return cls.CRITICAL
elif level >= logging.ERROR: return cls.ERROR
elif level >= logging.WARNING: return cls.WARNING
elif level >= logging.INFO: return cls.INFO
elif level >= logging.DEBUG: return cls.DEBUG
else: return cls.DEFAULT
def __init__(self, stream=None):
logging.StreamHandler.__init__(self, stream)
def format(self, record):
text = logging.StreamHandler.format(self, record)
color = self._get_color(record.levelno)
return color + text + self.DEFAULT
class _WinColorStreamHandler(logging.StreamHandler):
# 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.
FOREGROUND_WHITE = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED
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.
DEFAULT = FOREGROUND_WHITE
CRITICAL = BACKGROUND_YELLOW | FOREGROUND_RED | FOREGROUND_INTENSITY | BACKGROUND_INTENSITY
ERROR = FOREGROUND_RED | FOREGROUND_INTENSITY
WARNING = FOREGROUND_YELLOW | FOREGROUND_INTENSITY
INFO = FOREGROUND_GREEN
DEBUG = FOREGROUND_CYAN
@classmethod
def _get_color(cls, level):
if level >= logging.CRITICAL: return cls.CRITICAL
elif level >= logging.ERROR: return cls.ERROR
elif level >= logging.WARNING: return cls.WARNING
elif level >= logging.INFO: return cls.INFO
elif level >= logging.DEBUG: return cls.DEBUG
else: return cls.DEFAULT
def _set_color(self, code):
import ctypes
ctypes.windll.kernel32.SetConsoleTextAttribute(self._outhdl, code)
def __init__(self, stream=None):
logging.StreamHandler.__init__(self, stream)
# get file handle for the stream
import ctypes, ctypes.util
# for some reason find_msvcrt() sometimes doesn't find msvcrt.dll on my system?
crtname = ctypes.util.find_msvcrt()
if not crtname:
crtname = ctypes.util.find_library("msvcrt")
crtlib = ctypes.cdll.LoadLibrary(crtname)
self._outhdl = crtlib._get_osfhandle(self.stream.fileno())
def emit(self, record):
color = self._get_color(record.levelno)
self._set_color(color)
logging.StreamHandler.emit(self, record)
self._set_color(self.FOREGROUND_WHITE)
# select ColorStreamHandler based on platform
import platform
if platform.system() == 'Windows':
ColorStreamHandler = _WinColorStreamHandler
else:
ColorStreamHandler = _AnsiColorStreamHandler
@brianray
Copy link

@twmht

something like (not tested)

import logging
import logging.handlers

my_logger = logging.getLogger('MyLogger')
my_logger.setLevel(logging.DEBUG)

my_logger.addHandler(ColorStreamHandler)  ## <- Just added it

my_logger.debug('this is debug')
my_logger.critical('this is critical')

@FichteFoll
Copy link

This failed for me on Windows with Python 3.5 x86_64.

Exchanged line 104 for this:

        crtname = ctypes.util.find_msvcrt()
        if not crtname:
            crtname = ctypes.util.find_library("msvcrt")

@stevenengler
Copy link

stevenengler commented Jul 26, 2016

There's a small typo on the Windows version. Change line 106 to:

self._outhdl = crtlib._get_osfhandle(self.stream.fileno())

Edit: Thanks, this is now fixed in the latest version.

@senser
Copy link

senser commented Feb 14, 2017

in linux color escapes are redirected to file too. Here's a fix:

  1. add new property:
@property
def is_tty(self):
    isatty = getattr(self.stream, 'isatty', None)
    return isatty and isatty()
  1. modify 'format' method:
    if self.is_tty:
        return color + text + self.DEFAULT
    else:
        return text

thanks to plumberjack

@kavvkon
Copy link

kavvkon commented Oct 29, 2018

The following line fails from jupyter notebook or ipython in windows:

Line 106: self._outhdl = crtlib._get_osfhandle(self.stream.fileno())

In this case IOStream object has no fileno method

C:\Program Files (x86)\Anaconda2\envs\py36\lib\logging\config.py in configure(self)
    557                     try:
--> 558                         handler = self.configure_handler(handlers[name])
    559                         handler.name = name

C:\Program Files (x86)\Anaconda2\envs\py36\lib\logging\config.py in configure_handler(self, config)
    730         try:
--> 731             result = factory(**kwargs)
    732         except TypeError as te:

~\AppData\Local\myproj\misc\colorstreamhandler.py in __init__(self, stream)
    158         crtlib = ctypes.cdll.LoadLibrary(crtname)
--> 159         self._outhdl = crtlib._get_osfhandle(self.stream.fileno())
    160 

UnsupportedOperation: fileno

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment