Files
AppAutoTest/utils/logger.py
CNWei 9355a576a9 feat: 新增日志系统与执行追踪装饰器
- 基于 Loguru 重新封装,支持异步写入和多线程安全。
- 实现 @trace_step 装饰器,自动记录步骤名、参数及执行耗时。
- 引入 source 标签区分框架系统(System)与业务任务(task)日志。
- 新增 logger 模块测试用例 test_logger.py
2026-01-15 18:00:05 +08:00

125 lines
3.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python
# coding=utf-8
"""
@author: CNWei,ChenWei
@Software: PyCharm
@contact: t6g888@163.com,chenwei@zygj.com
@file: logger
@date: 2026/1/15 11:30
@desc:
"""
import sys
import time
import functools
from pathlib import Path
import inspect
from loguru import logger
# 1. 确定日志存储路径
LOG_DIR = Path(__file__).parent.parent / "logs"
LOG_DIR.mkdir(exist_ok=True)
# 2. 统一定义日志格式 (美化版)
# <green> 等标签是控制台颜色,文件日志中会自动剥离颜色代码
LOG_FORMAT = (
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
"<level>{level: <8}</level> | "
"<magenta>{extra[source]: <8}</magenta> | "
"<cyan>{module}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - "
"<level>{message}</level>"
)
def setup_logger():
"""
只需在项目入口调用一次。
如果是简单的自动化脚本,甚至可以直接在模块内执行。
"""
# 移除 Loguru 默认的控制台处理器(避免重复打印)
logger.remove()
# 添加自定义控制台输出
logger.add(
sys.stdout,
format=LOG_FORMAT,
level="INFO",
colorize=True,
# 默认给一个 'Global' 的 device 标签
filter=lambda record: record["extra"].setdefault("source", "System")
)
# 添加按天滚动的日志文件
logger.add(
str(LOG_DIR / "appium_{time:YYYY-MM-DD}.log"),
format=LOG_FORMAT,
level="DEBUG",
rotation="00:00", # 每天午夜滚动
retention="10 days", # 保留最近10天
compression="zip", # 旧日志自动压缩
encoding="utf-8",
enqueue=True # 开启队列模式,确保多线程下日志不串行
)
# --- 核心特性 1装饰器集成 ---
def trace_step(step_desc="", source: str = 'task'):
"""
通用执行追踪装饰器:
1. 智能识别并过滤 self/cls 参数
2. 记录入参、出参、耗时
3. 异常自动捕获并记录
"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# --- 智能参数解析 ---
# 获取函数的签名
sig = inspect.signature(func)
params = list(sig.parameters.values())
# 检查第一个参数名是否为 'self' 或 'cls'
# 这样既兼容了 PageObject 的实例方法,也兼容了纯函数
if params and params[0].name in ('self', 'cls'):
display_args = args[1:]
else:
display_args = args
# 格式化参数显示,方便阅读
args_repr = [repr(a) for a in display_args]
kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]
all_params = ", ".join(args_repr + kwargs_repr)
func_name = f"{func.__module__}.{func.__name__}"
# 使用 bind 临时改变这一步的 source 标签
_logger = logger.bind(source=source)
# 使用关联的上下文 logger
# logger.info(f"🚀 [步骤开始] {step_desc} | 执行方法: {func_name} | 参数: {display_args} {kwargs}")
_logger.info(f"🚀 [步骤开始] {step_desc} | 方法: {func_name}({all_params})")
start_t = time.perf_counter()
try:
result = func(*args, **kwargs)
duration = time.perf_counter() - start_t
_logger.success(f"✅ [步骤成功] {step_desc} | 耗时: {duration:.2f}s | 返回: {result!r}")
return result
except Exception as e:
duration = time.perf_counter() - start_t
_logger.error(
f"❌ [步骤失败] {step_desc} | 耗时: {duration:.2f}s | 异常: {type(e).__name__}: {e}")
raise e
return wrapper
return decorator
# 初始化
setup_logger()
# 导出供外部使用
__all__ = ["logger", "trace_step"]