Pytest 封装logging自定义日志等级 —— Python 实践

        在做自动化测试、接口服务或者后台系统开发时,日志是我们排查问题、了解程序运行状态不可或缺的工具。Python 内置的 logging 模块功能已经很强大,但在实际项目中直接使用,常常会出现配置重复、日志输出不统一、文件管理混乱等问题。
        为了提升效率和可维护性,我们通常会对 logging 进行封装。本文就带你一步步实现一个结构清晰、功能完善的日志模块,支持日志等级控制、自动轮转、控制台与文件分别输出,并通过单例模式确保全局只使用一个日志实例,避免资源浪费和冲突。

🧾 一、项目背景

  • 原始 logging.basicConfig() 配置简单,难以满足复杂项目需求。
  • 多个模块频繁调用 getLogger() 容易造成日志重复、格式不统一。
  • 日志文件管理(如大小限制、备份策略)需要更精细的控制。

🛠️ 二、封装目标

  • 支持不同日志级别(DEBUG/INFO/WARNING/ERROR/CRITICAL)
  • 控制台仅输出 INFO 及以上级别日志
  • 文件记录所有 DEBUG 级别日志,并支持自动轮转
  • 使用单例模式避免重复初始化
  • 支持多模块调用,统一日志入口

📦 三、依赖模块介绍

import os
import logging
from logging.handlers import RotatingFileHandler
from pathlib import Path
  • logging:Python 标准日志模块
  • RotatingFileHandler:用于按大小切割日志文件
  • Path:来自 pathlib,用于跨平台路径处理
  • os:用于目录创建和判断

🔧 四、核心函数详解

1. 定义日志输出格式

def setup_logger_formatter():
    return logging.Formatter(
        '%(levelname)s %(asctime)s [%(filename)s:%(lineno)d] %(thread)d %(message)s',
        datefmt='%Y-%m-%d %H:%M:%S'
    )

说明:

  • %(levelname):日志等级(INFO、ERROR 等)
  • %(asctime):时间戳
  • %(filename):记录日志的源文件名
  • %(lineno):代码行号
  • %(thread):线程 ID
  • %(message):日志内容

2. 配置日志文件处理器,支持日志轮转

def setup_file_handler(log_file: str):
    try:
        file_handler = RotatingFileHandler(
            log_file,
            encoding='utf-8',
            maxBytes=20 * 1024 * 1024,  # 单个日志文件最大 20MB
            backupCount=10  # 保留最多 10 个备份文件
        )
        file_handler.setFormatter(setup_logger_formatter())
        file_handler.setLevel(logging.DEBUG)
        return file_handler
    except Exception as e:
        print(f"日志文件处理器初始化失败: {e}")
        raise

说明:

  • RotatingFileHandler:当日志文件超过指定大小时自动分割
  • maxBytes:单个文件最大容量(20MB)
  • backupCount:最多保留的备份文件数(10 个)

3. 配置控制台输出处理器

def dispatch(self, method: t.Text, *args: t.Union[t.List, t.Tuple], **kwargs: t.Dict) -> Response:
    handler = getattr(self, method.lower())
    return handler(*args, **kwargs)

说明:

  • StreamHandler:标准输出日志
  • 控制台只显示 INFO 及以上级别日志,避免调试信息干扰

🔁 五、日志单例类 LoggerSingleton

为避免多次初始化同一个 logger,我们采用单例模式封装日志对象。

class LoggerSingleton:
    """
    日志单例类,确保整个程序中只有一个日志实例
    """
    _instance = None

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(LoggerSingleton, cls).__new__(cls)
        return cls._instance

    def __init__(self):
        if not hasattr(self, 'logger'):
            self.logger = self.init_logger()

    def init_logger(self):
        """初始化日志"""
        try:
            # 获取项目根目录
            basedir = Path(__file__).resolve().parent.parent
            logs_dir = basedir / "logs"
            log_file = logs_dir / "server.log"

            # 确保 logs 目录存在
            logs_dir.mkdir(parents=True, exist_ok=True)

            # 创建日志记录器
            logger = logging.getLogger('apitest')
            if not logger.handlers:  # 防止重复初始化
                logger.setLevel(logging.DEBUG)  # 全局日志级别

                # 添加文件处理器
                file_handler = setup_file_handler(log_file)
                logger.addHandler(file_handler)

                # 添加控制台处理器
                console_handler = setup_console_handler()
                logger.addHandler(console_handler)

            return logger
        except Exception as e:
            print(f"日志初始化失败: {e}")
            raise

特点:

  • 单例模式:确保整个程序只有一个日志实例
  • 延迟加载:只有首次访问时才初始化
  • 防止重复添加 handlers
  • 自动创建日志目录

🧪 六、日志目录结构

该脚本会在项目根目录下创建 logs 文件夹,存放日志文件:

project_root/
├── logs/
│   └── server.log
├── utils/
│   └── logger.py
└── main.py

✅ 七、示例测试代码

if __name__ == '__main__':
    logger.debug("调试日志测试")
    logger.info("信息日志测试")
    logger.warning("警告日志测试")
    logger.error("错误日志测试")
    logger.critical("严重错误日志测试")

输出效果如下:

INFO 2025-04-05 10:00:00 [test.py:10] 12345 信息日志测试
WARNING 2025-04-05 10:00:00 [test.py:11] 12345 警告日志测试
ERROR 2025-04-05 10:00:00 [test.py:12] 12345 错误日志测试
CRITICAL 2025-04-05 10:00:00 [test.py:13] 12345 严重错误日志测试

其中,DEBUG 级别只写入文件,不在控制台输出。

📁 八、完整代码

# -*- coding: utf-8 -*-
"""
日志类封装模块
"""

import os
import logging
from logging.handlers import RotatingFileHandler
from pathlib import Path


def setup_logger_formatter():
    """配置日志格式"""
    return logging.Formatter(
        '%(levelname)s %(asctime)s [%(filename)s:%(lineno)d] %(thread)d %(message)s',
        datefmt='%Y-%m-%d %H:%M:%S'
    )


def setup_file_handler(log_file: str):
    """配置文件处理器"""
    try:
        file_handler = RotatingFileHandler(
            log_file,
            encoding='utf-8',
            maxBytes=20 * 1024 * 1024,  # 20MB
            backupCount=10
        )
        file_handler.setFormatter(setup_logger_formatter())
        file_handler.setLevel(logging.DEBUG)
        return file_handler
    except Exception as e:
        print(f"日志文件处理器初始化失败: {e}")
        raise


def setup_console_handler():
    """配置控制台处理器"""
    console_handler = logging.StreamHandler()
    console_handler.setFormatter(setup_logger_formatter())
    console_handler.setLevel(logging.INFO)
    return console_handler


class LoggerSingleton:
    """
    日志单例类,确保整个程序中只有一个日志实例
    """
    _instance = None

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(LoggerSingleton, cls).__new__(cls)
        return cls._instance

    def __init__(self):
        if not hasattr(self, 'logger'):
            self.logger = self.init_logger()

    def init_logger(self):
        try:
            basedir = Path(__file__).resolve().parent.parent
            logs_dir = basedir / "logs"
            log_file = logs_dir / "server.log"

            logs_dir.mkdir(parents=True, exist_ok=True)

            logger = logging.getLogger('apitest')
            if not logger.handlers:
                logger.setLevel(logging.DEBUG)

                file_handler = setup_file_handler(log_file)
                logger.addHandler(file_handler)

                console_handler = setup_console_handler()
                logger.addHandler(console_handler)

            return logger
        except Exception as e:
            print(f"日志初始化失败: {e}")
            raise


# 初始化日志单例
logger = LoggerSingleton().logger

 ✅ 九、总结

        本文对 Python 的 logging 模块进行了二次封装,构建了一个结构清晰、易于管理的日志模块。通过单例模式确保全局日志实例唯一,避免重复初始化。
主要功能包括:

  • 控制台输出仅显示 INFO 及以上级别日志,减少干扰;
  • 文件记录所有 DEBUG 级别日志,便于排查问题;
  • 支持日志自动轮转(按大小切割),防止文件过大;
  • 统一日志格式,包含等级、时间、文件名、行号等信息;
  • 自动创建日志目录,提升脚本可移植性。

该模块适用于自动化测试、接口服务、后台系统等多种场景,具备良好的扩展性和实用性。如需进一步增强功能,还可添加邮件报警、远程日志上传等功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值