How can I color Python logging output?
Asked Answered
C

48

542

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?

Cedar answered 21/12, 2008 at 3:57 Comment(10)
You should specify that you want a multiplatform solution - both Linux and Windows.Isom
Related if you use Eclipse/PyDev: Colorize logs in eclipse consolePurvey
Perhaps you can also use colorlogAldrich
run pip install ipython and add alias python="ipython" to your shell startup script (e.g. ~/.bashrc for bash shell)Phooey
You may also try chromalog which I wrote to support all operating systems and Python versions (2.7 and 3.*)Gaziantep
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...Unmake
FriendlyLog (github.com/SebiSebi/friendlylog) is another alternative. It works with Python 2 & 3 under Linux, Windows and MacOS.Insubordinate
@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).Dorking
you don't even need packages to accomplish that, check out https://mcmap.net/q/25734/-how-can-i-color-python-logging-outputNatatorium
if you want to enable color only when stdout is a terminal: #1077613Pyelitis
N
358

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/

Natatorium answered 9/7, 2019 at 1:24 Comment(18)
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)←[0mAngelinaangeline
I liked this answer so much that I made a repo for it, with a few increments and a cheat sheet of ansi colors.Dishonor
@Angelinaangeline where do you run it? IDE console? windows terminal?Natatorium
@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 platformsNatatorium
@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 Angelinaangeline
@Angelinaangeline glad it worked for you. I will try to revise the answer to support windows OSNatatorium
Hi, I ran this on Ubuntu and works. The only thing is that this add double underline in my terminal, any thoughts on this?Frontolysis
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.Frontolysis
@DarrenChristopher is this solution you mentioned for windows?Natatorium
@SergeyPleshakov Tested on Ubuntu. Let me know if that doesn't work on Windows.Frontolysis
@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.Margrettmarguerie
It works on Linux, but on Windows I suggest colorama other than \x1b[??;21m.Hindgut
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.Ahab
DOESN'T WORK ON WINDOWS PLEASE MAKE THIS BOLD AND TOPBerke
How to change color of logging.exception it doesn't work to change the color.Maryannemarybella
See here all colors codes: talyian.github.io/ansicolorsGreasepaint
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.Acherman
Sergey, @Berke 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.Heighten
C
229

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

Cedar answered 21/12, 2008 at 5:17 Comment(7)
Where is YELLOW, WHITE, BLUE, etc. defined?Japheth
@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.htmlChagres
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.Bedim
@Vinay -- could you provide more details on how to implement this?Kiesha
@simon: plumberjack.blogspot.co.uk/2010/12/…Bedim
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.Dorrisdorry
@Cedar 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.Stout
A
210

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
Anikaanil answered 31/5, 2013 at 0:16 Comment(17)
funny enough, i was just going to add a link to "pypi.python.org/pypi/coloredlogs/0.4.7" in this thread!Phenomenalism
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.Palomo
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.Satisfactory
@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...Anikaanil
I expected either magic for (1), or (more reasonably) a way to tell coloredlogs.install which format to use, as in the colorlog package.Satisfactory
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.Glossematics
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.Anikaanil
If you want to use it in conjunction with the tensorflow logger simply use coloredlogs.install(logger=tf.logging._get_logger())Hydracid
easy to use.. but not able to save the logs to a file.. any workaround?Quipu
Can we also write it into a file?Cyclometer
Very well done!! and as of today it works in Windows like a dream :)Zenia
Installed, tested, doesn't work, uninstalled.Selfcontrol
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?Hollander
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...Hollander
How do you change the colours?Berke
"found a collection of code snippets that people are copy/pasting :-(" Personally I'd much rather copy and paste 10 lines of code into my project than include an additional dependency for 10 lines of code. And you're also including additional dependencies which makes it even worse. I much prefer code with 0 dependencies beyond the stdlibWyant
Looks interesting, but the project seems dead since 2021. github.com/xolox/python-coloredlogsSarita
C
116

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

Cling answered 30/5, 2014 at 23:39 Comment(4)
Great answer; +1. The code example could be trimmed though (are three calls to setLevel really needed?)Satisfactory
I was hoping I'd find an answer like this if I waded through the answers long enough. ☺ I hope @Cedar will consider making this the accepted answer, so future work-smart people can find what seems to be the best library with optimal laziness. 😉Glossematics
I just upvoted this for the messages examples of the OUTPUT ^^Benzocaine
Thanks! Really useful and worked for me like a charm!Afore
S
96

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))
Stadia answered 3/11, 2011 at 13:31 Comment(6)
@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 btwChampion
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.Loriloria
Most simple and cleanest solution to apply and understand.Jaban
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_codePrecincts
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.Chest
I'd say, the BEST!Tetrad
I
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())
Isom answered 26/8, 2009 at 18:29 Comment(6)
I wrote a StreamHandler class based on this, see gist.github.com/mooware/a1ed40987b6cc9ab9c65.Crosseye
this worked for me! line 90: should be args[1].msg = color + str(args[1].msg) + '\x1b[0m' # normal.Kirk
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.Clarkia
+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.Haubergeon
This results in color codes at the beginning and end of the log messages in actual log files.Regenerator
Can I keep the message string as normal color and only color the date, filename linenumber section on the left hand side?Cyclometer
V
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

Varese answered 29/9, 2017 at 4:56 Comment(2)
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)Ingeingeberg
How can I make the print() statement colorful?Palm
A
23

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

Antihero answered 22/8, 2021 at 3:12 Comment(6)
Or just do python -m rich.logging to see examples for different use cases :-)Ozonide
I tried, colored on DOS box only, all pink on jupyternotebook or jupyterlab.Vorous
@Vorous 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.Antihero
Oh, this is great. Have used rich but had no idea about this logging integration, thanks 🙏Arman
Can it do file logging too?Thimbleweed
One warning about Rich logging: all Rich output is unbuffered. While likely not a performance issue for the terminal applications that Rich was intended for, my team's profiling has shown that constant flushing to the terminal of our logs accounts for 3/4 of the runtime of many of our API endpoints in development. Rich isn't well suited to log formatting.Carducci
K
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

Krakau answered 18/6, 2016 at 9:5 Comment(2)
Also can use colorlog.basicConfig instead of logging.basicConfig which has some good defaultsFranz
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)Madison
M
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)
Mechanician answered 5/2, 2010 at 8:36 Comment(1)
Does this need any other code to use the ColoredConsoleHandler? Or does just defining a subclass of logging.StreamHandler magically register it somehow?Unmake
P
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
Pharmaceutical answered 28/3, 2010 at 12:49 Comment(1)
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.7Subreption
M
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

Meimeibers answered 14/1, 2011 at 13:52 Comment(1)
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.Hemline
H
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.

Highball answered 20/12, 2013 at 8:8 Comment(1)
Note that this answer is old as Rome and I recommend ColoredLogs by Peter Odding insteadHighball
P
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

Pskov answered 11/8, 2021 at 17:24 Comment(4)
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.Berke
@Berke After installing coloredlogs, the code from minimal usage should show you colors. If not maybye your command line program don't support colors.Pskov
It shows colours ok, I just don't seem to be able to figure out how to change them.Berke
To me this is the smartes and simplest solution.Perlite
Q
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
Quiet answered 25/5, 2020 at 5:59 Comment(0)
O
9

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
Overlord answered 13/12, 2020 at 22:46 Comment(0)
C
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)
Confirmand answered 19/4, 2010 at 11:1 Comment(0)
U
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.

Unmake answered 19/8, 2015 at 10:45 Comment(0)
S
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)
Showman answered 22/11, 2017 at 13:22 Comment(0)
S
7

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

Soutine answered 5/8, 2022 at 18:44 Comment(0)
G
6

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

Garrison answered 8/5, 2021 at 8:37 Comment(0)
F
6

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

Fosdick answered 21/1, 2022 at 3:35 Comment(1)
It's probably the only one that works in Python 2.7 too!Kalevala
A
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)))`
Amaty answered 13/8, 2015 at 14:52 Comment(1)
+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.Haubergeon
C
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

Cortese answered 29/4, 2020 at 20:3 Comment(0)
W
3

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

Windowpane answered 3/9, 2023 at 13:41 Comment(0)
M
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])
Motorbus answered 3/10, 2014 at 17:10 Comment(0)
I
2

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

Insubordinate answered 6/10, 2019 at 16:43 Comment(1)
Looking forward to the new PR to reduce module path clutterBarbicel
C
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)

Chest answered 21/1, 2020 at 8:13 Comment(0)
H
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]
)
Holliman answered 11/2, 2022 at 14:19 Comment(3)
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, } Cockhorse
Not really, @jaywailking, I was intentionally trying not to color the whole line, but only the levelname part.Holliman
Yes, it makes more sense to color only to the levels, thanks for the code.Oculomotor
M
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
Meimeibers answered 12/1, 2011 at 18:7 Comment(2)
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.Meimeibers
See the "plumber jack" solution. I think this is a better way to solve the problem (i.e. the handler should do the colourisation). #384576Meimeibers
S
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).

Stenographer answered 18/5, 2013 at 23:34 Comment(0)
C
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.

Crispate answered 29/4, 2015 at 16:3 Comment(4)
when i use it, messages are printed twice. do you know why?Microphysics
@ 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?Crispate
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.Microphysics
@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.Crispate
U
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
Urrutia answered 4/6, 2015 at 16:56 Comment(0)
J
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")

Jeweller answered 28/2, 2017 at 20:23 Comment(1)
For console you can leave out filename or simply filename='' should work. modify basicConfig to include other properties like file number, module ..Jeweller
S
1

This is an Enum containing the colour codes:

class TerminalColour:
    """
    Terminal colour formatting codes
    """
    # https://mcmap.net/q/25739/-how-do-i-print-colored-text-to-the-terminal
    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'
        },
Subtype answered 19/2, 2018 at 14:23 Comment(0)
U
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 
Unchaste answered 29/8, 2020 at 15:29 Comment(1)
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…Poulter
C
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!

Carin answered 22/2, 2022 at 3:31 Comment(0)
M
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")
Misgive answered 11/5, 2022 at 14:8 Comment(0)
G
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")
Gambier answered 29/6, 2022 at 17:32 Comment(0)
F
1

Just the simplest easy solution. Google ANSI escape codes for other colors.

black = "\u001b[30m"
red = "\u001b[31m"
green = "\u001b[32m"
yellow = "\u001b[33m"
blue = "\u001b[34m"
magenta = "\u001b[35m"
cyan = "\u001b[36m"
white = "\u001b[37m"

print(magenta + "Your message ")
Fredfreda answered 14/12, 2023 at 19:16 Comment(0)
P
0

Use pyfancy.

Example:

print(pyfancy.RED + "Hello Red!" + pyfancy.END)
Pigling answered 1/2, 2015 at 18:16 Comment(1)
The question was to tweak the logging functionality to use a separate coloring library.Francesfrancesca
M
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.

Muezzin answered 29/7, 2017 at 22:5 Comment(0)
G
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")})
Grissom answered 21/12, 2021 at 15:18 Comment(1)
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.Leonhard
F
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()])
Flowerlike answered 30/4, 2022 at 8:53 Comment(0)
S
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())
Shieh answered 5/1, 2023 at 11:7 Comment(0)
R
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")
Rooks answered 3/2, 2023 at 18:35 Comment(0)
W
0

This doesn't exactly answer the question (because it doesn't use the python logger), but because I see so many answers that unnecessarily add dependencies on someone's one off package, I figured it's worthwhile to post it. I use a variation of this code in many languages when I want quick colorized logging. It also supports nesting colors. For example, you can do

print(error_log(f"ERROR: There was a problem connecting to {cyan(hostname)}")

and "hostname" will be printed in cyan and the rest printed in red.

Note that you can add any additional text (fg) colors by adding the appropriate ansi sequences.

Here is the code which is copied and pasted into many of my projects (I'm allergic to dependencies):

# Python 3
# fmt: off
def color_code(code): return f"\x1b[{code}m"
def colorize(code: int, s: str) -> str: return f"{color_code(code)}{str(s).replace(color_code(0), color_code(code))}{color_code(0)}"
def green(s: str) -> str: return colorize(32, s)
def yellow(s: str) -> str: return colorize(33, s)
def red(s: str) -> str: return colorize(31, s)
def cyan(s: str) -> str: return colorize(36, s)
def magenta(s: str) -> str: return colorize(35, s)
def bold(s: str) -> str: return colorize(1, s)
def info_log(*strs: str) -> None:
    for s in strs: print(yellow(s))
def warning_log(*strs: str) -> None:
    for s in strs: print(bold(yellow(s)))
def success_log(*strs: str) -> None:
    for s in strs: print(green(s))
def error_log(*strs: str) -> None:
    for s in strs: print(red(s))
# fmt: on

The fmt comments ensure black won't expand this into a big mess.

EDIT: For anyone still using python2, here's code for that

# Python 2
def color_code(code): return '\x1b[%sm' % code
def colorize(code, s): return '%s%s%s' % (color_code(code), str(s).replace(color_code(0), color_code(code)), color_code(0))
def green(s): return colorize(32, s)
def yellow(s): return colorize(33, s)
def red(s): return colorize(31, s)
def cyan(s): return colorize(36, s)
def magenta(s): return colorize(35, s)

def info_log(s): print yellow(s)
def success_log(s): print green(s)
def error_log(s): print red(s)

What about background colors and decorations (bold, italic, etc)

It does not currently support bg, fg, or decorations (bold, italic, blink, etc), but I have that partially implemented in zsh (it's more complex because you need to persist styles which requires recursion/more logic) here: https://github.com/brandon-fryslie/rad-shell/blob/master/init-plugin/init-plugin.plugin.zsh#L30

All my code is explicitly MIT licensed so people do with it as you wish. Code posted to Stackoverflow is NOT licensed MIT, but this code is also on GitHub with an MIT license so please use it if you wish.

Wyant answered 1/12, 2023 at 21:44 Comment(0)
A
0

Simplest Possible - No Python - Linux Only Solution

This assumes grep uses --color. Here it omits INFO and DEBUG. The words fed to egrep will be in color. The $ is required to include non-matching lines with no color.

$ tail -f my-app.log | egrep 'WARNING|ERROR|CRITICAL|$'

I know it does not exactly answer the OP's Python logging question...

Annulose answered 6/12, 2023 at 14:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.