日志可追踪软件运行时的数据和状态,有利于调试,日志模块在后台开发中不可缺少
python logging模块提供一系列接口和方法用于日志记录(Tutorial)。
日志优先级分为:
debug : 10
info : 20
warning : 30
error : 40
当通过logger.setLevel()设定级别后,低于该级别的日志将不被打印。
该优先级别支持自定义,例如,可设置KEY = logger.ERROR + 1,如此,KEY的优先级别将最高
1 普通打印
import logging
logging.warning('Watch out')
logging.info('Here is info')
2 打印至文件
import logging
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
logging.debug('debug info')
logging.info('info')
logging.warning('warning info')
note:format中 levelname为日志级别,message为日志信息, asctime为时间, filename为函数模块文件名, funcName为调用日志模块的函数名
logging库包含loggers,handlers,filters和formatters四大组件
loggers:暴露接口供应用层逻辑调用
handlers:将loggers产生的log record放到日志容器(文件 or 控制台)
filters:过滤组件
formatter:定义日志格式
一条日志,即logRecord对象,从loggers生成后,经过filters, formatters,通过handlers最后到达容器
应用在使用logging时,通过name调用logger实例,在python一个方便的用法,是将模块名作为Logger名,ex.
logger = logging.getLogger(__name__)
通过logger调用debug(), info()来生成不同级别的日志信息
logger对象的三件事:提供接口供应用实时记录日志(log -> _log -> makeRecord);根据优先级过滤及格式化记录(setLevel | setFilter | setFormatter);将日志定向到用户所需的容器内(addHandler)
logger.exception vs logger.error:前者除了error信息,还附带程序的stack trace信息
logger提供log方法,用于自定义logLevel
ex.
KEY = logger.ERROR + 1
logger = logging.getLogger(__name__)
logger.log(KEY,*args, **kw)
StreamHandler:向sys.stdout或sys.stderr写日志
FileHandler,:向文件输出日志,无该文件时会新建,默认模式为a,尾部追加
RotatingFileHandler:同样是向文件输出日志,但可管理文件大小,当达到一定大小后,会自动创建一个新文件来基础输出,构造函数中的maxBytes控制最大字节数,backupCount控制文件名后缀最大循环(ex. 30,当到达30后会重新以1为后缀,老1文件会被删除)
TimedRotatingFileHandler:不根据大小而根据时间间隔创建。构造函数中的interval表明距离上一个日志文件创建时间的时间间隔,S秒,M分,H小时,D天,W每星期,midnight凌晨。也可通过重写computeRollover()来定制间隔。
文件 | HTTP url | email by SMTP
日志最终通过Handler写入到上述容器中。若logging检测用户未定义任何的日志容器,将默认使用root作为logger,将日志以默认的格式写入sys.stderr,即显示在控制台。
ex. 一个常用的logger示例 ( 调用logger对象方法来设置filter, handler, formatter等 )
import logging
# create logger, set level
logger = logging.getLogger('log example')
logger.setLevel(logging.DEBUG)
# create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
#create formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# add formatter to ch
ch.setFormatter(formatter)
# add ch to logger
logger.addHandler(ch)
# `application` logging
logger.debug('debug info')
logger.info('info')
logger.error('error info')
logger.critical('critical info')
运行结果
C:\Python27\python.exe D:/PythonEx/logMgr/app_web.py
2017-03-14 20:45:00,469 - log example - DEBUG - debug info
2017-03-14 20:45:00,470 - log example - INFO - info
2017-03-14 20:45:00,470 - log example - ERROR - error info
2017-03-14 20:45:00,470 - log example - CRITICAL - critical info
另一种方法:通过通过.conf文件配置level,handler, filter等,加载配置的api为:logging.config.fileConfig(‘logging.conf’)
一个logging.conf文件可能是:
[loggers]
keys=root,example
[handlers]
keys=consoleHandler,rotateFileHandler
[formatters]
keys=simpleFormatter
[formatter_simpleFormatter]
format=[%(asctime)s](%(levelname)s)%(name)s : %(message)s
[logger_root]
level=DEBUG
handlers=consoleHandler #root logger control console log
[logger_example]
level=DEBUG
handlers=rotateFileHandler #example logger create log to file
qualname=example
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)
[handler_rotateFileHandler]
class=handlers.RotatingFileHandler
level=DEBUG
formatter=simpleFormatter
args=('test.log'),'a',10000,10)
note : args必须为元组tuple
ex.
import logging
import logging.config
logging.config.fileConfig("logging.conf")
#create logger
logger = logging.getLogger("example")
# "application" logging
logger.debug("debug info")
logger.info("info")
logger.error("error info")
运行
C:\Python27\python.exe D:/PythonEx/logMgr/app_web.py
[2017-03-15 11:38:50,519 : DEBUG :example : debug info]
[2017-03-15 11:38:50,519 : INFO :example : info]
[2017-03-15 11:38:50,519 : ERROR :example : error info]
以后台开发为例,程序大多是多线程的。日志虽然是重要的运维和调试部分,但在操作系统中读写文件耗时,考虑到性能,有必要收集日志后定时处理。
在handler之前加入日志收集线程,这里采用gevent.Greenlet:
class gCollector(gevent.Greenlet):
"""Collect records
"""
time_interval = 30
records_count_upper = 100
def __init__(self, handlerClass):
super(gCollector, self).__init__()
self._handler_class = handlerClass
self._logRecords = []
self._interval = self.time_interval
def _run(self):
while True:
try:
logLen = len(self._logRecords)
if logLen:
self._interval -= 1
if self._interval<=0 or logLen >= self.records_count_upper: #each 10s or to max upper limit
logRecords = self._logRecords
self._logRecords = []
self._interval = self.time_interval
for log in logRecords:
self._handler_class.emit(log) #call handler handle
self._handler_class.flush() #stream handler need flush
gevent.sleep(1)
except:
defaultLogger.exception("") #with traceback info
def emit(self, record):
self._logRecords.append(record) #collect records
定义上述的收集线程后,需要定制Handler来继承该类 。如下:
class myFileHandler(gCollector, logging.FileHandler):
'''
My File Handler
'''
def __init__(self, filename, delay=True):
super(gCollector, self).__init__(logging.FileHandler)
super(myFileHandler, self).__init__(filename, delay=delay)
def emit(self, record):
logging.FileHandler.emit(self, record)
需要注意的是,使用gCollector增加收集和定时写入后,日志的生成时间和写入disk的时间不再一致。对于TimeRotateFileHandler等在每次写入时会判定是否需要重新生成日志文件的类,如下:
def shouldRollover(self, record):
"""
Determine if rollover should occur.
record is not used, as we are just comparing times, but it is needed so
the method signatures are the same
"""
t = int(time.time())
if t >= self.rolloverAt:
return 1
#print "No need to rollover: %d, %d" % (t, self.rolloverAt)
return 0
上述实现时,在判断时使用当前时刻,这不适用我们的gCollector订制,这个函数也应该被重写,使用record.created代替time.time():
def shouldRollover(self, record):
if record.created >= self.rolloverAt
return 1
return 0
额外,也可基于已有的Handler按照需求进行定制,例如重新制定日志文件的生成规则,对于TimedRotateHandler,通过传入when和interval就可以确定每个日志文件的生成时间,但若我们希望在每天某个时刻生成一个文件(例如零点),就需要修改computeRollover函数。下述代码是固定时刻为每日5点:
def computeRollover(self, currentTime):
'''Rewrite default func
'''
timeStruct = time.localtime(currentTime)
timeStruct = time.strptime("%s-%s-%s 5" % (timeStruct.tm_year, timeStruct.tm_mon, timeStruct.tm_mday), "%Y-%m-%d %H")
return TimedRotatingFileHandler.computeRollover(self, int(time.mktime(timeStruct)))