Skip to content

Latest commit

 

History

History
212 lines (160 loc) · 7.17 KB

logging5.md

File metadata and controls

212 lines (160 loc) · 7.17 KB

Python的日志系统(五)

上一期内容我们主要介绍了如何实现一个网络日志器,本期我们介绍logger对象的层级关系。

Root Logger

在前面几期内容中,我们发现,logging默认的日志器名称是root。实际上,logging中日志器是以层级关系来组织的,而根日志器就是root,每一级之间以点运算符相隔,非常类似于Python的模块层级关系。并且,如果我们通过getLogger来获取logger时采用__name__为参数(__name__是什么?),那么得到的logger就是以模块的层级关系来组织的,并且,所有的logger最终会指向root这一个根日志器。

# 如下模块结构
# main.py
# src
# ├── alpha
# │   └── logger.py
# ├── beta
# │   └── logger.py
# └──── __init__.py

# src/alpha/logger.py
import logging

logger = logging.getLogger(__name__)
logger.info("src/alpha/logger")

# src/__init__.py
import logging

logger = logging.getLogger(__name__)
logger.info("src")

# main.py
import logging
logging.basicConfig(
	level=logging.DEBUG
)
logger.logging.getLogger()
logger.info("main")

import src.logger
import src.alpha.logger

# console output
# INFO:root:main
# INFO:src.alpha.logger:src/alpha module
# INFO:src:src module

可以看到,main.py中未指定名称的logger默认为root,而子模块中的logger名称正是模块的层次名称。

那么,层级关系有什么用呢?它允许我们层次化管理日志的内容。下层的日志需要向上层传递,直至最终到达root,在过程中,我们可以对不同层级的logger定义不同的处理方式:

# src/alpha/logger.py
import logging

logger = logging.getLogger(__name__)
logger.critical("critical message will be handled twice")
logger.info("info message will be handled only by root")

# src/__init__.py
import logging

logger = logging.getLogger(__name__)
handler = logging.StreamHandler()
fmt = logging.Formatter("src || %(levelname)s:%(name)s - %(message)s")
handler.setFormatter(fmt)
handler.setLevel(logging.WARNING)
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)

# main.py
import logging
logging.basicConfig(
	level=logging.DEBUG,
    format="root || %(name)s:%(levelname)s - %(message)s"
)

import src.alpha.logger

运行main.py,结果是:

src || CRITICAL:src.alpha.logger - critical message will be handled twice
root || src.alpha.logger:CRITICAL - critical message will be handled twice
root || src.alpha.logger:INFO - info message will be handled only by root

可以看到,CRITICAL消息被srcroot输出了两次,而INFO消息只被root输出了一次。这是因为srchandler等级设置为了WARNING,而root的等级设置为了DEBUG

有效等级

在上面的例子中,我们将srclogger的等级由DEBUG改为WARNING,再重新运行main.py看一下结果:

# src/__init__.py
...
logger.setLevel(logging.WARNING)

# Output
src || CRITICAL:src.alpha.logger - critical message will be handled twice
root || src.alpha.logger:CRITICAL - critical message will be handled twice

可以看到,INFO消息没有出现。这是因为INFO低于了logger的有效等级。每一个logger都有一个有效等级。如果logger通过setLevel设置了除NOTSET以外的等级,那么有效等级就是所设置的等级;如果logger没有设置,则向root的方向遍历,第一个设置了除NOTSET以外等级的祖先logger所设置的等级,就是这个logger的有效等级。可见,如果整条路径上都没有设置过等级,那么有效等级就是root的等级,默认为WARNING

# 目录结构
# main.py
# src
# ├── alpha
# │   ├── gamma
# │   │   └── logger.py
# │   └── __init__.py
# └── __init__.py

# src/alpha/gamma/logger.py
import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
print(logger.getEffectiveLevel())

# src/alpha/__init__.py
import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.CRITICAL)

# src/__init__.py
import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

# main.py
import src.alpha.gamma.logger

直接运行main.py,会输出20,也就是INFO级别所对应的整数。如果我们将logger.py中的setLevel注释再运行,则会输出它的父logger的等级,也就是CRITICAL对应的50。再将alpha/__init__.py注释,会继续向上一级logger遍历,也就是DEBUG的级别10。如果将src/__init__.py注释,则最终到达了root,它的默认级别是WARNING,所以会输出30

此外,如果我们显式设置等级为NOTSET,它仍旧会被忽略,并继续向上遍历。如果直到root也被设置为NOTSET,那么最终会输出0

# src/alpha/gamma/logger.py
import logging

logger = logging.getLogger(__name__)
logger.setLevel(logging.NOTSET)
print(logger.getEffectiveLevel())

# 0

实际上,EffectiveLevel表明了由该logger所产生的日志,能够被处理的最低等级logger的等级表示该logger只会处理大于等于该等级的日志,并且,日志会从子logger一级一级向root传递,所以,EffectiveLevel就定义了日志消息在整条logger链上通过所需要的等级。例如,logger的有效等级是INFO,那么,由logger产生的DEBUG消息就不会被链上的任何logger处理,而INFO消息则会被某一个logger处理。

我们可以通过isEnabledFor方法,查询一个logger能否处理某一个级别的日志消息:

import logging

logger = logging.getLogger(__name__)

# 此时依靠getEffectiveLevel的结果来判断
# 即,root的默认等级WARNING
print(logger.isEnabledFor(logging.CRITICAL))
# True
print(logger.isEnabledFor(logging.WARNING))
# True
print(logger.isEnabledFor(logging.INFO))
# False

logger.setLevel(logging.ERROR)

print(logger.isEnabledFor(logging.CRITICAL))
# True
print(logger.isEnabledFor(logging.WARNING))
# False
print(logger.isEnabledFor(logging.INFO))
# False

logging模块还提供了全局禁用某一等级以下消息的函数disable,这一禁用将适用于程序中任何一个logger

# src.alpha.gamma.logger
import logging

def run():
    logger = logging.getLogger(__name__)
    logger.info("This is an info log")
    logger.error("This is an error log")

# main.py
import logging
import src.alpha.gamma.logger as sagl
logging.basicConfig(
	level=logging.INFO,
    format="root || %(name)s:%(levelname)s - %(message)s"
)

sagl.run()
logging.disable(logging.WARNING)
sagl.run()

输出:

root || src.alpha.gamma.logger:INFO - This is an info log
root || src.alpha.gamma.logger:ERROR - This is an error log   
root || src.alpha.gamma.logger:ERROR - This is an error log

可以看到,INFO消息被disable禁掉了,虽然root本身的等级允许INFO消息被处理。