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函数生效的问题。