You can do this with the code below, and the code in your question was actually very close to what you needed, all you have to do is call the cmap object you have.
For values outside of the range [0.0, 1.0] it will return the under and over colour (respectively). This, by default, is the minimum and maximum colour within the range (so 0.0 and 1.0). This default can be changed with cmap.set_under() and cmap.set_over().
For “special” numbers such as np.nan and np.inf the default is to use the 0.0 value, this can be changed using cmap.set_bad() similarly to under and over as above.
Finally it may be necessary for you to normalize your data such that it conforms to the range [0.0, 1.0]. This can be done using matplotlib.colors.Normalize simply as shown in the small example below where the arguments vmin and vmax describe what numbers should be mapped to 0.0 and 1.0 respectively.
A logarithmic normaliser (matplotlib.colors.LogNorm) is also available for data ranges with a large range of values.
(Thanks to both Joe Kington and tcaswell for suggestions on how to improve the answer.)
回答 1
为了获得rgba整数值而不是float值,我们可以
rgba = cmap(0.5,bytes=True)
因此,为了简化基于Ffisegydd的答案的代码,代码将如下所示:
#import colormapfrom matplotlib import cm
#normalize item number values to colormap
norm = matplotlib.colors.Normalize(vmin=0, vmax=1000)#colormap possible values = viridis, jet, spectral
rgba_color = cm.jet(norm(400),bytes=True)#400 is one of value between 0 and 1000
In order to get rgba integer value instead of float value, we can do
rgba = cmap(0.5,bytes=True)
So to simplify the code based on answer from Ffisegydd, the code would be like this:
#import colormap
from matplotlib import cm
#normalize item number values to colormap
norm = matplotlib.colors.Normalize(vmin=0, vmax=1000)
#colormap possible values = viridis, jet, spectral
rgba_color = cm.jet(norm(400),bytes=True)
#400 is one of value between 0 and 1000
If you have several figures or subplots that you want to modify, it can be helpful to use the matplotlib context manager to change the color, instead of changing each one individually. The context manager allows you to temporarily change the rc parameters only for the immediately following indented code, but does not affect the global rc parameters.
This snippet yields two figures, the first one with modified colors for the axis, ticks and ticklabels, and the second one with the default rc parameters.
import matplotlib.pyplot as plt
with plt.rc_context({'axes.edgecolor':'orange', 'xtick.color':'red', 'ytick.color':'green', 'figure.facecolor':'white'}):
# Temporary rc parameters in effect
fig, (ax1, ax2) = plt.subplots(1,2)
ax1.plot(range(10))
ax2.plot(range(10))
# Back to default rc parameters
fig, ax = plt.subplots()
ax.plot(range(10))
You can type plt.rcParams to view all available rc parameters, and use list comprehension to search for keywords:
# Search for all parameters containing the word 'color'
[(param, value) for param, value in plt.rcParams.items() if 'color' in param]
import matplotlib.pyplot as plt
import numpy as np
num_plots =20# Have a look at the colormaps here and decide which one you'd like:# http://matplotlib.org/1.2.1/examples/pylab_examples/show_colormaps.html
colormap = plt.cm.gist_ncar
plt.gca().set_prop_cycle(plt.cycler('color', plt.cm.jet(np.linspace(0,1, num_plots))))# Plot several different functions...
x = np.arange(10)
labels =[]for i in range(1, num_plots +1):
plt.plot(x, i * x +5* i)
labels.append(r'$y = %ix + %i$'%(i,5*i))# I'm basically just demonstrating several different legend options here...
plt.legend(labels, ncol=4, loc='upper center',
bbox_to_anchor=[0.5,1.1],
columnspacing=1.0, labelspacing=0.0,
handletextpad=0.0, handlelength=1.5,
fancybox=True, shadow=True)
plt.show()
First off, if you have a lot (>5) of things you want to plot on one figure, either:
Put them on different plots (consider using a few subplots on one figure), or
Use something other than color (i.e. marker styles or line thickness) to distinguish between them.
Otherwise, you’re going to wind up with a very messy plot! Be nice to who ever is going to read whatever you’re doing and don’t try to cram 15 different things onto one figure!!
Beyond that, many people are colorblind to varying degrees, and distinguishing between numerous subtly different colors is difficult for more people than you may realize.
That having been said, if you really want to put 20 lines on one axis with 20 relatively distinct colors, here’s one way to do it:
import matplotlib.pyplot as plt
import numpy as np
num_plots = 20
# Have a look at the colormaps here and decide which one you'd like:
# http://matplotlib.org/1.2.1/examples/pylab_examples/show_colormaps.html
colormap = plt.cm.gist_ncar
plt.gca().set_prop_cycle(plt.cycler('color', plt.cm.jet(np.linspace(0, 1, num_plots))))
# Plot several different functions...
x = np.arange(10)
labels = []
for i in range(1, num_plots + 1):
plt.plot(x, i * x + 5 * i)
labels.append(r'$y = %ix + %i$' % (i, 5*i))
# I'm basically just demonstrating several different legend options here...
plt.legend(labels, ncol=4, loc='upper center',
bbox_to_anchor=[0.5, 1.1],
columnspacing=1.0, labelspacing=0.0,
handletextpad=0.0, handlelength=1.5,
fancybox=True, shadow=True)
plt.show()
import matplotlib.pyplot as plt
import numpy as np
fig1 = plt.figure()
ax1 = fig1.add_subplot(111)for i in range(1,15):
ax1.plot(np.array([1,5])*i,label=i)
您需要的一段代码:
colormap = plt.cm.gist_ncar #nipy_spectral, Set1,Paired
colors =[colormap(i)for i in np.linspace(0,1,len(ax1.lines))]for i,j in enumerate(ax1.lines):
j.set_color(colors[i])
ax1.legend(loc=2)
If you don’t know the number of the plots you are going to plot you can change the colours once you have plotted them retrieving the number directly from the plot using .lines, I use this solution:
Some random data
import matplotlib.pyplot as plt
import numpy as np
fig1 = plt.figure()
ax1 = fig1.add_subplot(111)
for i in range(1,15):
ax1.plot(np.array([1,5])*i,label=i)
The piece of code that you need:
colormap = plt.cm.gist_ncar #nipy_spectral, Set1,Paired
colors = [colormap(i) for i in np.linspace(0, 1,len(ax1.lines))]
for i,j in enumerate(ax1.lines):
j.set_color(colors[i])
ax1.legend(loc=2)
The result is the following:
回答 2
TL; DR不,它不能自动完成。是的,有可能。
import matplotlib.pyplot as plt
my_colors = plt.rcParams['axes.prop_cycle']()# <<< note that we CALL the prop_cycle
fig, axes = plt.subplots(2,3)for ax in axes.flatten(): ax.plot((0,1),(0,1),**next(my_colors))
TL;DR No, it can’t be done automatically. Yes, it is possible.
import matplotlib.pyplot as plt
my_colors = plt.rcParams['axes.prop_cycle']() # <<< note that we CALL the prop_cycle
fig, axes = plt.subplots(2,3)
for ax in axes.flatten(): ax.plot((0,1), (0,1), **next(my_colors))
Each plot (axes) in a figure (figure) has its own cycle of colors — if you don’t force a different color for each plot, all the plots share the same order of colors but, if we stretch a bit what “automatically” means, it can be done.
The OP wrote
[…] I have to identify each plot with a different color which should be automatically generated by [Matplotlib].
But… Matplotlib automatically generates different colors for each different curve
In [10]: import numpy as np
...: import matplotlib.pyplot as plt
In [11]: plt.plot((0,1), (0,1), (1,2), (1,0));
Out[11]:
So why the OP request? If we continue to read, we have
Can you please give me a method to put different colors for different plots in the same figure?
and it make sense, because each plot (each axes in Matplotlib’s parlance) has its own color_cycle (or rather, in 2018, its prop_cycle) and each plot (axes) reuses the same colors in the same order.
In [12]: fig, axes = plt.subplots(2,3)
In [13]: for ax in axes.flatten():
...: ax.plot((0,1), (0,1))
If this is the meaning of the original question, one possibility is to explicitly name a different color for each plot.
If the plots (as it often happens) are generated in a loop we must have an additional loop variable to override the color automatically chosen by Matplotlib.
In [14]: fig, axes = plt.subplots(2,3)
In [15]: for ax, short_color_name in zip(axes.flatten(), 'brgkyc'):
...: ax.plot((0,1), (0,1), short_color_name)
Another possibility is to instantiate a cycler object
from cycler import cycler
my_cycler = cycler('color', ['k', 'r']) * cycler('linewidth', [1., 1.5, 2.])
actual_cycler = my_cycler()
fig, axes = plt.subplots(2,3)
for ax in axes.flat:
ax.plot((0,1), (0,1), **next(actual_cycler))
Note that type(my_cycler) is cycler.Cycler but type(actual_cycler) is itertools.cycle.
...# Plot several different functions...
labels =[]
plotHandles =[]for i in range(1, num_plots +1):
x,= plt.plot(some x vector, some y vector)#need the ',' per ** below
plotHandles.append(x)
labels.append(some label)
plt.legend(plotHandles, labels,'upper left',ncol=1)
I would like to offer a minor improvement on the last loop answer given in the previous post (that post is correct and should still be accepted). The implicit assumption made when labeling the last example is that plt.label(LIST) puts label number X in LIST with the line corresponding to the Xth time plot was called. I have run into problems with this approach before. The recommended way to build legends and customize their labels per matplotlibs documentation ( http://matplotlib.org/users/legend_guide.html#adjusting-the-order-of-legend-item) is to have a warm feeling that the labels go along with the exact plots you think they do:
...
# Plot several different functions...
labels = []
plotHandles = []
for i in range(1, num_plots + 1):
x, = plt.plot(some x vector, some y vector) #need the ',' per ** below
plotHandles.append(x)
labels.append(some label)
plt.legend(plotHandles, labels, 'upper left',ncol=1)
What named colors are available in matplotlib for use in plots? I can find a list on the matplotlib documentation that claims that these are the only names:
b: blue
g: green
r: red
c: cyan
m: magenta
y: yellow
k: black
w: white
However, I’ve found that these colors can also be used, at least in this context:
import matplotlib.pyplot as plt
from matplotlib import colors as mcolors
colors = dict(mcolors.BASE_COLORS,**mcolors.CSS4_COLORS)# Sort colors by hue, saturation, value and name.
by_hsv = sorted((tuple(mcolors.rgb_to_hsv(mcolors.to_rgba(color)[:3])), name)for name, color in colors.items())
sorted_names =[name for hsv, name in by_hsv]
n = len(sorted_names)
ncols =4
nrows = n // ncols
fig, ax = plt.subplots(figsize=(12,10))# Get height and width
X, Y = fig.get_dpi()* fig.get_size_inches()
h = Y /(nrows +1)
w = X / ncols
for i, name in enumerate(sorted_names):
row = i % nrows
col = i // nrows
y = Y -(row * h)- h
xi_line = w *(col +0.05)
xf_line = w *(col +0.25)
xi_text = w *(col +0.3)
ax.text(xi_text, y, name, fontsize=(h *0.8),
horizontalalignment='left',
verticalalignment='center')
ax.hlines(y + h *0.1, xi_line, xf_line,
color=colors[name], linewidth=(h *0.8))
ax.set_xlim(0, X)
ax.set_ylim(0, Y)
ax.set_axis_off()
fig.subplots_adjust(left=0, right=1,
top=1, bottom=0,
hspace=0, wspace=0)
plt.show()
I constantly forget the names of the colors I want to use and keep coming back to this question =)
The previous answers are great, but I find it a bit difficult to get an overview of the available colors from the posted image. I prefer the colors to be grouped with similar colors, so I slightly tweaked the matplotlib answer that was mentioned in a comment above to get a color list sorted in columns. The order is not identical to how I would sort by eye, but I think it gives a good overview.
I updated the image and code to reflect that ‘rebeccapurple’ has been added and the three sage colors have been moved under the ‘xkcd:’ prefix since I posted this answer originally.
I really didn’t change much from the matplotlib example, but here is the code for completeness.
import matplotlib.pyplot as plt
from matplotlib import colors as mcolors
colors = dict(mcolors.BASE_COLORS, **mcolors.CSS4_COLORS)
# Sort colors by hue, saturation, value and name.
by_hsv = sorted((tuple(mcolors.rgb_to_hsv(mcolors.to_rgba(color)[:3])), name)
for name, color in colors.items())
sorted_names = [name for hsv, name in by_hsv]
n = len(sorted_names)
ncols = 4
nrows = n // ncols
fig, ax = plt.subplots(figsize=(12, 10))
# Get height and width
X, Y = fig.get_dpi() * fig.get_size_inches()
h = Y / (nrows + 1)
w = X / ncols
for i, name in enumerate(sorted_names):
row = i % nrows
col = i // nrows
y = Y - (row * h) - h
xi_line = w * (col + 0.05)
xf_line = w * (col + 0.25)
xi_text = w * (col + 0.3)
ax.text(xi_text, y, name, fontsize=(h * 0.8),
horizontalalignment='left',
verticalalignment='center')
ax.hlines(y + h * 0.1, xi_line, xf_line,
color=colors[name], linewidth=(h * 0.8))
ax.set_xlim(0, X)
ax.set_ylim(0, Y)
ax.set_axis_off()
fig.subplots_adjust(left=0, right=1,
top=1, bottom=0,
hspace=0, wspace=0)
plt.show()
Additional named colors
Updated 2017-10-25. I merged my previous updates into this section.
xkcd
If you would like to use additional named colors when plotting with matplotlib, you can use the xkcd crowdsourced color names, via the ‘xkcd:’ prefix:
plt.plot([1,2], lw=4, c='xkcd:baby poop green')
Now you have access to a plethora of named colors!
Tableau
The default Tableau colors are available in matplotlib via the ‘tab:’ prefix:
This is more similar to specifying and RGB tuple rather than a named color (apart from the fact that the hex code is passed as a string), and I will not include an image of the 16 million colors you can choose from…
# python2:import matplotlib
for name, hex in matplotlib.colors.cnames.iteritems():print(name, hex)# python3:import matplotlib
for name, hex in matplotlib.colors.cnames.items():print(name, hex)
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.colors as colors
import math
fig = plt.figure()
ax = fig.add_subplot(111)
ratio =1.0/3.0
count = math.ceil(math.sqrt(len(colors.cnames)))
x_count = count * ratio
y_count = count / ratio
x =0
y =0
w =1/ x_count
h =1/ y_count
for c in colors.cnames:
pos =(x / x_count, y / y_count)
ax.add_patch(patches.Rectangle(pos, w, h, color=c))
ax.annotate(c, xy=pos)if y >= y_count-1:
x +=1
y =0else:
y +=1
plt.show()
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.colors as colors
import math
fig = plt.figure()
ax = fig.add_subplot(111)
ratio = 1.0 / 3.0
count = math.ceil(math.sqrt(len(colors.cnames)))
x_count = count * ratio
y_count = count / ratio
x = 0
y = 0
w = 1 / x_count
h = 1 / y_count
for c in colors.cnames:
pos = (x / x_count, y / y_count)
ax.add_patch(patches.Rectangle(pos, w, h, color=c))
ax.annotate(c, xy=pos)
if y >= y_count-1:
x += 1
y = 0
else:
y += 1
plt.show()
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?
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
}classColoredFormatter(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)
并使用它,创建自己的Logger:
# Custom logger class with multiple destinationsclassColoredLogger(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)
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).
回答 1
多年前,我写了一个彩色的流处理程序供自己使用。然后我浏览了此页面,发现了人们正在复制/粘贴的代码片段集合:-(。我的流处理程序当前仅在UNIX(Linux,Mac OS X)上可用,但优点是可以在PyPI(和GitHub)上使用),而且使用起来非常简单,还具有Vim语法模式:-)。将来我可能会将其扩展到Windows上。
要安装软件包:
$ pip install coloredlogs
确认其有效:
$ coloredlogs --demo
要开始使用自己的代码:
$ python
>import coloredlogs, logging
> coloredlogs.install()> logging.info("It works!")2014-07-3021:21:26 peter-macbook root[7471] INFO It works!
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:
# Usage: add Colorer.py near you script and import it.import logging
importColorer
logging.warn("a warning")
logging.error("some error")
logging.info("some info")
着色器
#!/usr/bin/env python# encoding: utf-8import logging
# now we patch Python code to add color support to logging.StreamHandlerdef add_coloring_to_emit_windows(fn):# add methods we need to the classdef _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 classdef new(*args):
levelno = args[1].levelno
if(levelno>=50):
color ='\x1b[31m'# redelif(levelno>=40):
color ='\x1b[31m'# redelif(levelno>=30):
color ='\x1b[33m'# yellowelif(levelno>=20):
color ='\x1b[32m'# green elif(levelno>=10):
color ='\x1b[35m'# pinkelse:
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())
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())
import logging
LOG_LEVEL = logging.DEBUG
LOGFORMAT =" %(log_color)s%(levelname)-8s%(reset)s | %(log_color)s%(message)s%(reset)s"from colorlog importColoredFormatter
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")
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
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")
#!/usr/bin/env pythonfrom copy import copy
from logging importFormatter
MAPPING ={'DEBUG':37,# white'INFO':36,# cyan'WARNING':33,# yellow'ERROR':31,# red'CRITICAL':41,# white on red bg}
PREFIX ='\033['
SUFFIX ='\033[0m'classColoredFormatter(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
returnFormatter.format(self, colored_record)
用法示例
app.py
#!/usr/bin/env pythonimport logging
from colored_log importColoredFormatter# 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 pythonimport logging
log = logging.getLogger('main.sub_module')
log.debug("Hello from the sub module")
结果
终端输出
app.log内容
2017-09-2900:32:23,434- main - DEBUG - app has started
2017-09-2900:32:23,434- main - INFO -Logging to 'app.log'in the script dir
2017-09-2900:32:23,435- main - WARNING -Thisis my last warning, take heed
2017-09-2900:32:23,435- main - ERROR -Thisis an error
2017-09-2900: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
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
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. :)
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.
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.
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).
import copy
import logging
classColoredConsoleHandler(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'# redelif(levelno >=40):# ERROR
color ='\x1b[31m'# redelif(levelno >=30):# WARNING
color ='\x1b[33m'# yellowelif(levelno >=20):# INFO
color ='\x1b[32m'# greenelif(levelno >=10):# DEBUG
color ='\x1b[35m'# pinkelse:# NOTSET and anything else
color ='\x1b[0m'# normal
myrecord.msg = color + str(myrecord.msg)+'\x1b[0m'# normal
logging.StreamHandler.emit(self, myrecord)
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)
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)
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)
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:
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.
classColouredFormatter(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)
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)
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.
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.
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.
The idea is to use the clint library. Which has support for MAC, Linux and Windows shells (CLI).
回答 21
这是一个包含颜色代码的枚举:
classTerminalColour:"""
Terminal colour formatting codes
"""# /programming/287871/print-in-terminal-with-colors
MAGENTA ='\033[95m'
BLUE ='\033[94m'
GREEN ='\033[92m'
YELLOW ='\033[93m'
RED ='\033[91m'
GREY ='\033[0m'# normal
WHITE ='\033[1m'# bright white
UNDERLINE ='\033[4m'
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)
输出:
实现方式:
"""
colargulog - Python3 Logging with Colored Arguments and new string formatting style
Written by david.ohana@ibm.com
License: Apache-2.0
"""import logging
import logging.handlers
import re
classColorCodes:
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"classColorizedArgsFormatter(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 inColorizedArgsFormatter.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)@staticmethoddef rewrite_record(record: logging.LogRecord):ifnotBraceFormatStyleFormatter.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.whileTrue:if"_{{"notin 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
classBraceFormatStyleFormatter(logging.Formatter):def __init__(self, fmt: str):
super().__init__()
self.formatter = logging.Formatter(fmt)@staticmethoddef is_brace_format_style(record: logging.LogRecord):if len(record.args)==0:returnFalse
msg = record.msg
if'%'in msg:returnFalse
count_of_start_param = msg.count("{")
count_of_end_param = msg.count("}")if count_of_start_param != count_of_end_param:returnFalseif count_of_start_param != len(record.args):returnFalsereturnTrue@staticmethoddef rewrite_record(record: logging.LogRecord):ifnotBraceFormatStyleFormatter.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
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. ("{}").
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:
Implementation:
"""
colargulog - Python3 Logging with Colored Arguments and new string formatting style
Written by david.ohana@ibm.com
License: Apache-2.0
"""
import logging
import logging.handlers
import re
class ColorCodes:
grey = "\x1b[38;21m"
green = "\x1b[1;32m"
yellow = "\x1b[33;21m"
red = "\x1b[31;21m"
bold_red = "\x1b[31;1m"
blue = "\x1b[1;34m"
light_blue = "\x1b[1;36m"
purple = "\x1b[1;35m"
reset = "\x1b[0m"
class ColorizedArgsFormatter(logging.Formatter):
arg_colors = [ColorCodes.purple, ColorCodes.light_blue]
level_fields = ["levelname", "levelno"]
level_to_color = {
logging.DEBUG: ColorCodes.grey,
logging.INFO: ColorCodes.green,
logging.WARNING: ColorCodes.yellow,
logging.ERROR: ColorCodes.red,
logging.CRITICAL: ColorCodes.bold_red,
}
def __init__(self, fmt: str):
super().__init__()
self.level_to_formatter = {}
def add_color_format(level: int):
color = ColorizedArgsFormatter.level_to_color[level]
_format = fmt
for fld in ColorizedArgsFormatter.level_fields:
search = "(%\(" + fld + "\).*?s)"
_format = re.sub(search, f"{color}\\1{ColorCodes.reset}", _format)
formatter = logging.Formatter(_format)
self.level_to_formatter[level] = formatter
add_color_format(logging.DEBUG)
add_color_format(logging.INFO)
add_color_format(logging.WARNING)
add_color_format(logging.ERROR)
add_color_format(logging.CRITICAL)
@staticmethod
def rewrite_record(record: logging.LogRecord):
if not BraceFormatStyleFormatter.is_brace_format_style(record):
return
msg = record.msg
msg = msg.replace("{", "_{{")
msg = msg.replace("}", "_}}")
placeholder_count = 0
# add ANSI escape code for next alternating color before each formatting parameter
# and reset color after it.
while True:
if "_{{" not in msg:
break
color_index = placeholder_count % len(ColorizedArgsFormatter.arg_colors)
color = ColorizedArgsFormatter.arg_colors[color_index]
msg = msg.replace("_{{", color + "{", 1)
msg = msg.replace("_}}", "}" + ColorCodes.reset, 1)
placeholder_count += 1
record.msg = msg.format(*record.args)
record.args = []
def format(self, record):
orig_msg = record.msg
orig_args = record.args
formatter = self.level_to_formatter.get(record.levelno)
self.rewrite_record(record)
formatted = formatter.format(record)
# restore log record to original state for other handlers
record.msg = orig_msg
record.args = orig_args
return formatted
class BraceFormatStyleFormatter(logging.Formatter):
def __init__(self, fmt: str):
super().__init__()
self.formatter = logging.Formatter(fmt)
@staticmethod
def is_brace_format_style(record: logging.LogRecord):
if len(record.args) == 0:
return False
msg = record.msg
if '%' in msg:
return False
count_of_start_param = msg.count("{")
count_of_end_param = msg.count("}")
if count_of_start_param != count_of_end_param:
return False
if count_of_start_param != len(record.args):
return False
return True
@staticmethod
def rewrite_record(record: logging.LogRecord):
if not BraceFormatStyleFormatter.is_brace_format_style(record):
return
record.msg = record.msg.format(*record.args)
record.args = []
def format(self, record):
orig_msg = record.msg
orig_args = record.args
self.rewrite_record(record)
formatted = self.formatter.format(record)
# restore log record to original state for other handlers
record.msg = orig_msg
record.args = orig_args
return formatted
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()
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)
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.