前段时间,我看到一个Mono应用程序的输出是彩色的,可能是因为它的日志系统(因为所有的消息都是标准化的)。
现在,Python有了日志记录模块,它允许您指定许多选项来定制输出。所以,我想象类似的事情可能与Python,但我不知道如何在任何地方做到这一点。
是否有办法使Python日志模块输出为彩色?
我想要的(例如)错误显示为红色,调试消息显示为蓝色或黄色,等等。
当然,这可能需要一个兼容的终端(大多数现代终端都是);但如果不支持颜色,我可以退回到原始的日志输出。
有什么想法,我可以得到彩色输出与日志模块?
一个简单但非常灵活的工具为任何终端文本着色是'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代码,恕我说这是一个坏主意,因为它会破坏日志文件中的grepping模式之类的事情,除非您始终记得在grep正则表达式中匹配ANSI代码。
简化的日志库:
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()])
以下是我的解决方案:
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])
一个简单但非常灵活的工具为任何终端文本着色是'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代码,恕我说这是一个坏主意,因为它会破坏日志文件中的grepping模式之类的事情,除非您始终记得在grep正则表达式中匹配ANSI代码。
我更喜欢使用这个片段:
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())
用法:
import logging
from .log import logger
logger.setLevel(logging.DEBUG)
logger.debug("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
coloredlogs
Instalation
pip install coloredlogs
使用
最小的用法:
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')
结果:
从消息级调试开始:
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')
结果:
从库中隐藏消息:
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')
结果:
格式化日志消息:
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')
结果:
可用的格式属性:
%(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
来源:
Coloredlogs包
日志库