先介绍一下咱们为何要使用日志,日常咱们编写程序为了验证程序运行与debug,一般会使用print函数来对一些中间结果进行输出验证,在验证成功后再将print语句注释或删除掉。这样作在小型程序中还比较灵活,可是对于大型项目来讲,就十分繁琐了----->因此使用日志log就很天然了,日志能够调整日志级别,来决定咱们是否输出对应级别的日志,同时还能够将日志导入文件记录下来。函数
再介绍一下logging中的log级别:ui
Level | Numeric Value |
---|---|
logging.CRITICAL | 50 |
logging.ERROR | 40 |
logging.WARNING | 30 |
logging.INFO | 20 |
logging.DEBUG | 10 |
实际上这些level都是整数值,可由如type(logging.info)验证为int类型。debug
logging模块的模块级使用方法就是使用一些模块级接口函数。并且还有一个比较重要的是对日志的输出形式和输出目的地等进行设置。日志
接口函数也是对应日志级别而输出信息的:
logging.debug(msg)
logging.info(msg)
logging.warning(msg)
logging.error(msg)
logging.critical(msg)
这几个函数除了日志级别上的区别,其实都是使用默认的root logger来对信息进行log的,它是处于日志器层级关系最顶层的日志器,且该实例是以单例模式存在的。见源码:code
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)点咱们进一下探寻:orm
root = RootLogger(WARNING)
在logging的源码中能够看到如上语句,即咱们将logging模块import后,其实已经默认的建立了一个root logger对象,而且以后咱们本身建立的logger都是root logger的子类。对象
对于日志的设置,咱们是使用logging.basicConfig(**kwargs)函数,它是对root logger进行设置的,通常使用较多的关键字参数以下:接口
对于logging.basicConfig函数有一点须要注意:咱们不能在该函数前使用任何模块级日志输出函数如logging.info、logging.error,由于它们会调用一个不带参的basicConfig函数,使得logging.basicConfig函数失效。见源码(因为代码过多,建议参考注释进行阅读):ci
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函数失效的问题。