Log日志模块
Python 的 logging
模块提供了几个不同的日志级别,每个级别表示不同的日志严重性。以下是这些日志级别及其解释,从最详细到最少详细的顺序:(debug<info<warning<error<critical
)
-
DEBUG (10): 最详细的日志级别,通常用于诊断问题,记录程序的每个细节。适用于开发阶段。
-
INFO (20): 记录正常运行的信息,用于确认程序按预期运行。适用于记录程序的主要事件和状态更新。
-
WARNING (30): 记录潜在的问题或可能导致未来错误的情况。通常不会阻止程序的执行,但可能需要关注。
-
ERROR (40): 记录运行时错误或异常,通常会影响程序的某些功能。需要修复的问题,但不一定会导致程序终止。
-
CRITICAL (50): 记录严重错误,通常会导致程序无法继续运行。表示非常严重的问题,程序需要立即修复。
logging模块默认日志级别为WARNING,只打印级别在warning及以上的级别日志
Logging默认日志模块
在python当中提供了默认的日志输出模块longging,我们可以调用logging方法实现不同级别日志的打印,如下默认输出到控制台
logging.debug("debug...") logging.info("info...") logging.warning("warning...") logging.error("error...") logging.critical("critical...")
我们可以调用logging.basicConfig()方法定义logging,如定义logging的日志格式,将logging信息打印到日志文件
-
filename='log.txt'
指定日志文件路径
-
filemode='w'/‘a’ filemode=w会
覆盖之前的日志文件
,为a时不存在日志文件则创建日志文件
,如果存咋则追加信息到日志文件
-
logging日志名称为root
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', filename='log.txt', filemode='w')
日志格式参数如下
%(levelno)s:打印日志级别的数值 %(levelname)s:打印日志级别的名称 %(pathname)s:打印当前执行程序的路径,其实就是sys.argv[0] %(filename)s:打印当前执行程序名 %(funcName)s:打印日志的当前函数 %(lineno)d:打印日志的当前行号 %(asctime)s:打印日志的时间 %(thread)d:打印线程ID %(threadName)s:打印线程名称 %(process)d:打印进程ID %(message)s:打印日志信息
Logger自定义日志
logging.getLogger(logger_name) 调用logging的getLogger方法返回一个Logger自定义日志类型的对象,可以自定义日志名称,当再次调用相同logger名称的getLogger方法时,返回相同的logger实例(类似于一个单例设计模式)
# 自定义logger,logging.getLogger()返回一个自定义logger对象实例,test_logger表示自定义logger名称,全局logger和logging名称为root,当logger名称相同时,返回相同的logger实例 test_logger = logging.getLogger("test_logger") test_logger.setLevel(logging.DEBUG)
handler可以理解为是logger的配置,一个logger可以add多个handler
# 定义文件输出handler file_handler = logging.FileHandler('test_logger.txt', mode='w') # 设置日志级别 file_handler.setLevel(logging.DEBUG) file_handler.setFormatter(logging.Formatter('%(name)s - %(levelname)s - %(message)s')) # 添加file_handler到test_logger test_logger.addHandler(file_handler) # 定义控制台输出handler console_handler = logging.StreamHandler() # 设置日志级别 console_handler.setLevel(logging.DEBUG) console_handler.setFormatter(logging.Formatter('%(name)s - %(levelname)s - %(message)s')) # 添加console_handler到test_logger test_logger.addHandler(console_handler) # 调用自定义logger打印日志 test_logger.error("test_logger error") test_logger.debug("test_logger debug")
为什么logger和file_handler、console_handler都需要分别设置日志级别呢
在 Python 的 logging
模块中,logger 和 handler 都有各自的日志级别设置,这是为了提供更细粒度的控制。
-
Logger 的日志级别:控制这个
Logger
本身记录哪些级别的日志。它相当于日志的总开关,任何低于这个级别的日志都会被过滤掉,不会被处理器处理。 -
Handler 的日志级别:控制日志输出的细节。负责将允许的日志实际记录下来。即使
Logger
允许生成某个级别的日志,Handler
也可以进一步限制它实际输出的日志类型。
双重控制机制能够让你在不同的输出目标(如文件或控制台)上有更大的灵活性。例如,你可能希望:
-
Logger 捕获所有的日志级别。
-
控制台 只显示
INFO
以上的消息。 -
日志文件 记录
DEBUG
以上的所有消息。
日志的继承
在 Python 的日志系统中,logger
是按层级结构组织的。子 logger 会自动继承父 logger 的配置,除非你显式地修改子 logger 的某些配置。
首先我们自定义父logger logger = logging.getLogger("main")
配置文件conf.ini [log] log_level = INFO log_format = "%%(asctime)s - %%(name)s - %%(filename)s[line:%%(lineno)d] - %%(levelname)s - %%(message)s"
parseConf = ParseConf(CONF_PATH) _log_level = parseConf.get_value("log", "log_level") _log_format = parseConf.get_value("log", "log_format") _log_file = LOG_PATH def log_init(): logger = logging.getLogger("main") logger.setLevel(level=_log_level) formatter = logging.Formatter(_log_format) handler = TimedRotatingFileHandler( filename=_log_file, when="D", interval=1, backupCount=7 ) handler.setLevel(_log_level) handler.setFormatter(formatter) logger.addHandler(handler) console = logging.StreamHandler() console.setLevel(_log_level) console.setFormatter(formatter) logger.addHandler(console)
当你创建 logger = logging.getLogger("main.jd")
时,"main.jd"
是 "main"
logger 的子 logger。子 logger 会继承父 logger 的日志级别和handler,即 main
的 logger
已经配置了日志级别、文件和控制台的 handler,子 logger "main.jd"
也会自动继承这些配置。
当 logger = logging.getLogger("main.jd")
生成日志时,日志会沿着 logger 层级链上传到父 logger,最终由父 logger 的 handler 来处理日志输出。
if __name__ == "__main__": logger1 = logging.getLogger("main") logger2 = logging.getLogger("main.jd") logger2 = logging.getLogger("main.bd") # 以上main.js和main.bd都适用于main规则,相当于其子模块,我们可以使用(日志.xxx)的方式去区分日志模块 logger1.info("logger1 info ...") logger2.error("logger1 error ...") logger1.warning("logger1 warning ...") logger1.debug("logger1 debug ...")
以上创建的日志对象main.js和main.bd都适用于main规则,相当于其子模块,我们可以使用(日志.xxx)的方式去区分日志模块
"2024-10-12 10:20:20,816 - main - log.py[line:39] - INFO - logger1 info ..." "2024-10-12 10:20:20,816 - main.jd - log.py[line:40] - ERROR - logger2 error ..." "2024-10-12 10:20:20,817 - main - log.py[line:41] - WARNING - logger1 warning ..." "2024-10-12 10:20:20,817 - main.bd - log.py[line:43] - INFO - logger3 info ..." "2024-10-12 10:20:20,817 - main.bd - log.py[line:44] - INFO - logger3 info ..."
日志异常捕获
日志异常捕获可以调用logging和自定义logger对象的exception方法,如下所示:
try: 1 / 0 except Exception as e: test_logger.exception(f"get exception{e}") logging.exception(f"get exception{e}")
相比于如下阶段
try: ... except Exception as e: self.logger.error(e) # TypeError: 'Session' object is not callable
exception(e)能打印详细的异常日志
try: ... except Exception as e: self.logger.exception(e) 打印信息如下: ERROR - 'Session' object is not callable" Traceback (most recent call last): File "C:\Users\v-williamqiu\Desktop\wx\workspace\api_test\testcase\test_api.py", line 79, in test_get_phpwind res = self.sess("get", url=urls) TypeError: 'Session' object is not callable
日志分割和日志滚动
在自动化测试框架中,日志记录对于调试、追踪问题、记录测试执行过程至关重要。日志分割(log splitting)和日志滚动(log rotation)是日志管理中的两个重要概念,主要用于避免日志文件过大,方便维护和分析。
1. 日志分割(Log Splitting)
日志分割是指根据一定的策略将日志输出分割为多个不同的日志文件。通常会根据模块、功能或测试用例的不同,创建单独的日志文件,以便针对特定测试或功能调试。
示例:
-
按功能模块分割:每个模块(如登录模块、注册模块)生成单独的日志文件。
-
按测试用例分割:每个测试用例生成一个独立的日志文件,方便分析单个测试用例的执行情况。
2. 日志滚动(Log Rotation)
日志滚动是指当一个日志文件达到一定大小或时间时,自动创建新的日志文件,避免单个日志文件过大。日志滚动通常结合时间或文件大小策略来进行。
常见的日志滚动策略:
-
按文件大小滚动:当日志文件达到设定的大小(例如 10MB)时,创建一个新的日志文件,并且保留旧的日志文件。
-
按时间滚动:根据时间间隔(例如每天或每周)创建新的日志文件。
-
按日志数量限制:指定保留的日志文件数量,当超过这个数量时,删除最早的日志文件。
示例:
-
文件大小策略:当日志文件超过 10MB 时,生成新的日志文件,例如
app.log
会被重命名为app.log.1
,新的日志继续写入app.log
。 -
按日期策略:每天生成新的日志文件,如
app-2024-10-10.log
,这样每一天的日志都存储在不同的文件中。
如何实现日志滚动与分割?
在 Python 中,logging
模块中的 RotatingFileHandler
和 TimedRotatingFileHandler
可以用于实现日志滚动。
代码示例:
import logging from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler # 定义日志格式 formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') # 文件大小滚动(最大 10MB,最多保留 5 个日志文件) file_handler_size = RotatingFileHandler('app.log', maxBytes=10*1024*1024, backupCount=5) file_handler_size.setFormatter(formatter) # 时间滚动(每天创建一个新日志文件,最多保留 7 天的日志) file_handler_time = TimedRotatingFileHandler('app.log', when='midnight', interval=1, backupCount=7) file_handler_time.setFormatter(formatter) # 配置 logger logger = logging.getLogger('my_logger') logger.setLevel(logging.DEBUG) logger.addHandler(file_handler_size) logger.addHandler(file_handler_time) # 示例日志记录 logger.info('这是一个日志条目')
-
RotatingFileHandler:根据文件大小设置日志分割和日志滚动策略
logging.handlers.RotatingFileHandler( filename, # 日志文件名 mode='a', # 文件模式,'a' 代表追加模式 maxBytes=0, # 文件大小限制(字节),0 表示不限制 backupCount=0, # 备份文件数量,0 表示不进行滚动 encoding=None, # 文件编码格式 delay=False # 是否延迟创建文件 )
# 表示每个日志文件最大内存为1GB,超过日志分割,且日志数量超过5时覆盖 handler = RotatingFileHandler('app.log', maxBytes=1*1024*1024, backupCount=5)
-
TimedRotatingFileHandler:根据时间设置日志分割和日志滚动策略。可以设置日志分割(每隔多少天生成新的日志文件)和日志滚动(多少天滚动覆盖之前的日志文件)策略
logging.handlers.TimedRotatingFileHandler( filename, # 日志文件名 when='midnight', # 日志分割时间的单位,可以是 's'、'm'、'h'、'd'、'midnight' 、'W0' 到 'W6'(周天到周六)等 interval=1, # 日志文件滚动间隔数,与 'when' 参数结合使用,相当于1midnigt backupCount=7 # 保留的旧日志文件数量,超过这个数量旧的日志会被删除 )
# 表示每天晚中午分割日志,当日志数为7时覆盖日志 handler = TimedRotatingFileHandler('app.log', when='midnight', interval=1, backupCount=7)
特性 RotatingFileHandler
TimedRotatingFileHandler
触发条件 日志文件大小超过 maxBytes
时分割按时间间隔(如每天、每小时等)分割日志 使用场景 日志文件可能较大,需控制文件大小 日志文件按时间规律生成,如按天、按小时等 日志滚动 超过指定文件大小时滚动日志,备份旧日志 达到时间间隔时滚动日志,备份旧日志 备份文件数量控制 通过 backupCount
限制日志文件数量,删除旧日志同样可以通过 backupCount
限制日志文件数量适用场景 大量日志的生成,需要根据大小限制进行滚动 定时任务、周期性日志生成,希望根据时间分割日志
总结:
-
日志分割 用于将不同类型的日志分开存储,方便调试和分析。
-
日志滚动 是为了避免单个日志文件过大,按时间或大小生成新的日志文件,并保留一定数量的历史日志。