|
|
#
|
|
|
# Copyright (C) 2010-2012 Vinay Sajip. All rights reserved. Licensed under the new BSD license.
|
|
|
# (Note: 2018 modifications by @stamparm)
|
|
|
#
|
|
|
|
|
|
import logging
|
|
|
import re
|
|
|
import sys
|
|
|
|
|
|
from lib.core.settings import IS_WIN # 导入一个设置,用于判断是否在Windows系统上运行
|
|
|
|
|
|
if IS_WIN:
|
|
|
import ctypes
|
|
|
import ctypes.wintypes
|
|
|
|
|
|
# Reference: https://gist.github.com/vsajip/758430
|
|
|
# https://github.com/ipython/ipython/issues/4252
|
|
|
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms686047%28v=vs.85%29.aspx
|
|
|
# 设置Windows API函数SetConsoleTextAttribute的参数和返回值类型
|
|
|
ctypes.windll.kernel32.SetConsoleTextAttribute.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD]
|
|
|
ctypes.windll.kernel32.SetConsoleTextAttribute.restype = ctypes.wintypes.BOOL
|
|
|
|
|
|
def stdoutEncode(data): # 用于编码标准输出数据的函数
|
|
|
return data
|
|
|
|
|
|
class ColorizingStreamHandler(logging.StreamHandler):
|
|
|
# 定义颜色名称到索引的映射
|
|
|
color_map = {
|
|
|
'black': 0,
|
|
|
'red': 1,
|
|
|
'green': 2,
|
|
|
'yellow': 3,
|
|
|
'blue': 4,
|
|
|
'magenta': 5,
|
|
|
'cyan': 6,
|
|
|
'white': 7,
|
|
|
}
|
|
|
|
|
|
# 定义日志级别到颜色和样式的映射
|
|
|
level_map = {
|
|
|
logging.DEBUG: (None, 'blue', False),
|
|
|
logging.INFO: (None, 'green', False),
|
|
|
logging.WARNING: (None, 'yellow', False),
|
|
|
logging.ERROR: (None, 'red', False),
|
|
|
logging.CRITICAL: ('red', 'white', False)
|
|
|
}
|
|
|
csi = '\x1b[' # ANSI转义序列的前缀
|
|
|
reset = '\x1b[0m' # ANSI重置颜色的转义序列
|
|
|
bold = "\x1b[1m" # ANSI加粗的转义序列
|
|
|
disable_coloring = False # 是否禁用颜色
|
|
|
|
|
|
@property
|
|
|
def is_tty(self):
|
|
|
# 检查流是否是终端
|
|
|
isatty = getattr(self.stream, 'isatty', None)
|
|
|
return isatty and isatty() and not self.disable_coloring
|
|
|
|
|
|
def emit(self, record):
|
|
|
# 发送日志记录
|
|
|
try:
|
|
|
message = stdoutEncode(self.format(record))
|
|
|
stream = self.stream
|
|
|
|
|
|
#如果当前流不是TTY,直接写入消息
|
|
|
|
|
|
if not self.is_tty:
|
|
|
if message and message[0] == "\r":
|
|
|
message = message[1:]
|
|
|
stream.write(message)
|
|
|
#如果是TTY,调用output_colorized方法来输出带颜色的消息
|
|
|
else:
|
|
|
self.output_colorized(message)
|
|
|
stream.write(getattr(self, 'terminator', '\n'))
|
|
|
|
|
|
self.flush()
|
|
|
except (KeyboardInterrupt, SystemExit):
|
|
|
raise
|
|
|
except IOError:
|
|
|
#IO错误时,什么也不做(pass)
|
|
|
pass
|
|
|
except:
|
|
|
#其他异常时,调用handleError方法
|
|
|
self.handleError(record)
|
|
|
|
|
|
if not IS_WIN:
|
|
|
def output_colorized(self, message):
|
|
|
# 如果不是Windows系统,直接写入消息
|
|
|
self.stream.write(message)
|
|
|
else:
|
|
|
ansi_esc = re.compile(r'\x1b\[((?:\d+)(?:;(?:\d+))*)m')
|
|
|
# 正则表达式,用于匹配ANSI转义序列
|
|
|
|
|
|
nt_color_map = {
|
|
|
0: 0x00, # black
|
|
|
1: 0x04, # red
|
|
|
2: 0x02, # green
|
|
|
3: 0x06, # yellow
|
|
|
4: 0x01, # blue
|
|
|
5: 0x05, # magenta
|
|
|
6: 0x03, # cyan
|
|
|
7: 0x07, # white
|
|
|
}
|
|
|
|
|
|
def output_colorized(self, message):
|
|
|
# 如果是Windows系统,解析ANSI转义序列并设置控制台颜色
|
|
|
parts = self.ansi_esc.split(message)
|
|
|
h = None
|
|
|
fd = getattr(self.stream, 'fileno', None)
|
|
|
|
|
|
#文件描述符有效,并且是标准输出或标准错误,获取对应的Windows句柄
|
|
|
if fd is not None:
|
|
|
fd = fd()
|
|
|
|
|
|
if fd in (1, 2): # stdout or stderr
|
|
|
h = ctypes.windll.kernel32.GetStdHandle(-10 - fd)
|
|
|
|
|
|
#循环处理分割后的消息部分
|
|
|
while parts:
|
|
|
text = parts.pop(0)
|
|
|
|
|
|
#如果部分是文本,写入并刷新流
|
|
|
if text:
|
|
|
self.stream.write(text)
|
|
|
self.stream.flush()
|
|
|
|
|
|
#如果还有部分,取出下一个部分作为参数
|
|
|
if parts:
|
|
|
params = parts.pop(0)
|
|
|
|
|
|
#如果句柄有效,将参数分割并转换为整数,初始化颜色代码
|
|
|
if h is not None:
|
|
|
params = [int(p) for p in params.split(';')]
|
|
|
color = 0
|
|
|
|
|
|
for p in params:
|
|
|
if 40 <= p <= 47:
|
|
|
color |= self.nt_color_map[p - 40] << 4
|
|
|
elif 30 <= p <= 37:
|
|
|
color |= self.nt_color_map[p - 30]
|
|
|
elif p == 1:
|
|
|
color |= 0x08 # foreground intensity on
|
|
|
elif p == 0: # reset to default color
|
|
|
color = 0x07
|
|
|
else:
|
|
|
pass # error condition ignored
|
|
|
|
|
|
ctypes.windll.kernel32.SetConsoleTextAttribute(h, color)
|
|
|
|
|
|
def _reset(self, message):
|
|
|
#重置消息的颜色
|
|
|
if not message.endswith(self.reset):
|
|
|
# 如果消息不以重置序列结尾,则添加重置序列
|
|
|
reset = self.reset
|
|
|
elif self.bold in message:
|
|
|
# 如果消息包含加粗,则在重置后加粗
|
|
|
reset = self.reset + self.bold
|
|
|
else:
|
|
|
reset = self.reset
|
|
|
|
|
|
return reset
|
|
|
|
|
|
def colorize(self, message, levelno):
|
|
|
# 根据日志级别给消息上色
|
|
|
if levelno in self.level_map and self.is_tty:
|
|
|
bg, fg, bold = self.level_map[levelno]
|
|
|
params = []
|
|
|
|
|
|
#如果背景色有效,添加背景色参数
|
|
|
if bg in self.color_map:
|
|
|
params.append(str(self.color_map[bg] + 40))
|
|
|
|
|
|
#如果前景色有效,添加前景色参数
|
|
|
if fg in self.color_map:
|
|
|
params.append(str(self.color_map[fg] + 30))
|
|
|
#如果需要加粗,添加加粗参数
|
|
|
if bold:
|
|
|
params.append('1')
|
|
|
|
|
|
#如果参数和消息都有效,检查消息是否有前缀(空格),并提取出来
|
|
|
if params and message:
|
|
|
if message.lstrip() != message:
|
|
|
prefix = re.search(r"\s+", message).group(0)
|
|
|
message = message[len(prefix):]
|
|
|
else:
|
|
|
prefix = ""
|
|
|
|
|
|
message = "%s%s" % (prefix, ''.join((self.csi, ';'.join(params),
|
|
|
'm', message, self.reset)))
|
|
|
|
|
|
return message
|
|
|
|
|
|
def format(self, record):
|
|
|
# 格式化日志记录
|
|
|
message = logging.StreamHandler.format(self, record)
|
|
|
return self.colorize(message, record.levelno) |