1. logging日志框架
主要包括四部分:
- Loggers: 可供程序直接调用的接口,app通过调用提供的api来记录日志
- Handlers: 决定将日志记录分配至正确的目的地
- Filters:对日志信息进行过滤, 提供更细粒度的日志是否输出的判断
- Formatters: 制定最终记录打印的格式布局
1)loggers
loggers 就是程序可以直接调用的一个日志接口,可以直接向logger写入日志信息。logger并不是直接实例化使用的,而是通过logging.getLogger(name)来获取对象,事实上logger对象是单例模式,logging是多线程安全的,也就是无论程序中哪里需要打日志获取到的logger对象都是同一个。但是不幸的是logger并不支持多进程,这个在后面的章节再解释,并给出一些解决方案。
【注意】loggers对象是有父子关系的,当没有父logger对象时它的父对象是root,当拥有父对象时父子关系会被修正。举个例子,logging.getLogger("abc.xyz") 会创建两个logger对象,一个是abc父对象,一个是xyz子对象,同时abc没有父对象,所以它的父对象是root。但是实际上abc是一个占位对象(虚的日志对象),可以没有handler来处理日志。但是root不是占位对象,如果某一个日志对象打日志时,它的父对象会同时收到日志,所以有些使用者发现创建了一个logger对象时会打两遍日志,就是因为他创建的logger打了一遍日志,同时root对象也打了一遍日志。
上述几个例子中我们了解到了logging.debug()、logging.info()、logging.warning()、logging.error()、logging.critical()(分别用以记录不同级别的日志信息),logging.basicConfig()(用默认日志格式(Formatter)为日志系统建立一个默认的流处理器(StreamHandler),设置基础配置(如日志级别等)并加到root logger(根Logger)中)这几个logging模块级别的函数,另外还有一个模块级别的函数是logging.getLogger([name])(返回一个logger对象,如果没有指定名字将返回root logger)
Logger是一个树形层级结构,输出信息之前都要获得一个Logger(如果没有显示的获取则自动创建并使用root Logger)。
logger = logging.getLogger()返回一个默认的Logger也即root Logger,并应用默认的日志级别、Handler和Formatter设置。
当然也可以通过Logger.setLevel(lel)指定最低的日志级别,可用的日志级别有logging.DEBUG、logging.INFO、logging.WARNING、logging.ERROR、logging.CRITICAL。
Logger.debug()、Logger.info()、Logger.warning()、Logger.error()、Logger.critical()输出不同级别的日志,只有日志等级大于或等于设置的日志级别的日志才会被输出。
logging.getLogger([name]),创建Logger对象。日志记录的工作主要由Logger对象来完成。在调用getLogger时要提供Logger的名称(注:多次使用相同名称来调用getLogger,返回的是同一个对象的引用。),Logger实例之间有层次关系,这些关系通过Logger名称来体现,如:
p = logging.getLogger(“root”)
c1 = logging.getLogger(“root.c1”)
c2 = logging.getLogger(“root.c2”)
例子中,p是父logger, c1,c2分别是p的子logger。c1, c2将继承p的设置。如果省略了name参数, getLogger将返回日志对象层次关系中的根Logger。
<1>我们明明通过logger1.setLevel(logging.DEBUG)将logger1的日志级别设置为了DEBUG,为何显示的时候没有显示出DEBUG级别的日志信息,而是从INFO级别的日志开始显示呢?
原来只要logging.getLogger(name)中名称参数name相同则返回的Logger实例就是同一个,且仅有一个,也即name与Logger实例一一对应。在logger2实例中通过logger2.setLevel(logging.INFO)设置mylogger的日志级别为logging.INFO,所以最后logger1的输出遵从了后来设置的日志级别。
-----------------------返回结果只有4条-------------------------
<2>为什么logger1、logger2对应的每个输出分别显示两次?
而孩子,孙子,重孙……既会将消息分发给他的handler进行处理也会传递给所有的祖先Logger处理,故打印2次,如果 是孙子则打印3次。
---------------返回9条信息-------------
孩子,孙子,重孙……可逐层继承来自祖先的日志级别、Handler、Filter设置,也可以通过Logger.setLevel(lel)、Logger.addHandler(hdlr)、Logger.removeHandler(hdlr)、Logger.addFilter(filt)、Logger.removeFilter(filt)。设置自己特别的日志级别、Handler、Filter。若不设置则使用继承来的值。
例2:
dad=logging.getLogger() #创建父对象
son=logging.getLogger('son') #创建子对象
grandson=logging.getLogger('son.grandson') #创建孙对象
fm=logging.Formatter('%(asctime)s--%(name)s--%(message)s') #设置格式
ch=logging.StreamHandler() #设置输出流向
ch.setFormatter(fm) #添加格式
dad.addHandler(ch) #添加流向
son.addHandler(ch)
grandson.addHandler(ch)
dad.setLevel(logging.ERROR)
son.setLevel(logging.WARNING)
grandson.setLevel(logging.DEBUG)
dad.debug('dad debug message')
dad.info('dad info message')
dad.warning('dad warning message')
dad.error('dad error message')
dad.critical('dad critical message')
son.debug('son debug message')
son.info('son info message')
son.warning('son warning message')
son.error('son error message')
son.critical('son critical message')
grandson.debug('grandson debug message')
grandson.info('grandson info message')
grandson.warning('grandson warning message')
grandson.error('grandson error message')
grandson.critical('grandson critical message')
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2019-01-11 11:22:52,253--root--dad error message
2019-01-11 11:22:52,253--root--dad critical message
2019-01-11 11:22:52,253--son--son warning message
2019-01-11 11:22:52,253--son--son warning message
2019-01-11 11:22:52,253--son--son error message
2019-01-11 11:22:52,253--son--son error message
2019-01-11 11:22:52,253--son--son critical message
2019-01-11 11:22:52,253--son--son critical message
2019-01-11 11:22:52,253--son.grandson--grandson debug message
2019-01-11 11:22:52,253--son.grandson--grandson debug message
2019-01-11 11:22:52,253--son.grandson--grandson debug message
2019-01-11 11:22:52,254--son.grandson--grandson info message
2019-01-11 11:22:52,254--son.grandson--grandson info message
2019-01-11 11:22:52,254--son.grandson--grandson info message
2019-01-11 11:22:52,254--son.grandson--grandson warning message
2019-01-11 11:22:52,254--son.grandson--grandson warning message
2019-01-11 11:22:52,254--son.grandson--grandson warning message
2019-01-11 11:22:52,254--son.grandson--grandson error message
2019-01-11 11:22:52,254--son.grandson--grandson error message
2019-01-11 11:22:52,254--son.grandson--grandson error message
2019-01-11 11:22:52,254--son.grandson--grandson critical message
2019-01-11 11:22:52,254--son.grandson--grandson critical message
2019-01-11 11:22:52,254--son.grandson--grandson critical message
2)Handlers
Handlers 将logger发过来的信息进行准确地分配,送往正确的地方。举个栗子,送往控制台或者文件或者both或者其他地方(进程管道之类的)。它决定了每个日志的行为,是之后需要配置的重点区域。
每个Handler同样有一个日志级别,一个logger可以拥有多个handler也就是说logger可以根据不同的日志级别将日志传递给不同的handler。当然也可以相同的级别传递给多个handlers这就根据需求来灵活的设置了。
3)Filters
Filters 提供了更细粒度的判断,来决定日志是否需要打印。原则上handler获得一个日志就必定会根据级别被统一处理,但是如果handler拥有一个Filter可以对日志进行额外的处理和判断。例如Filter能够对来自特定源的日志进行拦截or修改甚至修改其日志级别(修改后再进行级别判断)。
logger和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 ch还有添加打印格式
fh.setFormatter(formatter)
ch.setFormatter(formatter)
# 定义一个filter
filter = logging.Filter('mylogger')
#添加filter样式
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('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')
View Code
2019-01-11 15:44:35,357 - mylogger - INFO - logger1 info message
2019-01-11 15:44:35,357 - mylogger - INFO - logger1 info message
2019-01-11 15:44:35,357 - mylogger - WARNING - logger1 warning message
2019-01-11 15:44:35,357 - mylogger - WARNING - logger1 warning message
2019-01-11 15:44:35,357 - mylogger - ERROR - logger1 error message
2019-01-11 15:44:35,357 - mylogger - ERROR - logger1 error message
2019-01-11 15:44:35,357 - mylogger - CRITICAL - logger1 critical message
2019-01-11 15:44:35,357 - mylogger - CRITICAL - logger1 critical message
2019-01-11 15:44:35,357 - mylogger - INFO - logger2 info message
2019-01-11 15:44:35,357 - mylogger - INFO - logger2 info message
2019-01-11 15:44:35,357 - mylogger - WARNING - logger2 warning message
2019-01-11 15:44:35,357 - mylogger - WARNING - logger2 warning message
2019-01-11 15:44:35,357 - mylogger - ERROR - logger2 error message
2019-01-11 15:44:35,357 - mylogger - ERROR - logger2 error message
2019-01-11 15:44:35,357 - mylogger - CRITICAL - logger2 critical message
2019-01-11 15:44:35,357 - mylogger - CRITICAL - logger2 critical message
View Code
4) Formatters
Formatters 指定了最终某条记录打印的格式布局。Formatter会将传递来的信息拼接成一条具体的字符串,默认情况下Format只会将信息%(message)s直接打印出来。Format中有一些自带的LogRecord属性可以使用,如下表格:
一个Handler只能拥有一个Formatter 因此如果要实现多种格式的输出只能用多个Handler来实现。
上图只是一部分,更详细的在docs.python.org里找logging模块