1、背景

大家应该都遇到过这种场景,你找到了一个bug但是不能复现。开发说,我加一下log,测的时候再观察下,出现问题及时告诉我。

log对debug至关重要,但并不是越多越好。关键代码一般都需要打印log,在出现问题时能够快速debug。日常使用时,一般重点关注error log。也可以将info级别的log也保存下来便于后续出问题时进行分析。

且log一般不会保留很长时间,只保留最近一段时间的log。

2、Log的级别

越往下级别越高
D(Debug):表示调试级别的日志信息
I(Information):表示信息级别的日志信息
W(Warning):表示警告级别的日志信息
E(Error):表示错误级别的日志信息

3、log的输出方式有两种

3.1 控制台输出

import logging
from logging import Logger


class MyLogger(Logger):

    def __init__(self):
        # 设置日志的名字、日志的收集级别
        super().__init__("test_api", logging.DEBUG)

        # 自定义日志格式(Formatter), 实例化一个日志格式类
        fmt_str = '%(asctime)s  %(levelname)s  %(filename)s : %(funcName)s  [line: %(lineno)s]  %(message)s'
        formatter = logging.Formatter(fmt_str)

        # 实例化控制台渠道(StreamHandle)
        sh = logging.StreamHandler()
        # 设置控制台输出的日志级别
        sh.setLevel(logging.INFO)
        # 设置渠道当中的日志显示格式
        sh.setFormatter(formatter)
        # 将渠道与日志收集器绑定起来
        self.addHandler(sh)
        sh.close()


# 实例化MyLogger对象,在其他文件直接使用log就能调用
log = MyLogger()


if __name__ == '__main__':
    log.error("this is a error log")
    log.info("this is a info log")
    log.debug("this is a debug log")
    log.warning("this is a warning log")

Result:
由于设置了控制台输入log的级别为INFO,所以INFO以及比INFO级别更高的log会输出在控制台。

2023-05-24 15:15:22,445  ERROR  my_logger.py : <module>  [line: 69]  this is a error log
2023-05-24 15:15:22,445  INFO  my_logger.py : <module>  [line: 70]  this is a info log
2023-05-24 15:15:22,445  WARNING  my_logger.py : <module>  [line: 72]  this is a warning log

Process finished with exit code 0

但日常使用时,一般控制台只输出ERROR级别的log。修改这里的代码:

sh.setLevel(logging.ERROR)

Result:再次执行,可见控制台只输出了errorlog

2023-05-24 15:29:20,465  ERROR  stream_logger.py : <module>  [line: 31]  this is a error log

Process finished with exit code 0

3.2 文件输出

将输出log到某个文件。
代码跑了很久,log也会很多,这时候一个文件肯定是不够的,并且保留太久之前的log也没有太大意义,因此当log达到最大字节长度,将自动backup 几个log文件。并且当所有log文件都达到最大长度时,只保留最新的log。【将文件字节长度改小一些,多次执行可以看到效果】

使用RotatingFileHandler()可以自动backup log文件

# 当log达到最大字节长度,将自动backup 几个log文件。并且当所有log文件都达到最大长度时,只保留最新的log
fh = handlers.RotatingFileHandler(all_log_path_file, maxBytes=10**2, backupCount=5, encoding="utf-8", mode="a")

代码如下:

import logging
from logging import Logger, handlers


class MyLogger(Logger):

    def __init__(self):

        # 获取日志文件路径
        all_log_path_file = "test.log"

        # 设置日志的名字、日志的收集级别
        super().__init__("test_api", logging.DEBUG)

        # 自定义日志格式(Formatter), 实例化一个日志格式类
        fmt_str = '%(asctime)s  %(levelname)s  %(filename)s : %(funcName)s  [line: %(lineno)s]  %(message)s'
        formatter = logging.Formatter(fmt_str)

        # 实例化文件渠道(FileHandle)
        '''
        创建一个文件实例,如果 api_test.log 文件不存在,就会自动创建;
        mode 参数设置为追加;另外为防止乱码, encoding 参数设置为 utf-8 编码格式
        '''
        # 当log达到最大字节长度,将自动backup5个log文件。当5个log文件都达到最大长度时,将只保留最新的log。
        fh = handlers.RotatingFileHandler(all_log_path_file, maxBytes=10**3, backupCount=5,
                                               encoding="utf-8", mode="a")
        # 设置向文件输出的日志格式
        fh.setLevel(logging.DEBUG)
        # 设置渠道当中的日志显示格式
        fh.setFormatter(formatter)
        # 加载文件实例到 logger 对象中
        self.addHandler(fh)
        # 关闭文件
        fh.close()


# 实例化MyLogger对象,在其他文件直接使用log就能调用
log = MyLogger()


if __name__ == '__main__':
    log.error("this is a error log")
    log.info("this is a info log")
    log.debug("this is a debug log")
    log.warning("this is a warning log")

Result:

打开log文件,可以看到打印的log。

log 创建不出来 python_开发语言

如果要调整文件输出日志的级别,修改这里

# 设置向文件输出的日志格式, 修改成error级别
fh.setLevel(logging.ERROR)

4、代码封装

  • 在控制台输出ERROR级别的log
  • 在log文件中输出DEBUG级别的log
  • 将ERROR级别的log单独输出在一个文件中
  • 当log达到最大字节长度时,将自动backup5个log文件。当5个log文件都达到最大长度时,将只保留最新的log

代码如下:

import logging
import os
from logging import Logger, handlers
from config.settings import get_log_path


class MyLogger(Logger):

    def __init__(self):
        # log_name = '{}.log'.format(time.strftime("%Y_%m_%d_%H_%M_%S", time.localtime()))
        # log_path_file = os.path.join(get_log_path(), log_name)

        # 获取日志文件路径
        all_log_path_file = os.path.join(get_log_path(), "api_test.log")
        error_log_path_file = os.path.join(get_log_path(), "error.log")

        # 设置日志的名字、日志的收集级别
        super().__init__("test_api", logging.DEBUG)

        # 自定义日志格式(Formatter), 实例化一个日志格式类
        fmt_str = '%(asctime)s  %(levelname)s  %(filename)s : %(funcName)s  [line: %(lineno)s]  %(message)s'
        formatter = logging.Formatter(fmt_str)
        # formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

        # 实例化控制台渠道(StreamHandle)
        sh = logging.StreamHandler()
        # 设置控制台输出的日志级别
        sh.setLevel(logging.ERROR)
        # 设置渠道当中的日志显示格式
        sh.setFormatter(formatter)
        # 将渠道与日志收集器绑定起来
        self.addHandler(sh)

        # 实例化文件渠道(FileHandle)
        # fh = logging.FileHandler(log_path_file, mode='a', encoding="utf-8")
        '''
        创建一个文件实例,如果 api_test.log 文件不存在,就会自动创建;
        mode 参数设置为追加;另外为防止乱码, encoding 参数设置为 utf-8 编码格式
        '''
        fh = handlers.RotatingFileHandler(all_log_path_file, maxBytes=10**6, backupCount=5,
                                               encoding="utf-8", mode="a")
        # 设置向文件输出的日志格式
        fh.setLevel(logging.DEBUG)
        # 设置渠道当中的日志显示格式
        fh.setFormatter(formatter)
        # 加载文件实例到 logger 对象中
        self.addHandler(fh)
        # 当log达到最大字节长度,将自动backup5个log文件。当5个log文件都达到最大长度时,将只保留最新的log。
        fh1 = handlers.RotatingFileHandler(error_log_path_file, maxBytes=10 ** 6, backupCount=5,
                                          encoding="utf-8", mode="a")
        # 设置向文件输出的日志格式
        fh1.setLevel(logging.ERROR)
        # 设置渠道当中的日志显示格式
        fh1.setFormatter(formatter)
        # 加载文件实例到 logger 对象中
        self.addHandler(fh1)

        fh.close()
        fh1.close()
        sh.close()


# 实例化MyLogger对象,在其他文件直接使用log就能调用
log = MyLogger()


if __name__ == '__main__':
    log.error("this is a error log")
    log.info("this is a info log")
    log.debug("this is a debug log")
    log.warning("this is a warning log")