我们都知道python在2.x之后自带了一个模块import logging.
但是每次都要写log很麻烦,同时我想把info,debug之类的指令通过颜色分离开来。
于是写了一个简单的类似glog的小程序(完善是不可能完善的,checkeq这辈汁都不可能写的)
1 import logging 2 from colorlog import ColoredFormatter 3 import sys 4 import os 5 6 def currentframe(): 7 """Return the frame object for the caller's stack frame.""" 8 try: 9 raise Exception 10 except: 11 return sys.exc_info()[2].tb_frame.f_back 12 _srcfile = os.path.normcase(currentframe.__code__.co_filename) 13 14 logging._srcfile = _srcfile 15 16 class myLogger(logging.Logger): 17 def __init__(self): 18 super(myLogger, self).__init__('my_logger') 19 formatter = ColoredFormatter( 20 "%(asctime)s - %(filename)s - [line:%(lineno)d] - %(log_color)s%(levelname)s: %(white)s%(message)s", 21 datefmt = None, 22 reset = True, 23 log_colors = { 24 'DEBUG': 'cyan', 25 'INFO': 'green', 26 'WARNING': 'yellow', 27 'ERROR': 'red', 28 'CRITICAL': 'red', 29 } 30 ) 31 32 self.logger = logging.getLogger('example') 33 handler = logging.StreamHandler() 34 handler.setFormatter(formatter) 35 self.logger.addHandler(handler) 36 self.logger.setLevel(logging.DEBUG) 37 38 def setLevel(self, opt): 39 self.logger.setLevel(opt) 40 41 def log(self, opt, str): 42 if opt == 'debug': 43 self.logger.debug(str) 44 if opt == 'info': 45 self.logger.info(str) 46 if opt == 'warning': 47 self.logger.warning(str) 48 if opt == 'error': 49 self.logger.error(str) 50 if opt == 'critical': 51 self.logger.critical(str) 52 53 def log_if(self, opt, flag, str): 54 if (flag == True): 55 self.log(opt, str) 56 57 def debug(self, str): 58 self.logger.debug(str) 59 60 def debug_if(self, flag, str): 61 if (flag == True): 62 self.logger.debug(str) 63 64 def info(self, str): 65 self.logger.info(str) 66 67 def info_if(self, flag, str): 68 if (flag == True): 69 self.logger.info(str) 70 71 def warning(self, str): 72 self.logger.warning(str) 73 74 def warning_if(self, flag, str): 75 if (flag == True): 76 self.logger.warning(str) 77 78 def error(self, str): 79 self.logger.error(str) 80 81 def error_if(self, flag, str): 82 if (flag == True): 83 self.logger.error(str) 84 85 def critical(self, str): 86 self.logger.critical(str) 87 88 def critical_if(self, flag, str): 89 if (flag == True): 90 self.logger.critical(str)
还有一个demo
1 import logging 2 from myLogger import myLogger 3 4 def demo(): 5 log = myLogger() 6 log.log('debug', 'log debug test') 7 8 log.log_if('info', True, 'log_if info test') 9 log.log('warning', 'log warning test') 10 log.log_if('warning', False, 'log_if warning test') 11 log.log_if('error', True, 'log_if error test') 12 log.log('critical', 'log critical test') 13 14 print(' ') 15 16 log.debug('log debug function test') 17 log.info('log info function test') 18 log.warning('log warning function test') 19 log.error('log error function test') 20 log.critical('log critical function test') 21 22 print(' ') 23 24 log.setLevel(logging.WARNING) 25 log.info('log info function test set level') 26 log.warning('log warning function test set level') 27 log.error_if(True, 'log error if true') 28 log.critical_if(False, 'log critical if false') 29 30 31 if __name__ == '__main__': 32 demo()
效果就如下图:
下面稍微解释一下python的logging的原理:
我们以python2.7的logging模块为例子:
先查看logging最开始的一个函数currentframe():
1 # next bit filched from 1.5.2's inspect.py 2 def currentframe(): 3 """Return the frame object for the caller's stack frame.""" 4 try: 5 raise Exception 6 except: 7 return sys.exc_info()[2].tb_frame.f_back
这个函数的作用就是获得当前函数(caller)的在内存中的栈帧。
那么这个函数有什么用的呢?
下面还有一句话:
1 # _srcfile is used when walking the stack to check when we've got the first 2 # caller stack frame. 3 # 4 _srcfile = os.path.normcase(currentframe.__code__.co_filename)
通过currentframe()的信息,就能获得_srcfile,也就是这个python_root/lib/logging/__init__.py的filename。
而我们在import logging之后,例如通过log.info(),希望获得当前的filename,logging是怎么做的呢?
在logging/__init__.py中的class Logger(Filterer)中有一个函数findCaller():
1 def findCaller(self): 2 """ 3 Find the stack frame of the caller so that we can note the source 4 file name, line number and function name. 5 """ 6 f = currentframe() 7 #On some versions of IronPython, currentframe() returns None if 8 #IronPython isn't run with -X:Frames. 9 if f is not None: 10 f = f.f_back 11 rv = "(unknown file)", 0, "(unknown function)" 12 while hasattr(f, "f_code"): 13 co = f.f_code 14 filename = os.path.normcase(co.co_filename) 15 if filename == _srcfile: 16 f = f.f_back 17 continue 18 rv = (co.co_filename, f.f_lineno, co.co_name) 19 break 20 return rv
显然这个函数的作用就是先获得当前这个findCaller的栈帧,然后不断地回退,一直到栈帧的文件信息不是当前logging/__init__.py为止,这时候就是我调用log.info()的具体位置了。
所以为了添加彩色弹幕信息,我们在新加一个myLogger类的时候,记得把logging中的_srcfile从python_root/lib/logging/__init__.py换成当前文件就好了,替换的方法和logging本身如出一辙。
至此我们理解和改造python log告一段落,但是glog的checkeq我是坚决不会写的(因为不会)。