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