logging
模块的日志,我们需要明白四个内容:
- 日志收集器
- 日志级别
- 输出渠道
- 日志内容
1. 日志的级别
在logging
模块中,对日志的输出级别有着划分,分别是:DEBUG/INFO/WARNING/ERROR/CRITICAL
这五种级别。
如果你想要设置你的日志输出级别是DEBUG
,那么logging
模块会全面输入所有的日志;如果设置的日志输出级别是INFO
,那么只会输出级别在INFO
及以上的日志内容;其余的日志级别输出也是一样的。
2. 日志收集器:自定义个人的日志收集器
日志收集器是用来收集日志并输出的,logging
中有一个默认的日志收集器,这个默认的日志收集器设置的日志的输出级别是WARNING
级别,并且日志的名字是root
,输出的格式也是一个默认的格式。
也就是说,如果你的日志级别是INFO
默认的日志收集器是不会输出日志的。
import logging
logging.warning("哈哈") # WARNING:root:哈哈
2.1 创建日志收集器
import logging
# 传入一个名字,作为这个日志收集器的名字
logger = logging.getLogger("admin")
此时,这个日志收集器没有日志级别,没有输出渠道,没有输出的日志内容格式。
所以我们需要去设置这些内容。
2.2 设置日志级别
import logging
# 创建一个日志收集器
logger = logging.getLogger("admin")
# 设置输出级别
logger.setLevel(logging.DEBUG)
定义日志级别的时候,调用setLevel()
方法,并传入日志的级别。
日志级别在logging
模块中有专门的常量,用来设置日志级别:
logging.DEBUG
logging.INFO
logging.WARNING
logging.ERROR
logging.CRITICAL
设置了日志的级别,但是依然输出不了任何的日志,因为没有设置日志的输出的渠道。
2.3 设置输出渠道
日志的输出渠道有两种,一个是控制台输出,一个是文件输出。
控制台输出:logging.StreamHandler()
文件输出:logging.FileHandler(filepath)
。文件输出路径需要传递一些参数,例如文件路径,编码格式等。
2.4 设置输出格式
输出格式在logging
的源码中,给出了输出的内容信息:
%(name)s 日志收集器的名字
%(levelno)s 数字形式的日志级别
%(levelname)s 文本形式的日志级别
%(pathname)s 调用日志输出函数的模块的完整路径名,可能没有
%(filename)s 调用日志输出函数的模块的文件名
%(module)s 调用日志输出函数的模块名
%(funcName)s 调用日志输出函数的函数名
%(lineno)d 调用日志输出函数的语句所在的代码行
%(created)f 当前时间,用UNIX标准的表示时间的浮 点数表示
%(relativeCreated)d 输出日志信息时的,自Logger创建以 来的毫秒数
%(asctime)s 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒
%(thread)d 线程ID。可能没有
%(threadName)s 线程名。可能没有
%(process)d 进程ID。可能没有
%(message)s 用户输出的消息
设置输出格式:
fmt = "%(asctime)s %(name)s %(levelname)s %(filename)s %(lineno)d line %(message)s"
fortmatter = logging.Formatter(fmt)
2.5 将输出渠道与输出格式进行绑定
handle = logging.StreamHandler() # 控制台输出
# logging.FileHandler() # 文件输出
# 设置输出格式
fmt = "%(asctime)s %(name)s %(levelname)s %(filename)s %(lineno)d line %(message)s"
fortmatter = logging.Formatter(fmt)
# 将输出渠道和输出格式绑定
handle.setFormatter(fortmatter)
2.6 将输出渠道添加到日志收集器中
# 设置输出渠道
handle = logging.StreamHandler() # 控制台输出
# logging.FileHandler() # 文件输出
# 设置输出格式
fmt = "%(asctime)s %(name)s %(levelname)s %(filename)s %(lineno)d line %(message)s"
fortmatter = logging.Formatter(fmt)
# 将输出渠道和输出格式绑定
handle.setFormatter(fortmatter)
# 将设置好的渠道,添加到日志收集器中,完成自定义日志收集器
logger.addHandler(handle)
这里指的是已经设置好输出格式的输出渠道,将这歌输出渠道添加到日志收集器中,这个自定义的日志收集器就设置好了。
完整代码:
import logging
# logging.warning("哈哈")
# 创建一个日志收集器
logger = logging.getLogger("admin")
# 设置输出级别
logger.setLevel(logging.DEBUG)
# 设置输出渠道
handle = logging.StreamHandler() # 控制台输出
# logging.FileHandler() # 文件输出
# 设置输出格式
fmt = "%(asctime)s %(name)s %(levelname)s %(filename)s %(lineno)d line %(message)s"
fortmatter = logging.Formatter(fmt)
# 将输出渠道和输出格式绑定
handle.setFormatter(fortmatter)
# 将设置好的渠道,添加到日志收集器中,完成自定义日志收集器
logger.addHandler(handle)
logger.debug("haha")
同一个日志收集器可以有多个输出渠道,并且每个输出渠道的输出日志级别都可以不相同。
2.7 对渠道的输出级别进行调控
当设置了一个输出渠道,那么就可以单独对这个输出渠道进行设置输出的 日志级别。
但是设置的输出级别,是不能超过自定义的日志收集器的输出级别的。
# 设置输出渠道
handle = logging.StreamHandler() # 控制台输出
# logging.FileHandler() # 文件输出
handle.setLevel(logging.INFO)
这样一来,在控制台中,就只会输出INFO
级别的日志。
封装一个属于自己的日志类:
import logging
class MyLogger(object):
def __init__(self, filename=None, level=logging.DEBUG):
# 初始化一个日志收集器
self.logger = logging.getLogger("admin")
# 初始化日志输出格式
self.fmt = "%(asctime)s %(name)s %(levelname)s %(filename)s-%(lineno)d line %(message)s"
# 设置日志收集器级别
self.logger.setLevel(level)
self.level = level
self.filename = filename
self.handle = None
def private_logger(self, level, msg):
# 设置输出渠道
if self.filename:
self.handle = logging.FileHandler(self.filename, encoding="utf-8")
else:
self.handle = logging.StreamHandler()
# 设置输出格式
formatter = logging.Formatter(self.fmt)
# 日志收集器与输出格式绑定
self.handle.setFormatter(formatter)
# 将 输出渠道添加到日志收集器中
self.logger.addHandler(self.handle)
# 使用反射原理,执行日志输出
if hasattr(self, level):
func = getattr(self, level)
func(msg)
# 移除添加的输出handle:在执行测试用例的时候,都会调用一次private_logger,而此时每一个logger中都已经有了一个handle,所以重复调用private_logger都会重复向同一个logger中添加同样的handle,造成日志输出重复。
# 所以需要在private_logger函数的末尾处,移除handle来防止日志重复。
self.logger.removeHandler(self.handle)
def debug(self, msg):
self.logger.debug(msg)
def info(self, msg):
self.logger.info(msg)
def warning(self, msg):
self.logger.warning(msg)
def error(self, msg):
self.logger.error(msg)
def critical(self, msg):
self.logger.critical(msg)
def exception(self, msg):
self.logger.exception(msg)
logger = MyLogger()
logger.private_logger("exception", "断言失败")
# logger.exception()
封装自定义日志类可能出现的问题
1. 重复输出日志
日志重复输出:Python Logging日志重复输出问题
这是因为在执行测试用例的时候,每一次调用封装的日志类,都会在同一个logger
中多次添加同一个handle
,造成日志输出重复。
所以在每次执行测试用例之前,都需要先移除logger
中已有的handle
,再去重新添加handle
来防止日志输出重复。