python logger使用与按需定制

python logger使用与按需定制

日志可追踪软件运行时的数据和状态,有利于调试,日志模块在后台开发中不可缺少

logging库

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对象

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)

常用handler

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,即显示在控制台。

日志处理流

python logger使用与按需定制_第1张图片

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]

定制Logger

以后台开发为例,程序大多是多线程的。日志虽然是重要的运维和调试部分,但在操作系统中读写文件耗时,考虑到性能,有必要收集日志后定时处理。
在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)))

你可能感兴趣的:(Python)