(从基础配置到高级封装,用 100 行代码构建企业级日志方案)
开篇:为什么日志比 print 更重要?
你有没有过这样的经历:
- 线上代码报错,但不知道哪里出了问题
- 调试时用 print 输出了一堆信息,上线时忘了删除
- 系统崩溃后,没有任何记录可以分析
print是调试的好帮手,但在线上环境,日志才是系统的「黑匣子」。Python 的logging模块提供了完整的日志处理功能,结合自定义异常,能帮你打造可靠的错误监控系统。
一、Python 日志处理基础
1. logging 模块的核心组件
logging 模块包含 4 个核心组件:
- Logger:日志记录器,负责生成日志
- Handler:日志处理器,负责将日志输出到指定位置(控制台、文件、邮件等)
- Formatter:日志格式化器,负责日志的格式
- Filter:日志过滤器,负责过滤日志
2. 基本使用示例
import logging
# 1. 配置日志
logging.basicConfig(
level=logging.INFO, # 日志级别
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', # 日志格式
datefmt='%Y-%m-%d %H:%M:%S' # 时间格式
)
# 2. 获取日志记录器
logger = logging.getLogger(__name__)
# 3. 输出不同级别的日志
logger.debug('调试信息') # 不会输出(默认级别是WARNING)
logger.info('普通信息') # 输出
logger.warning('警告信息') # 输出
logger.error('错误信息') # 输出
logger.critical('致命信息') # 输出
3. 日志级别
logging 模块定义了 5 种日志级别(从低到高):
| 级别 | 描述 | 应用场景 |
|---|---|---|
| DEBUG | 调试信息 | 开发阶段调试代码 |
| INFO | 普通信息 | 记录系统运行状态 |
| WARNING | 警告信息 | 潜在的问题,但不影响系统运行 |
| ERROR | 错误信息 | 系统出错,但能继续运行 |
| CRITICAL | 致命信息 | 系统崩溃,无法继续运行 |
二、高级日志配置
1. 输出日志到文件
import logging
# 创建日志记录器
logger = logging.getLogger('my_logger')
logger.setLevel(logging.DEBUG) # 设置日志级别
# 创建文件处理器
file_handler = logging.FileHandler('app.log', encoding='utf-8')
file_handler.setLevel(logging.INFO) # 文件处理器的级别
# 创建控制台处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG) # 控制台处理器的级别
# 创建日志格式化器
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s'
)
# 为处理器设置格式化器
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
# 将处理器添加到日志记录器
logger.addHandler(file_handler)
logger.addHandler(console_handler)
# 测试
logger.debug('调试信息') # 控制台输出
logger.info('普通信息') # 控制台+文件输出
logger.error('错误信息') # 控制台+文件输出
2. 日志滚动与切割
当日志文件过大时,需要对其进行滚动和切割。logging 模块提供了RotatingFileHandler(按大小切割)和TimedRotatingFileHandler(按时间切割)。
按大小切割示例
from logging.handlers import RotatingFileHandler
# 创建按大小滚动的文件处理器
# 单个文件最大1MB,保留5个备份文件
rotating_handler = RotatingFileHandler(
'app.log',
maxBytes=1024 * 1024, # 1MB
backupCount=5,
encoding='utf-8'
)
rotating_handler.setFormatter(formatter)
logger.addHandler(rotating_handler)
按时间切割示例
from logging.handlers import TimedRotatingFileHandler
import time
# 创建按时间滚动的文件处理器
# 每天凌晨0点切割,保留7个备份文件
timed_handler = TimedRotatingFileHandler(
'app.log',
when='midnight', # 切割时间点
interval=1, # 间隔时间
backupCount=7, # 备份数量
encoding='utf-8'
)
timed_handler.setFormatter(formatter)
logger.addHandler(timed_handler)
三、自定义异常
1. 为什么需要自定义异常?
Python 提供了很多内置异常(如ValueError、TypeError),但在实际开发中,自定义异常能让你的代码更清晰、更易于维护。比如:
- 你正在开发一个电商系统,需要处理「库存不足」的异常
- 你正在开发一个 API,需要处理「权限不足」的异常
2. 自定义异常的基本方法
自定义异常需要继承自Exception类或其子类:
# 自定义异常
class InventoryInsufficientError(Exception):
"""库存不足异常"""
def __init__(self, product_id, required_quantity, current_quantity):
self.product_id = product_id
self.required_quantity = required_quantity
self.current_quantity = current_quantity
# 调用父类的__init__方法
super().__init__(f"商品{product_id}库存不足:需要{required_quantity},当前{current_quantity}")
# 使用自定义异常
def check_inventory(product_id, required_quantity):
# 模拟库存查询
inventory = {
'1001': 5,
'1002': 10
}
current_quantity = inventory.get(product_id, 0)
if current_quantity < required_quantity:
raise InventoryInsufficientError(product_id, required_quantity, current_quantity)
return True
# 测试
try:
check_inventory('1001', 10)
except InventoryInsufficientError as e:
print(f"错误类型:{type(e).__name__}")
print(f"错误信息:{e}")
print(f"商品ID:{e.product_id}")
print(f"需要数量:{e.required_quantity}")
print(f"当前库存:{e.current_quantity}")
运行结果:
错误类型:InventoryInsufficientError
错误信息:商品1001库存不足:需要10,当前5
商品ID:1001
需要数量:10
当前库存:5
四、日志与自定义异常的结合
1. 异常捕获与日志记录
在捕获自定义异常时,应该将异常信息完整地记录到日志中:
import logging
# 配置日志(省略,同上)
try:
check_inventory('1001', 10)
except InventoryInsufficientError as e:
# 记录错误日志,包括异常的详细信息
logger.error(
"库存不足异常",
extra={
'product_id': e.product_id,
'required_quantity': e.required_quantity,
'current_quantity': e.current_quantity
},
exc_info=True # 记录完整的堆栈信息
)
日志输出:
2024-05-20 14:30:00 - my_logger - ERROR - app.py:100 - 库存不足异常
Traceback (most recent call last):
File "app.py", line 98, in <module>
check_inventory('1001', 10)
File "app.py", line 50, in check_inventory
raise InventoryInsufficientError(product_id, required_quantity, current_quantity)
InventoryInsufficientError: 商品1001库存不足:需要10,当前5
2. 企业级异常处理方案
结合日志和自定义异常,我们可以构建一个统一的异常处理装饰器:
import logging
import functools
# 配置日志(省略,同上)
def handle_exception(func):
"""统一异常处理装饰器"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except InventoryInsufficientError as e:
logger.error(
"库存不足异常",
extra={
'product_id': e.product_id,
'required_quantity': e.required_quantity,
'current_quantity': e.current_quantity
},
exc_info=True
)
# 可以在这里添加额外的处理逻辑,如发送邮件、短信等
return False
except Exception as e:
logger.error(
"未知异常",
exc_info=True
)
return False
return wrapper
# 使用装饰器
@handle_exception
def process_order(order_id):
# 模拟订单处理流程
check_inventory('1001', 10)
# 其他订单处理逻辑...
return True
# 测试
process_order('order_001')
五、最佳实践
1. 日志相关
- 不要使用 print:在线上环境,所有输出都应该通过日志记录
- 设置合适的日志级别:开发阶段用 DEBUG,线上环境用 INFO 或 WARNING
- 使用结构化日志:包含时间、级别、文件名、行号、用户 ID、请求 ID 等信息
- 定期备份和清理日志:避免日志文件过大
- 记录异常的完整信息:包括堆栈追踪、参数信息等
2. 自定义异常相关
- 继承自 Exception 类:不要继承自 BaseException
- 提供详细的错误信息:包含异常的上下文和参数
- 保持异常类的简洁:只包含必要的属性和方法
- 使用一致的命名规范:以 Error 或 Exception 结尾
- 不要过度使用自定义异常:只有在需要区分特定错误场景时才使用
六、总结
日志处理和自定义异常是 Python 开发中不可或缺的两个部分:
- 日志:记录系统的运行状态和错误信息,是系统的「黑匣子」
- 自定义异常:清晰地表达特定的错误场景,提高代码的可读性和维护性
结合日志和自定义异常,你可以构建一个可靠的错误监控系统,及时发现和定位线上问题。在实际开发中,应该遵循最佳实践,确保日志和异常处理的规范和高效。


被折叠的 条评论
为什么被折叠?



