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。
如果要调整文件输出日志的级别,修改这里
# 设置向文件输出的日志格式, 修改成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")