Logging模块
对于log,不会陌生。一般程序中,都会打印一些log让流程更清晰,或者出现错误的时候能够快速通过log找到问题节点,以前写的一些简单的程序,通过print可以打印在屏幕上,通过file.write()写在文档中,但感觉有些模范,不是很专业的样子。Python中有一个专业处理log的模块,叫logging模块。
logging模块的简单应用:
import logging
logging.debug('debug')
logging.info('info')
logging.warning('warning')
logging.error('error')
logging.critical('critical')
>>>
WARNING:root:warning
ERROR:root:error
CRITICAL:root:critical
首先我们看到的debug,info,warning,error,critical,这是log五个等级(日志级别等级CRITICAL > ERROR > WARNING > INFO > DEBUG > NOTSET),默认情况下,Python只会显示warning及以上的信息,如果想让所有log全部输出,而且有一定的格式,就需要对logging进行一些设置:
import logging
logging.basicConfig(
format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
filename='test.log',
filemode='w'
)
logging.debug('debug')
logging.info('info')
logging.warning('warning')
logging.error('error')
logging.critical('critical')
现在在控制台上已经没有打印的log了,因为在logging.basicConfig中,定义了一个filename,log都会保存在'test.log'中,打开文件内容如下:
2019-03-18 13:58:33,470 logging模块.py[line:11] WARNING warning
2019-03-18 13:58:33,470 logging模块.py[line:12] ERROR error
2019-03-18 13:58:33,470 logging模块.py[line:13] CRITICAL critical
这样看起来就很舒服了,在logging.basicConfig中还有其他设置,分别如下:
filename:用指定的文件名创建FiledHandler(后边会具体讲解handler的概念),这样日志会被存储在指定的文件中。
filemode:文件打开方式,在指定了filename时使用这个参数,默认值为“a”还可指定为“w”。
format:指定handler使用的日志显示格式。
datefmt:指定日期时间格式。
level:设置rootlogger(后边会讲解具体概念)的日志级别
stream:用指定的stream创建StreamHandler。可以指定输出到sys.stderr,sys.stdout或者文件(f=open('test.log','w')),默认为sys.stderr。若同时列出了filename和stream两个参数,则stream参数会被忽略。
format参数中可能用到的格式化串:
%(name)s Logger的名字
%(levelno)s 数字形式的日志级别
%(levelname)s 文本形式的日志级别
%(pathname)s 调用日志输出函数的模块的完整路径名,可能没有
%(filename)s 调用日志输出函数的模块的文件名
%(module)s 调用日志输出函数的模块名
%(funcName)s 调用日志输出函数的函数名
%(lineno)d 调用日志输出函数的语句所在的代码行
%(created)f 当前时间,用UNIX标准的表示时间的浮 点数表示
%(relativeCreated)d 输出日志信息时的,自Logger创建以 来的毫秒数
%(asctime)s 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒
%(thread)d 线程ID。可能没有
%(threadName)s 线程名。可能没有
%(process)d 进程ID。可能没有
%(message)s用户输出的消息
Logger
现在出现了一个问题,如果我写了filename参数,就不会在屏幕上显示了,我怎么才能又能在控制台上显示,又能保存在文本中呢?这里就需要用到logger了:
import logging logger = logging.getLogger() fh = logging.FileHandler('test.log') #创建一个Handler,用来保存log到文件 ch = logging.StreamHandler() #创建一个Handler,用来显示log到控制台 # 创建一个显示log的格式 fm = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') fh.setFormatter(fm) #在文本中设置成这种格式保存 ch.setFormatter(fm) #在控制台中设置成这种格式显示 logger.addHandler(fh) #将fh的功能添加到logger,logger就有了fh的功能了 logger.addHandler(ch) #将ch的功能添加到logger,logger就又有了ch的功能了 logger.setLevel('DEBUG') #将默认的log等级改为debug logger.debug('debug') logger.info('info') logger.warning('warning') logger.error('error') logger.critical('critical')
在控制和test.log中都打印的是:
2019-03-18 14:24:07,722 - root - DEBUG - debug
2019-03-18 14:24:07,722 - root - INFO - info
2019-03-18 14:24:07,722 - root - WARNING - warning
2019-03-18 14:24:07,722 - root - ERROR - error
2019-03-18 14:24:07,722 - root - CRITICAL - critical
这个例子可以看到,logger就好像一个有吸星大法的人,本来没有武功,但是吸收了fh和ch后,就有了他们的功能。logging库提供了多个组件:Logger、Handler、Filter、Formatter。Logger对象提供应用程序可直接使用的接口,Handler发送日志到适当的目的地,Filter提供了过滤日志信息的方法,Formatter指定日志显示格式。
有多中可用的Handler:
logging.StreamHandler 可以向类似与sys.stdout或者sys.stderr的任何文件对象(file object)输出信息
logging.FileHandler 用于向一个文件输出日志信息
logging.handlers.RotatingFileHandler 类似于上面的FileHandler,但是它可以管理文件大小。当文件达到一定大小之后,它会自动将当前日志文件改名,然后创建一个新的同名日志文件继续输出
logging.handlers.TimedRotatingFileHandler 和RotatingFileHandler类似,不过,它没有通过判断文件大小来决定何时重新创建日志文件,而是间隔一定时间就自动创建新的日志文件
logging.handlers.SocketHandler 使用TCP协议,将日志信息发送到网络。
logging.handlers.DatagramHandler 使用UDP协议,将日志信息发送到网络。
logging.handlers.SysLogHandler 日志输出到syslog
logging.handlers.NTEventLogHandler 远程输出日志到Windows NT/2000/XP的事件日志
logging.handlers.SMTPHandler 远程输出日志到邮件地址
logging.handlers.MemoryHandler 日志输出到内存中的制定buffer
logging.handlers.HTTPHandler 通过"GET"或"POST"远程输出到HTTP服务器
各个Handler的具体用法可查看参考书册:
https://docs.python.org/2/library/logging.handlers.html#module-logging.handlers
下面再看一个例子:
import logging logger1 = logging.getLogger('logger') logger2 = logging.getLogger('logger') fh = logging.FileHandler('test.log') #创建一个Handler,用来保存log到文件 ch = logging.StreamHandler() #创建一个Handler,用来显示log到控制台 # 创建一个显示log的格式 fm = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') fh.setFormatter(fm) #在文本中设置成这种格式保存 ch.setFormatter(fm) #在控制台中设置成这种格式显示
logger.addHandler(fh)
logger.addHandler(ch)
logger1.addHandler(fh) logger1.addHandler(ch) logger1.setLevel('DEBUG') #将默认的log等级改为debug logger2.addHandler(fh) logger2.addHandler(ch) logger2.setLevel('INFO') #将默认的log等级改为info logger1.debug('debug') logger1.info('info') logger1.warning('warning') logger1.error('error') logger1.critical('critical') logger2.debug('debug') logger2.info('info') logger2.warning('warning') logger2.error('error') logger2.critical('critical')
显示的结果为:
2019-03-18 14:56:31,728 - logger - INFO - info
2019-03-18 14:56:31,728 - logger - INFO - info
2019-03-18 14:56:31,728 - logger - WARNING - warning
2019-03-18 14:56:31,728 - logger - WARNING - warning
2019-03-18 14:56:31,729 - logger - ERROR - error
2019-03-18 14:56:31,729 - logger - ERROR - error
2019-03-18 14:56:31,729 - logger - CRITICAL - critical
2019-03-18 14:56:31,729 - logger - CRITICAL - critical
2019-03-18 14:56:31,729 - logger - INFO - info
2019-03-18 14:56:31,729 - logger - INFO - info
2019-03-18 14:56:31,729 - logger - WARNING - warning
2019-03-18 14:56:31,729 - logger - WARNING - warning
2019-03-18 14:56:31,729 - logger - ERROR - error
2019-03-18 14:56:31,729 - logger - ERROR - error
2019-03-18 14:56:31,729 - logger - CRITICAL - critical
2019-03-18 14:56:31,729 - logger - CRITICAL - critical
什么情况?为什么logger1的debug没有显示?原来logger是一个树状结构,输出信息之前都要获得一个Logger(如果没有显示的获取则自动创建并使用root Logger,如第一个例子所示),logger1和logger2对应的是同一个Logger实例,只要logging.getLogger(name)中名称参数name相同则返回的Logger实例就是同一个,且仅有一个,也即name与Logger实例一一对应。在logger2实例中通过logger2.setLevel(logging.INFO)设置mylogger的日志级别为logging.INFO,所以最后logger1的输出遵从了后来设置的日志级别。
还有一个问题,为什么logger1和logger2都打印了两次?这是因为我们通过logger = logging.getLogger()显示的创建了root logger,而logger1 = logging.getLogger('mylogger')创建了root Logger的孩子(root.logger1)mylogger,logger2同样。而孩子,孙子,重孙……既会将消息分发给他的handler进行处理也会传递给所有的祖先logger处理。所以,把logger干活的地方注释掉,logger.addHandler(fh) 、logger.addHandler(ch) 就OK了,不信可以试试。
孩子,孙子,重孙……可逐层继承来自祖先的日志级别、Handler、Filter设置,也可以通过Logger.setLevel(lel)、Logger.addHandler(hdlr)、Logger.removeHandler(hdlr)、Logger.addFilter(filt)、Logger.removeFilter(filt)。设置自己特别的日志级别、Handler、Filter。若不设置则使用继承来的值。
filter
限制只有满足过滤规则的日志才会输出。
比如我们定义了filter = logging.Filter('a.b.c'),并将这个Filter添加到了一个Handler上,则使用该Handler的Logger中只有名字带 a.b.c前缀的Logger才能输出其日志。
filter = logging.Filter('mylogger')
logger.addFilter(filter)
这是只对logger这个对象进行筛选
如果想对所有的对象进行筛选,则:
filter = logging.Filter('mylogger')
fh.addFilter(filter)
ch.addFilter(filter)
这样,所有添加fh或者ch的logger对象都会进行筛选。
import logging logger = logging.getLogger() # 创建一个handler,用于写入日志文件 fh = logging.FileHandler('test.log') # 再创建一个handler,用于输出到控制台 ch = logging.StreamHandler() formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') fh.setFormatter(formatter) ch.setFormatter(formatter) # 定义一个filter filter = logging.Filter('mylogger') fh.addFilter(filter) ch.addFilter(filter) # logger.addFilter(filter) # logger.addHandler(fh) # logger.addHandler(ch) logger.setLevel(logging.DEBUG) logger.debug('logger debug message') logger.info('logger info message') logger.warning('logger warning message') logger.error('logger error message') logger.critical('logger critical message') logger1 = logging.getLogger('root.mylogger') logger1.setLevel(logging.DEBUG) logger2 = logging.getLogger('mylogger') logger2.setLevel(logging.INFO) logger1.addHandler(fh) logger1.addHandler(ch) logger2.addHandler(fh) logger2.addHandler(ch) logger1.debug('logger1 debug message') logger1.info('logger1 info message') logger1.warning('logger1 warning message') logger1.error('logger1 error message') logger1.critical('logger1 critical message') logger2.debug('logger2 debug message') logger2.info('logger2 info message') logger2.warning('logger2 warning message') logger2.error('logger2 error message') logger2.critical('logger2 critical message')
定义一个filter,就是只有mylogger的log才会打印,但是只有ch和fh添加了这个过滤,logger没有添加,所以结果为:
logger warning message
logger error message
logger critical message
2019-03-18 15:23:36,911 - mylogger - INFO - logger2 info message
2019-03-18 15:23:36,911 - mylogger - WARNING - logger2 warning message
2019-03-18 15:23:36,911 - mylogger - ERROR - logger2 error message
2019-03-18 15:23:36,911 - mylogger - CRITICAL - logger2 critical message
从上面的程序可以看到,前面配置太多了,整个程序很长, 所以可以把配置的相关信息写到配置文件中,比如一个conf文件:
[loggers]
import logging import logging.config logging.config.fileConfig("logging.conf") # 采用配置文件 # create logger logger = logging.getLogger("simpleExample") # "application" code logger.debug("debug message") logger.info("info message") logger.warn("warn message") logger.error("error message") logger.critical("critical message")
以上就是logging模块的简单用法。