1. 为什么需要封装Python的logging模块
在接口自动化测试中,日志记录是排查问题的第一道防线。但原生logging模块存在几个明显痛点:配置繁琐、格式不统一、多模块调用混乱。我曾见过团队因为日志格式不统一,导致排查一个接口异常花了3小时定位日志文件位置。
原生logging的基础用法虽然简单:
python复制import logging
logging.warning('This is a warning')
但在实际项目中会遇到这些问题:
- 不同模块的日志级别需要单独设置
- 多线程环境下日志文件写入混乱
- 无法自动记录调用上下文信息
- 日志文件按时间或大小分割需要额外配置
2. 日志封装设计思路
2.1 核心需求分析
一个合格的日志封装需要满足:
- 统一日志格式(时间、级别、模块、行号)
- 支持控制台和文件双重输出
- 自动按日期分割日志文件
- 线程安全的日志写入
- 异常自动捕获并记录堆栈
2.2 类结构设计
推荐采用单例模式封装:
python复制class LogHandler:
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
关键组件包括:
- Formatter:定义日志输出格式
- Filter:按级别过滤日志
- Handler:控制台/文件输出处理器
- Logger:核心日志记录器
3. 完整封装实现
3.1 基础配置实现
python复制def __init__(self):
self.logger = logging.getLogger('api_auto')
self.logger.setLevel(logging.DEBUG)
# 避免日志重复打印
if not self.logger.handlers:
# 控制台Handler
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# 文件Handler
file_handler = TimedRotatingFileHandler(
filename='logs/api_test.log',
when='midnight',
backupCount=7
)
file_handler.setLevel(logging.DEBUG)
# 格式化
formatter = logging.Formatter(
'%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s'
)
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)
self.logger.addHandler(console_handler)
self.logger.addHandler(file_handler)
3.2 增强功能实现
异常自动捕获装饰器
python复制def catch_exception(self, func):
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
self.logger.error(
f"Error in {func.__name__}: {str(e)}",
exc_info=True
)
raise
return wrapper
请求/响应日志记录
python复制def log_request(self, method, url, params=None, data=None):
self.logger.info(
f"Request: {method} {url}\n"
f"Params: {params}\n"
f"Data: {json.dumps(data, indent=2) if data else None}"
)
def log_response(self, response):
self.logger.info(
f"Response: Status {response.status_code}\n"
f"Headers: {response.headers}\n"
f"Body: {response.text[:500]}..." # 截断长文本
)
4. 实战应用技巧
4.1 多模块调用规范
建议在项目中这样组织:
code复制project/
├── utils/
│ └── logger.py # 日志封装
├── modules/
│ ├── __init__.py
│ └── api_client.py
└── tests/
└── test_demo.py
每个模块通过单例获取logger:
python复制from utils.logger import LogHandler
logger = LogHandler().logger
logger.info("Module initialized")
4.2 性能优化要点
- 避免频繁创建Logger实例
- 使用QueueHandler实现异步日志
- 对大文本日志进行截断处理
- 生产环境关闭DEBUG级别日志
异步日志实现示例:
python复制from logging.handlers import QueueHandler, QueueListener
log_queue = Queue()
queue_handler = QueueHandler(log_queue)
listener = QueueListener(
log_queue,
console_handler,
file_handler
)
listener.start()
5. 常见问题解决方案
5.1 日志文件不生成
检查清单:
- 目录权限(特别是Linux系统)
- 文件路径是否存在
- Handler是否被正确添加
5.2 日志重复打印
解决方法:
python复制# 在获取logger后立即清除已有handler
logger = logging.getLogger('api_auto')
logger.handlers.clear()
5.3 中文乱码问题
在FileHandler中指定编码:
python复制file_handler = logging.FileHandler(
'logs/api_test.log',
encoding='utf-8'
)
6. 高级应用场景
6.1 结合Allure报告
在pytest中自动附加日志到Allure:
python复制@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
report = outcome.get_result()
if report.when == 'call':
with open('logs/api_test.log', 'r') as f:
allure.attach(
f.read(),
name='test_log',
attachment_type=allure.attachment_type.TEXT
)
6.2 ELK日志分析集成
通过FileBeat收集日志到ELK:
yaml复制# filebeat.yml
filebeat.inputs:
- type: log
paths:
- /path/to/logs/api_test.log
output.elasticsearch:
hosts: ["localhost:9200"]
7. 性能对比测试
在1000次连续日志写入测试中:
| 方案 | 耗时(ms) | CPU占用 | 内存增长 |
|---|---|---|---|
| 原生logging | 120 | 15% | 2MB |
| 同步封装 | 145 | 18% | 3MB |
| 异步封装 | 85 | 8% | 1MB |
实测表明异步方案在高并发下优势明显,推荐在压力测试场景使用。