在做自动化测试、接口服务或者后台系统开发时,日志是我们排查问题、了解程序运行状态不可或缺的工具。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 级别日志,便于排查问题;
- 支持日志自动轮转(按大小切割),防止文件过大;
- 统一日志格式,包含等级、时间、文件名、行号等信息;
- 自动创建日志目录,提升脚本可移植性。
该模块适用于自动化测试、接口服务、后台系统等多种场景,具备良好的扩展性和实用性。如需进一步增强功能,还可添加邮件报警、远程日志上传等功能。