logging模块的日志,我们需要明白四个内容:

  1. 日志收集器
  2. 日志级别
  3. 输出渠道
  4. 日志内容

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来防止日志输出重复。