前段时间,我看到一个带有彩色输出的Mono应用程序,可能是因为它的日志系统(因为所有的消息都是标准化的).
现在,Python有了这个logging
模块,它允许你指定很多选项来自定义输出.所以,我想象Python可能会有类似的东西,但我无法在任何地方找到如何做到这一点.
有没有办法让Python logging
模块输出颜色?
我想要的(例如)红色错误,蓝色或黄色调试消息,等等.
当然这可能需要一个兼容的终端(大多数现代终端); 但logging
如果不支持颜色,我可以回退到原始输出.
有关如何使用记录模块获得彩色输出的任何想法?
我已经知道了颜色逃逸,我刚才在我的bash提示中使用过它们.不管怎么说,还是要谢谢你.
我想要的是将它与日志记录模块集成,我最终在经过几次尝试和错误之后做了.
这是我最终得到的:
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)
要使用它,请创建自己的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)
以防万一其他人需要它.
如果您使用多个记录器或处理程序,请小心:ColoredFormatter
正在更改记录对象,该记录对象将进一步传递给其他处理程序或传播到其他记录程序.如果您已配置文件记录器等,则可能不希望在日志文件中包含颜色.为了避免这种情况,最好在返回格式化字符串之前简单地在操作levelname属性之前创建record
with 的副本copy.copy()
,或者将levelname重置为之前的值(在评论中记入Michael).
几年前我写了一个彩色的流处理程序供我自己使用.然后我遇到了这个页面,发现了一些人们正在复制/粘贴的代码片段:-(.我的流处理程序目前只适用于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-30 21:21:26 peter-macbook root[7471] INFO It works!
上例中显示的默认日志格式包含日期,时间,主机名,记录器名称,PID,日志级别和日志消息.这就是它在实践中的样子:
这是一个适用于任何平台的解决方案.如果它不只是告诉我,我会更新它.
工作原理:在支持ANSI转义的平台上使用它们(非Windows),在Windows上,它确实使用API调用来更改控制台颜色.
该脚本会破坏标准库中的logging.StreamHandler.emit方法,为其添加包装器.
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())
更新:因为这是我长期以来一直想要抓住的一个痒,我继续为像我这样只想要简单方法做事的懒人写了一个库:zenlog
Colorlog非常适合这种情况.它可以在PyPI上使用(因此可以安装pip install colorlog
)并且可以主动维护.
这是一个快速复制和粘贴的代码片段,用于设置日志记录和打印体面的日志消息:
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")
输出:
针对预定义日志级别的快速而脏的解决方案,无需定义新类.
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))
好吧,我想我也可以添加我的彩色记录器的变化.
这并不奇怪,但它使用起来非常简单,并且不会更改记录对象,从而避免在使用文件处理程序时将ANSI转义序列记录到日志文件中.它不会影响日志消息格式.
如果您已经在使用日志记录模块的Formatter,那么要获得彩色级别名称,您只需要使用ColoredFormatter替换您的建议处理程序Formatter.如果您要记录整个应用程序,则只需要为顶级记录器执行此操作.
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)
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")
终端输出
app.log内容
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
当然,您可以根据需要设置格式化终端和日志文件输出.只有日志级别才会着色.
我希望有人觉得这很有用,而且不仅仅是太多了.:)
可以从这个GitHub Gist下载Python示例文件:https: //gist.github.com/KurtJacobson/48e750701acec40c7161b5a2f79e6bfd
我从airmind支持前景和背景标签更新了示例.只需在日志格式化程序字符串中使用颜色变量$ BLACK - $ WHITE.设置背景只需使用$ 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
所以现在您可以在配置文件中简单地执行以下操作:
[formatter_colorFormatter] class=logging.ColorFormatter format= $COLOR%(levelname)s $RESET %(asctime)s $BOLD$COLOR%(name)s$RESET %(message)s
您可以导入colorlog模块并将其ColoredFormatter
用于着色日志消息.
主模块的锅炉板:
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__)
如果安装了colorlog模块并且输出实际上发送到终端,则代码仅启用日志消息中的颜色.这样可以避免在重定向日志输出时将转义序列写入文件.
此外,设置自定义颜色方案,更适合具有深色背景的终端.
一些示例记录调用:
log.debug ('Hello Debug') log.info ('Hello Info') log.warn ('Hello Warn') log.error ('Hello Error') log.critical('Hello Critical')
输出:
我将Sorin提供的原始示例和子类化的StreamHandler修改为ColorizedConsoleHandler.
他们的解决方案的缺点是它修改了消息,因为这正在修改实际的logmessage,任何其他处理程序也将获得修改后的消息.
由于我们使用多个记录器,因此在我们的案例中导致了带有颜色代码的日志文件.
下面的类只适用于支持ansi的平台,但是向它添加windows颜色代码应该是微不足道的.
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)
请看以下解决方案.流处理程序应该是着色的东西,然后你可以选择着色单词而不是整行(使用Formatter).
http://plumberjack.blogspot.com/2010/12/colorizing-logging-output-in-terminals.html
现在有一个已发布的PyPi模块可用于可自定义的彩色日志输出:
https://pypi.python.org/pypi/rainbow_logging_handler/
和
https://github.com/laysakura/rainbow_logging_handler
支持Windows
支持Django
可定制的颜色
由于它是作为Python egg分发的,因此很容易为任何Python应用程序安装.
定义一个班级
import logging class CustomFormatter(logging.Formatter): """Logging Formatter to add colors and count warning / errors""" grey = "\x1b[38;21m" yellow = "\x1b[33;21m" red = "\x1b[31;21m" 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)
实例化记录器
# 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)
并使用!
logger.debug("debug message") logger.info("info message") logger.warning("warning message") logger.error("error message") logger.critical("critical message")
结果
有很多回应.但没有人在谈论装饰者.所以这是我的.
因为它更简单.
不需要导入任何东西,也不需要编写任何子类:
#!/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__)
这将错误设置为红色,将调试消息设置为蓝色,等等.就像问题中的问题一样.
我们甚至可以调整包装器来使用color
动态设置消息颜色的参数logger.debug("message", color=GREY)
编辑:所以这里是适应装饰器在运行时设置颜色:
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)
airmind方法的另一个小混音,将所有内容保持在一个类中:
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)
要使用将格式化程序附加到处理程序,例如:
handler.setFormatter(ColorFormatter()) logger.addHandler(handler)
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)))`
用于着色任何终端文本的简单但非常灵活的工具是" colout ".
pip install colout myprocess | colout REGEX_WITH_GROUPS color1,color2...
'myprocess'输出中与正则表达式组1匹配的任何文本将使用color1,第2组使用color2等进行着色.
例如:
tail -f /var/log/mylogfile | colout '^(\w+ \d+ [\d:]+)|(\w+\.py:\d+ .+\(\)): (.+)$' white,black,cyan bold,bold,normal
即第一个正则表达式组(parens)匹配日志文件中的初始日期,第二个组匹配python文件名,行号和函数名称,第三个组匹配之后的日志消息.我还使用'粗体/法线'的并行序列以及颜色序列.这看起来像:
请注意,与我的任何正则表达式不匹配的行或部分行仍然会回显,因此这不像'grep --color' - 没有任何内容从输出中过滤掉.
显然这很灵活,你可以在任何进程中使用它,而不仅仅是拖尾日志文件.我通常只要想要为某些东西着色,就可以动态地制作一个新的正则表达式.出于这个原因,我更喜欢colout到任何自定义日志文件着色工具,因为我只需要学习一个工具,无论我着色的是什么:日志,测试输出,语法突出显示终端中的代码片段等.
它还避免在日志文件本身中实际转储ANSI代码,这是一个坏主意,因为它会破坏日志文件中的模式,除非你总是记得匹配grep正则表达式中的ANSI代码.