Python -- logging模块日志管理
简介:
logging
是 Python 标准库中的一个模块,它提供了灵活的事件日志记录功能。通过 logging
模块,开发者可以在程序中记录各种级别的日志信息,这对于调试、问题定位以及了解软件运行状态非常有用。相比于直接使用 print()
语句输出信息,logging
提供了更为强大和灵活的日志管理功能。
日志级别
Python 定义了6个日志级别(从低到高):
- DEBUG:调试信息
- INFO:普通信息
- WARNING:警告信息
- ERROR:错误信息
- CRITICAL:严重错误
基础使用
import logging
logging.basicConfig(level=logging.INFO)
logging.debug('这是一条调试信息') # 不会显示
logging.info('这是一条普通信息') # 会显示
logging.warning('这是一条警告信息') # 会显示
格式化输出
1、 %
格式化(推荐用于 logging)
常用格式字段:
%(asctime)s
:时间%(name)s
:记录器名称%(levelname)s
:日志级别%(message)s
:日志消息%(lineno)d
:源代码行号%(funcName)s
:函数名
占位符类型:
可以自由组合不同类型的占位符:
占位符 | 说明 | 示例变量 |
---|---|---|
%s |
字符串 | "Python" |
%d |
整数 | 42 |
%f |
浮点数 | 3.14159 |
%.2f |
保留2位小数的浮点数 | 3.14159 → 3.14 |
示例:
logging.debug("进程 %s 用了 %d 秒,CPU 占用率 %.2f%%", "worker1", 5, 78.456)
特点:
- ✅ logging 模块原生优化(延迟求值)
- ✅ 兼容所有 Python 版本
- ❌ 功能相对简单
2、 str.format()
方法
logging.info("用户: {}, 年龄: {}".format(name, age)) # 立即求值
logging.info("用户: {name}, 年龄: {age}".format(name=name, age=age)) # 命名参数
特点:
- ✅ 支持命名参数和复杂格式化
- ❌ 无条件立即求值(可能影响性能)
3、f-string (Python 3.6+)
logging.info(f"用户: {name}, 年龄: {age}") # 最直观但立即求值
特点:
- ✅ 代码可读性最高
- ❌ 无条件立即求值(性能敏感场景慎用)
4、字典格式化(结构化日志)
logging.info("用户数据: %s", {"name": name, "age": age}) # 适合 JSON 日志
特点:
- ✅ 适合机器解析的日志系统
- ✅ 保持数据结构完整性
5、在 Pytest 中应用
# pytest.ini
# 开启日志输出
# 启用实时控制台日志输出
log_cli = true
log_cli_encoding = utf-8
# 全局默认日志等级
log_level = INFO
log_format = %(asctime)s [%(levelname)s] %(name)s:%(lineno)d - %(message)s
log_date_format = %Y-%m-%d %H:%M:%S
# 日志文件路径
log_file = ../logs/pytest.log
log_file_encoding = utf-8
# 文件日志级别(通常比控制台更详细)
log_file_level = DEBUG
log_file_format = %(asctime)s [%(levelname)s] %(filename)s:%(lineno)d - %(message)s
import logging
logger = logging.getLogger(__name__)
def test_1():
logger.info("hhaa")
assert 1 == 2
6、基于 Pytest 优化
对日志文件名增加日期区分
# pytest.ini
[pytest]
# 启用实时控制台日志输出
log_cli = true
log_cli_encoding = utf-8
log_cli_level = INFO
log_cli_format = %(asctime)s [%(levelname)s] %(name)s:%(lineno)d - %(message)s
log_cli_date_format = %Y-%m-%d %H:%M:%S
# 关闭原生 log_file(由代码控制)
# log_file = ../logs/pytest.log
# 全局默认日志等级
log_level = INFO
conftest.py
方案1:一天一个log,并且日志内容为“追加”模式,而不是覆盖!
# --------------------------
# 日志优化,对日志文件增加日期,FileHandler 是 追加写入
# --------------------------
def pytest_configure(config):
# 创建 logs 目录
log_dir = os.path.join(os.path.dirname(__file__), "logs")
os.makedirs(log_dir, exist_ok=True)
# 生成带日期的日志文件名:pytest_20250809.log
today = datetime.now().strftime("%Y%m%d")
log_file = os.path.join(log_dir, f"pytest_{today}.log")
# 创建文件 handler
file_handler = logging.FileHandler(log_file, encoding="utf-8", mode="a")
file_handler.setLevel(logging.DEBUG) # 文件记录更详细
# 设置文件日志格式
formatter = logging.Formatter(
fmt="%(asctime)s [%(levelname)s] %(filename)s:%(lineno)d - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
file_handler.setFormatter(formatter)
# 添加到 root logger
root_logger = logging.getLogger()
root_logger.addHandler(file_handler)
方案2: 一次运行一个log,run1、run2......
# --------------------------
# 日志优化,对日志文件名增加日期
# --------------------------
def pytest_configure(config):
# 创建 logs 目录
log_dir = os.path.join(os.path.dirname(__file__), "logs")
os.makedirs(log_dir, exist_ok=True)
# 生成带日期的日志文件名:pytest_20250809.log
today = datetime.now().strftime("%Y%m%d")
base_name = f"pytest_{today}"
# 正则匹配:pytest_20250809_run1.log, pytest_20250809_run2.log 等
pattern = re.compile(rf"{base_name}_run(\d+)\.log$")
# 扫描目录,找出所有匹配的 run 序号
existing_runs = []
for filename in os.listdir(log_dir):
match = pattern.match(filename)
if match:
run_number = int(match.group(1))
existing_runs.append(run_number)
# 计算下一个序号
next_run = max(existing_runs) + 1 if existing_runs else 1
# 生成日志文件名
log_file = os.path.join(log_dir, f"{base_name}_run{next_run}.log")
# 创建文件 handler
file_handler = logging.FileHandler(log_file, encoding="utf-8", mode="a")
file_handler.setLevel(logging.DEBUG) # 文件记录更详细
# 设置文件日志格式
formatter = logging.Formatter(
fmt="%(asctime)s [%(levelname)s] %(filename)s:%(lineno)d - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
file_handler.setFormatter(formatter)
# 添加到 root logger
root_logger = logging.getLogger()
root_logger.addHandler(file_handler)
评论区