现在项目用的 logging 是配置的形式,配置文件中 Logger 只有 root。配置文件如下:

[loggers]
keys=root

[handlers]
keys=consoleHandler,fileHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=DEBUG
handlers=consoleHandler,fileHandler

[handler_consoleHandler]
class=StreamHandler
formatter=simpleFormatter
args=(sys.stdout,)

[handler_fileHandler]
class=FileHandler
formatter=simpleFormatter
args=('test.log', 'w')


[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s

在网上找了些资料后,我给 logger 加上了 notification,目的我也不明确,想说加加看嘛,后面可能会用到,也做个学习。

[loggers]
keys=root,notification

[logger_root]
level=DEBUG
handlers=consoleHandler,fileHandler

[logger_notification]
level=WARNING
qualname=notification
handlers=consoleHandler,fileHandler

我在运行下面代码时,发现会打印 2 次

import logging.config
logging.config.fileConfig('logging.conf')
logging.getLogger('notification').warning('hello')

我一直不明白,后来看了 logging 的 docs 才知道为什么,链接贴出来,后面还要继续看https://docs.python.org/2.7/library/logging.html

答案就是这段话,logger notification 是 root 的下级,所以它的消息会在它本身和它的上级的 handler 都进行打印。
所以改成下面这样就可以了,或者把 handlers=空也可以。

[logger_notification]
level=WARNING
qualname=notification
handlers=consoleHandler,fileHandler
propagate=0

如果在加个 logger notification.a,这个 logger 就是 notification,root 的下级,它的消息会通过本身,上级,上上级来打印。level 也有层级考虑,如果自己没设置,就用上级的。如果设置了,就用自己的。


接着总结:
logging 有 4 个主要的 class,Logger,Handler, Filter,Formatter.
这 4 个类在产生 log 时遵循下面的流程。(图上的字有点小)

Logger01 = logging.getLogger('name')      #name可以不写,默认是root,同一个名字的Logger对象用同一套设置。
Logger01.setLevel(logging.DEBUG)         #如果不设置,默认是WARNING等级

hand01 = logging.StreamHandler()           #hand01是一个handler对象,为了让Logger对象可以设置        
Logger01.addHandler(hand01)                 #Logger对象如果不设置是默认console输出

FORM01 = '%(asctime)s - %(name)s - %(message)s'               #具体格式,让Formatter的对象设置,如果Formatter的对象不设置,则用默认格式
form01 = logging.Formatter(fmt=FORM01)                                
hand01.setFormatter(form01)                                                    #Handler对象设置Fommatter,用到了Fommatter的对象。Logger对象是不能直接设置Fomatter的,只能通过设置                                                           
                                                                                                      #Handler对象间接设置Fomatter

def should_log(record):                                                        #这个是一个具体过滤条件,record是原始filter方法要求带的参数,后面给出的具体过滤条件都只能有一个record, 
    if record.msg == 'right':                                                     #record.msg就是传入的信息
        return True
    else:
        return False

filter01 = logging.Filter()                                                          #创建Filter的对象
filter01.filter = should_log                                                        #用should_log方法覆盖filter01.filter方法,如果不覆盖,就用默认的filter方法,如果覆盖了,就用自己写的方法。
Logger01.addFilter(filter01)                                                      #给Logger对象添加filter,这个filter还可以拿给handler对象添加


Logger01.debug('right')                                                         #执行这个语句就会在console打印出right,因为debug是符合logger01的level的,并且,信息也是符合过滤条件的。
Logger01.debug('work')                                                        #不符合过滤条件,不会打印出来

总结就是,Fomatter 对象是拿给 Handler 对象设置,Filter 对象可以拿给 Handler 和 Logger 设置,Handler 对象是拿给 Logger 设置,所有信息都是通过语句 Logger.DEBUG('msg') 来实现 log 的,这里面的 DEBUG 是可以换成其他等级的。而设置等级是 Handler 和 Logger 都可以设置的。如果都设置了,那就要都通过才会输出。
handler 的设置默认是从孩子像祖先一层层查找的,如果都设置了,就会打印很多次,可以通过 propagate=0 来避免这种情况。


fileconfig 里面没有 filter 的配置,因为不支持


import logging.config
import yaml

with open('logging.yml', 'r') as f_conf:
    dict_conf = yaml.full_load(f_conf)

logging.config.dictConfig(dict_conf)

logger = logging.getLogger('simpleExample')                         #对应的是yaml文件中叫simpleExample的logger对象的配置
logger.debug('debug message')
logger.info('info message')
logger.warn('warn message')
logger.error('error message')
logger.critical('critical message')



logging.yml的内容如下
version: 1
formatters:
  simple:
    class: logging.Formatter
    format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
handlers:
  console:
    class: logging.StreamHandler
    level: DEBUG
    formatter: simple
    stream: ext://sys.stdout
  console_err:
    class: logging.StreamHandler
    level: ERROR
    formatter: simple
    stream: ext://sys.stderr
loggers:
  simpleExample:
    level: DEBUG
    handlers: [console]
    propagate: yes
  root:
    level: DEBUG
    handlers: [console_err]

这个例子比较好理解,里面的 key 都是固定的,不能随便变。
但是这个例子不含 filter,我也不知道怎么加。

我就改成了下面这样,加了 filter,yaml 中的内容不变。

import logging.config
import yaml

def should_log(record):
    if record.msg == 'right':
        return True
    else:
        return False

with open('logging.yml', 'r') as f_conf:
    dict_conf = yaml.full_load(f_conf)

logging.config.dictConfig(dict_conf)

fil01 = logging.Filter()
fil01.filter = should_log

logger = logging.getLogger('simpleExample')
logger.addFilter(fil01)

logger.debug('right')
logger.info('info message')
logger.warn('warn message')
logger.error('error message')
logger.critical('critical message')

出来的结果如下
2019-09-20 16:52:19,326 - simpleExample - DEBUG - right


↙↙↙阅读原文可查看相关链接,并与作者交流