logging模块是Python内置的一个强大易用的日志模块。
logger是一个树结构,默认有个根root,其他logger都是其上的枝桠,比如创建一个name=’A.B’的logger,其实际结构就是 root-A-B
再创建一个name=’A.C.D’的logger,结构变为:
在输出log的时候,不能所有日志都输出出来,而是有所选择,比如有时候我们只希望看到warning以上严重程度的,如果有太多info、debug会让log可读性变得很差。
我们可以通过设置logger的level来实现这一点。在logging中,将level设置如下:
LevelName | LevelValue |
---|---|
CRITICAL/ FATAL | 50 |
ERROR | 40 |
WARNING/ WARN | 30 |
INFO | 20 |
DEBUG | 10 |
NOTSET | 0 |
示例:
import logging
logger = logging.getLogger('test')
logging.basicConfig() # basicConfig是logging提供的简单的配置方法,不用basicConfig则需要手动添加handler
logger.setLevel(logging.INFO) # 输出所有大于等于INFO级别的log
logger.info('I am <info> message.')
logger.debug('I am <debug> message.') # 不输出
输出:
INFO:test:I am <info> message.
要注意的是: root 的默认级别是 WARNING!, 而且logger实际输出时的level是取决于EffectiveLevel,即从该级往上走,遇到的第一个level不为0的logger的level,也就是说如果你创建了logger,而没有为其设置level,那它默认是NOTSET,程序会往上层找,直到root,而root级别是WARNING,所以可能会导致没有输出日志。
我们写日志一个很重要的问题就是把日志输出到什么地方去,我们可能希望某些日志在console打印出来,可能希望有更详细的日志输出到log文件里去。怎么控制这些输出就需要用handler了。
import logging
logger = logging.getLogger('test')
logger.addHandler(logging.StreamHandler()) # 添加StreamHandler
logger.setLevel(logging.INFO) # 输出所有大于INFO级别的log
logger.info('I am <info> message.')
logger.debug('I am <debug> message.') # 不输出
我们把上面的例子稍微改动了一下,可以看到输出如下,输出到了console里。在pycharm中都是显示在输出窗口中。只不过缺少了INFO:的级别类型
I am <info> message.
这就是logging提供的最基本的一个handler,其他各种handler都是从这个handler继承发展来的。理论上可以把日志输出到各种流中,stderr、文件、socket等都可以。当然logging已经将各种流handler封装好了。
import logging
logger = logging.getLogger('test')
logger.addHandler(logging.StreamHandler())
logger.addHandler(logging.FileHandler('test.log')) # 再添一个FileHandler
logger.setLevel(logging.INFO) # 输出所有大于INFO级别的log
logger.info('I am <info> message.')
logger.debug('I am <debug> message.') # 不输出
可以看到,info不仅仅输出到了console中,还在当前文件夹下创建了一个test.log文件并输出到了该文件中。在logging.handlers中还封装了一堆更高级的handlers,可以了解下,尤其是RotatingFileHandler和TimedRotatingFileHandler,可以把你的日志按一定规则分割成多份。你也可以自己封装handler哦,网上有人这么干的。
上面我们看到了logger的级别,可以控制这个logger要输出什么级别的log。但这里我们发现可以在logger里添加handler,控制输出log到哪里,明显发现,其实我们想要在不同的handler里输出不同级别的日志。
比如我们想要在console里输出warning以上的日志,在log文件里输出debug以上的日志,该怎么办呢?
handler也是有级别的。
import logging
logger = logging.getLogger('test')
logger.setLevel(logging.INFO) # 输出所有大于INFO级别的log
# 添加StreamHandler,并设置级别为WARNING
stream_hdl = logging.StreamHandler()
stream_hdl.setLevel(logging.WARNING)
logger.addHandler(stream_hdl)
# 添加FileHandler,并设置级别为DEBUG
file_hdl = logging.FileHandler('test.log')
file_hdl.setLevel(logging.DEBUG)
logger.addHandler(file_hdl)
logger.info('I am <info> message.')
logger.debug('I am <debug> message.') # 不输出
logger实例的log()函数,例如info()和debug()函数先针对logger实例的级别进行过滤,不超过logger实例级别的log()函数,不会被加载到logger实例中。
在这段代码中logger实例的等级为INFO,所以info()和debug()函数只有info函数会被记录到logger实例中。
logger实例中记录的log()函数再被StreamHandler和FileHandler根据各自的级别进行筛选。
这段代码中StreamHandler级别为WARNING,而FileHandler的级别为DEBUG,所以记录在logger实例中的info()函数只在FileHandler中输出,也就是只有在test.log函数中输出。
I am <info> message.
细心的话可以发现,我们后来自己添加的handler输出的log是没有格式的,就仅仅是输出而已。但basicConfig()输出的log是有格式的(虽然很丑)。
不同在于basicConfig()中的handler是带有formatter的。我们要添加formatter就需要用到logging中的另一个类Formatter
import logging
logger = logging.getLogger('test')
logger.setLevel(logging.DEBUG) # 输出所有大于INFO级别的log
fmt = logging.Formatter('%(name)s - %(levelname)s - %(asctime)s - %(message)s')
# 添加StreamHandler,并设置级别为WARNING
stream_hdl = logging.StreamHandler()
stream_hdl.setLevel(logging.DEBUG)
stream_hdl.setFormatter(fmt)
logger.addHandler(stream_hdl)
# 添加FileHandler,并设置级别为DEBUG
file_hdl = logging.FileHandler('test.log')
file_hdl.setLevel(logging.DEBUG)
file_hdl.setFormatter(fmt)
logger.addHandler(file_hdl)
logger.info('I am <info> message.')
logger.debug('I am <debug> message.') # 不输出
是不是漂亮多了,logging的formatter可以输出的不止这几个信息,还有很多:
我们可以通过多种途径配置logger,像上面用过的basicConfig()(没有用参数,可以自己阅读源码,很简单的几个参数),或者像上面的例子手动添加handler、设置formatter,logging还给了我们其他高级的配置方法:
logging.config里有多种配置方法,像fileConfig,可以直接读一个文件;像dictConfig,可以传一个dict进行配置(django就是这么配置的);似乎还能起个socket,来实时修改config,听起来很吊的样子。