共计 3500 个字符,预计需要花费 9 分钟才能阅读完成。
logging 根本介绍
先介绍一下咱们为什么要应用日志,平时咱们编写程序为了验证程序运行与 debug,通常会应用 print 函数来对一些两头后果进行输入验证,在验证胜利后再将 print 语句正文或删除掉。这样做在小型程序中还比拟灵便,然而对于大型项目来说,就非常繁琐了 —–> 所以应用日志 log 就很天然了,日志能够调整日志级别,来决定咱们是否输入对应级别的日志,同时还能够将日志导入文件记录下来。
再介绍一下 logging 中的 log 级别:
- Level Numeric Value
- logging.CTITICAL 50
- logging.ERROR 40
- logging.WARNING 30
- logging.INFO 20
- logging.DEBUG 10
即其实如 type(logging.INFO)是一个整数。
模块级的应用办法
logging 模块的模块级应用办法就是应用一些模块级接口函数。而且还有一个比拟重要的是对日志的输入模式和输入目的地等进行设置。
接口函数也是对应日志级别而输入信息的 :
logging.debug(msg)
logging.info(msg)
logging.warning(msg)
logging.error(msg)
logging.critical(msg)
这几个函数除了日志级别上的区别,其实都是应用默认的 root logger 来对信息进行 log 的,它是处于日志器层级关系最顶层的日志器,且该实例是以单例模式存在的。见源码:
def info(msg, *args, **kwargs):
if len(root.handlers) == 0:
basicConfig()
root.info(msg, *args, **kwargs)
这里能够见到在 logging 模块的 info 函数中:(1)首先进行了一个对于 root logger 的 handlers 属性的长度判断是否调用 basicConfig 函数。(2)之后是调用 root logger 的 info 函数来实现性能的。
这里对于第(2)点咱们进一下探寻:
root = RootLogger(WARNING)
在 logging 的源码中能够看到如上语句,即咱们将 logging 模块 import 后,其实曾经默认的创立了一个 root logger 对象,并且之后咱们本人创立的 logger 都是 root logger 的子类。
日志的设置
对于日志的设置,咱们是应用 logging.basicConfig(**kwargs)函数,它是对 root logger 进行设置的,个别应用较多的关键字参数如下:
- level 决定 root logger 的日志级别。
- format 它是日志格局字符串,指定日志输入的字段信息,如日期,日志级别,文件名,当然还有咱们的 msg 信息等等。
- datefmt 决定日期字段的输入格局。
- filename 日志信息的输入目的地文件,不指定时默认输入到控制台。
- filemode 目的地文件的关上模式,不指定默认为 ”a”。
对于 logging.basicConfig 函数有一点须要留神:咱们不能在该函数前应用任何模块级日志输入函数如 logging.info、logging.error,因为它们会调用一个不带参的 basicConfig 函数,使得 logging.basicConfig 函数生效 。见源码( 因为代码过多,倡议参考正文进行浏览):
def basicConfig(**kwargs):
_acquireLock()
try:
#这里因为不带参调用 basicConifg,#而 root.handlers 默认为空列表
#在 Logger 定义中可见 self.handlers 被设为[],#而默认的 root 实例在创立时只指定了 log 级别
#所以 if 条件必然通过
if len(root.handlers) == 0:
#因为不带参,所以 handlers 必为 None
handlers = kwargs.pop("handlers", None)
if handlers is None:
#这里因为不带参,所以即是 handlers 为 None
#通过下面的 if 判断,但 kwargs 同样为 None,#所以该 if 不通过
if "stream" in kwargs and "filename" in kwargs:
raise ValueError("'stream' and 'filename' should not be ""specified together")
else:
if "stream" in kwargs or "filename" in kwargs:
raise ValueError("'stream' or 'filename' should not be ""specified together with'handlers'")
#这里因为 handlers 为 None 通过 if 判断继续执行
if handlers is None:
filename = kwargs.pop("filename", None)
mode = kwargs.pop("filemode", 'a')
if filename:
h = FileHandler(filename, mode)
#不带参,kwargs 为 None,所以 filename
#在下面的执行语句的返回值为 None,所以
#执行这个 else 分支
else:
stream = kwargs.pop("stream", None)
h = StreamHandler(stream)
#留神这里,非常重要,可见 handlers 终于不为 None
#被赋予了一个列表,该列表有一个元素 h
handlers = [h]
dfs = kwargs.pop("datefmt", None)
style = kwargs.pop("style", '%')
if style not in _STYLES:
raise ValueError('Style must be one of: %s' % ','.join(_STYLES.keys()))
fs = kwargs.pop("format", _STYLES[style][1])
fmt = Formatter(fs, dfs, style)
#再看这里,非常重要
for h in handlers:
#这个无所谓,就是对 format 进行默认设置
if h.formatter is None:
h.setFormatter(fmt)
#这里最为要害,可见 root.addHandler(h)函数
#会把 h 增加进 root.handlers 列表中,那么很显然
#root.handlers 不再是一个空列表
root.addHandler(h)
level = kwargs.pop("level", None)
if level is not None:
root.setLevel(level)
if kwargs:
keys = ','.join(kwargs.keys())
raise ValueError('Unrecognised argument(s): %s' % keys)
finally:
_releaseLock()
所以即是不带参调用 basicConfig(),然而通过其函数体执行,root.handlers 的列表长度会不为 0,所以之后再调用 logging.basicConifg 函数时,对 root.handlers 判断,就会因而而间接略过函数体中 try 局部(次要局部),间接执行 finally,没有进行任何设置。
对象级应用
在 logging 模块中 logger 对象素来都不是间接实例化,而是通过一个模块级借口实现:logging.getLogger(name=None),留神咱们创立的 logger 都是 root logger 的子类。而通过咱们本人创立的 logger 对象,应用日志记录也是和模块级接口一样的:
logger.debug(msg)
logger.info(msg)
logger.warning(msg)
logger.error(msg)
logger.critical(msg)
同样对于日志格局的设置也是通过 logging.basicConfig 函数实现的,尽管该函数是对 root logger 的日志格局设置,但因为咱们定义的 logger 类都是 root logger 的子类,所以便可能沿用该设置。并且对于对象级接口,如 logger.info 函数:
def info(self, msg, *args, **kwargs):
if self.isEnabledFor(INFO):
self._log(INFO, msg, args, **kwargs)
可见其中没有对 basicConfig 函数的调用,所以也就没有批改 root.handlers 列表,即不会产生上文的 logging.basciConfig 函数生效的问题。