logging
*****本文参考了http://www.cnblogs.com/dkblog/archive/2011/08/26/2155018.html
■ 最最基本的用法
logging模块用于管理,生成日志信息文件
● 首先logging模块可以简单地向屏幕打印出信息:
logging.warning('Hello') #会在屏幕上输出WARNING:root:Hello的信息
在默认情况下,logging只会打印出级别高于warning的信息,比如
logging.debug("Hello") #屏幕上不会出现任何信息
● logging内置的警戒级别:
CRITICAL > ERROR > WARNING > INFO > DEBUG > NOTSET
● 当然所谓日志,那肯定是要以文件的形式存在的,通常可以用logging.basicConfig来进行日志文件的指定和一些基础的配置设置
basicConfig方法可选的参数有:
level 指定日志的最低记录级别,低于次级别的信息将不被记录,默认值为WARNING
filename 指定日志文件的文件名
filemode 指定日志文件的写入模式是w还是a,默认是a
format 指定日志单条信息的格式
datefmt 指定日志中日期时间的格式
比如以下这样配置一个logging参数:
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(filename)s [line %(lineno)d] %(levelname)s %(message)s ", datefmt = "%a, %Y %b %d %H:%M:%S", filename = "/tmp/test.log" )
配置完成之后,就可以在程序中用logging.debug/warning/error等方法来记录日志。比如
logging.debug("some debug message.") #执行完这句话后去/tmp/test.log查看可以看到记录 $cat /tmp/test.log Mon,2014 May 05 16:29:53 teset_logging.py [line 9] DEBUG some debug message.
上述的logging配置中的format可用的格式化字符串有:
%(name)s Logger的名字
%(levelname)s 该条记录的级别
%(lineno)d 该条记录行号
%(process)d 添加该条记录的PID
%(module)s 添加此条记录的模块名(就是调用了那句logging语句的脚本的名字)
%(funcName)s 添加了此条记录的函数名
%(asctime)s 字符串形式的时间,默认格式是 %y-%m-%d %H:%M:%S:%MS 可以用datefmt来调整
■ 不那么基本的基本用法
上面虽然可以简单地进行日志输出了,但是总体而言还是比较simple。一个完整的日志输出体系,不仅要有文件形式,还要有HTTP,socket等多种输出形式;不仅要在发出记录日志的信号后对一个文件写日志,还要能同时进行并行的日志记录;不仅要能写日志,还要能自动备份日志,自动对日志进行分片等等。其实这些功能logging模块也都提供了。下面来看一下这些更加高级点的日志记录方法
使用logging模块记录日志,我们必须要有一个logger对象。上面通过basicConfig来记录日志的就是logging给我们创建了一个默认的logger对象。logger对象可以通过logging.getLogger([name])的方法来获取,name在这里是你给logger取得一个名字,虽然不是必须的,但是规范地取一个名字会很有用,可以体现出不同logger间的层级。logger的名字一般遵从域名的命名习惯,比如可以叫app,然后再创建两个叫app.net和app.net.client的logger来代表app这个logger的下面层级的logger。至于层级到底是怎么回事后面再说吧。
● handler类
光有一个logger的空壳是没有用的,要进行日志的输出,我们至少还需要一个handler来实际操作。handler的类都在logging.handler里面,有很多种不同的handler来满足不同的日志记录需求。主要有的内置的handler有这些:
FileHandler(filename [,mode [,encoding [,delay]]]) (注意,FileHandler在logging中而不是logging.handlers)经典的文件handler,就是把日志内容写入filename指定的文件,mode是打开文件时的模式默认是'a',delay是一个boolean参数,如果设置为True,那么会延迟打开文件,即直到发出首条日志消息时才真的调用open方法打开文件
HTTPHandler(host ,url [, method]) HTTPHandler,用于将日志通过HTTP方法传递给指定的HTTP服务器
MemoryHandler(capacity [,flushLevel [,target]]) 这个处理器用于收集内存中的日志消息,然后将其转移到另一个处理器target。capacity参数指内存缓冲区的大小,单位是字节。如果没有设置target参数那么需要另外调用setTarget()方法来确定target指向的handler,否则将无法运行
RotatingFileHandler(filename[ ,mode[ ,maxBytes[, backupCount [, enoding [, delay]]]]]) RotatingFileHandler很有意思,和FileHandler类似的,它也是把日志写入文件中的,不过它提供了一个功能,就是当日志文件的大小达到指定大小的时候,就另起一个新文件写入,也就是说实现了日志的分片。最终得到的日志名字是filename,filename.1,filename.2等等。maxBytes指出了单个日志所能达到的最大大小,以字节记,默认值是0,即可以无限增长的单个文件。backupCount指出最多做几个日志文件,默认值是0。需要注意的是,通过这个handler得到的日志文件群,filename是最新的文件,随着filename.1,filename.2……到filename.N为止,依次变老,filename.N才是最早的日志。
StreamHandler([fileobj]) 将日志写入已经打开的类文件对象fileobj,如果不提供参数,那么消息会被写入sys.stderr中。
TimeRotatingFileHandler(filename[, when [, interval [, backupCount [, encoding [, delay[, utc]]]]]]) 和RotatingFileHandler类似,不过控制日志分片的不是文件大小而是时间。when可以指定为'S','M','H','D','W','midnight'等,配合interval指定的一个数字,表名每隔多长时间(多少)(单位)进行文件的分片。对于每一个分日志,其命名格式是filename.%Y-%m-%d_%H_%M_%S。
下面是一个简单的应用实例:
import os import logging from logging.handlers import RotatingFileHandler,HTTPHandler if __name__ == '__main__': LOG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)),'test.log') log = logging.getLogger('testlogger1') r_handler = RotatingFileHandler(LOG_FILE,maxBytes=1024*1024,backupCount=5) http_handler = HTTPHandler('127.0.0.1','http://127.0.0.1:5050',method='GET') log.addHandler(r_handler) log.addHandler(http_handler) log.setLevel(logging.DEBUG) #setLevel指定了这个logger所记录的最低等级的信息 log.log(level=logging.INFO,msg='This is a info message')
这就是一个log一次就既写入文件又发出http请求的logger,log.log这个方法指定了level和msg,是相对低层的方法,当碰到不是默认分级的日志记录可以这么写,对logging自带的那些分级也可直接log.info,log.warn,log.error等等。
可以看到logger对象有addHandler方法,这个方法关联了一个logger和一个handler。如果不想要某个handler发挥作用了就可以removeHandler来消除关联。
● Formatter类
上面的例子中,日志文件写入的就是This is a info message。如果需要更加详细的信息,就需要给logger指定一个Formatter类的对象来规定日志格式。Formatter的一般使用就是:
formatter = Formatter([fmt [, datefmt]]) fmt,datefmt的具体写法在最上面的basicConfig中有提到,就不说了。datefmt修饰的是fmt中日期时间的部分。
handler.setFormatter(formatter) 将formatter和handler关联起来。注意是关联handler不是logger,因为不同的处理器当然可以有不同样式的格式。切记切记
除此之外,Formatter还可以帮助logger在记录每条日志时加上动态的上下文。用法是在Formatter的fmt参数中加上相关字段名,然后调用日志记录方法时加上参数extra。比如:
formatter = Formatter(fmt="%(asctime)s %(filename)s [line %(lineno)d] %(levelname)s %(message)s || %(num)s ",datefmt = "%Y-%m-%d %H:%M:%S") handler = FileHandler('test.log') handler.setFormatter(formatter) log = getLogger('app') log.addHandler(handler) random_info = { 'num':random.random() #记得保证key的名字和formatter中的要一样,可以写多个key让解释器自由选取解析 } log.critical('Some Message',extra=random_info) ########## #最后日志中 #2017-08-15 13:33:43 test.py [line 19] INFO Some Solid Message Here || 0.217201896973 ##########
不过如果要这么做,那就要保证每次调用写入日志的方法时都必须带有extra参数,且里面一定要有相关字段存在,否则会报错。
● Filter类
从广义上来说,上面提到的log.setLevel其实也是筛选器的一种,它筛选掉了所以级别低于指定级别的日志信息。而狭义上的Filter,是一个类,可以用来对日志信息进行筛选。过滤器直接与logger对象相关联,比如log.setFilter(filter)或者log.removeFilter(filter)这样来用。另一方面,可以自定义一个Filter类来充当过滤器。任何一个类,只要继承自logging.Filter类并且实现了filter(self,record)方法就可以作为过滤器使用。其中,record是指一条日志记录对象,这个方法最终一定要返回一个True或者False,其中做的逻辑判断就是自定义过滤的本体。record这个参数具有的属性大致有以下这些:
record.name 记录器名称 record.levelname 级别 record.levelno 级别编号 record.pathname 完整脚本路径 record.filename 脚本名称 record.module 模块名字
record.lineno 调用日志发出方法的行数 record.funcName 调用日志发出方法的函数 record.created 发出日志消息的时间 record.thread 线程标识符 record.process 当前进程PID。
这个filter感觉和beautifulsoup里面定位html元素时用的filterfunc有异曲同工的意思,比如:
class MyFilter(Filter): def __init__(self): pass def filter(self,record): if record.module == 'test': return False else: return True log = getLogger(...) log.setFilter(MyFilter())
● 关于logger分级和日志上报
之前就说过了,给logger取的名字可以体现为不同logger间的层级关系。而层级关系的用处,在默认情况下就是体现为下层logger发出的日志信息会自动上报到上层logger中。比如parent_log = getLogger('app')而child_log = getLogger('app.child'),这样的话在child_log.log写入的东西都会反馈给parent_log对应的文件中(如果设置的是其他handler那么就是其他日志载体)。
需要注意的是,在日志上报的机制中,parent_log本身设置的level也好,filter也好都是不起作用的。换句话说,当child_log写出一条日志,这条日志没有被child_log本身的filter和level拒掉的话,那么就一定会反馈到parent_log里面去。这种设定乍一看挺不合理的,但是其反映的是一种分级处理日志信息的一种思想。
若想要改变下层logger的默认行为,可以设置logger.propagate这个标志False,就不会上报了。