在Python开发中,优雅地处理异常和记录详细的错误信息是编写健壮程序的关键。无论是开发Web应用、数据分析脚本还是自动化工具,当程序在用户环境中运行时,完善的错误处理机制能帮助我们快速定位和解决问题。本文将深入探讨Python中异常捕获的高级技巧和日志记录的最佳实践。
一、Python异常处理基础
1.1 基本的try-except语句
try:
# 可能引发异常的代码
result = 10 / 0
except ZeroDivisionError as e:
print(f"发生了除零错误: {e}")
1.2 捕获多种异常
try:
# 可能引发多种异常的代码
value = int("abc")
result = 10 / value
except (ValueError, ZeroDivisionError) as e:
print(f"捕获到异常: {type(e).__name__}: {e}")
1.3 完整的异常处理结构
try:
# 主要逻辑
data = {"key": "value"}
print(data["nonexistent_key"])
except KeyError as e:
print(f"键错误: {e}")
except Exception as e:
print(f"其他异常: {e}")
else:
print("代码执行成功,无异常发生")
finally:
print("无论是否发生异常,都会执行此代码块")
二、获取详细的错误信息
2.1 使用traceback模块
traceback模块提供了获取完整调用栈信息的功能:
import traceback
import sys
def risky_operation():
return 1 / 0
try:
risky_operation()
except Exception as e:
# 获取完整的异常信息
exc_type, exc_value, exc_traceback = sys.exc_info()
# 格式1: 基本traceback信息
print("基本Traceback:")
traceback.print_exc()
# 格式2: 转换为字符串
error_str = traceback.format_exc()
print("\n格式化的错误信息:")
print(error_str)
# 格式3: 获取调用栈的帧信息
print("\n详细调用栈:")
for frame, lineno in traceback.walk_tb(exc_traceback):
print(f"文件: {frame.f_code.co_filename}")
print(f"行号: {lino}")
print(f"函数: {frame.f_code.co_name}")
2.2 自定义错误信息收集函数
import sys
import traceback
from datetime import datetime
def get_detailed_error_info():
"""获取详细的错误信息"""
exc_type, exc_value, exc_traceback = sys.exc_info()
error_details = {
"timestamp": datetime.now().isoformat(),
"exception_type": exc_type.__name__ if exc_type else None,
"exception_message": str(exc_value) if exc_value else None,
"traceback": traceback.format_exc(),
"local_variables": {}
}
# 获取局部变量(最后一个帧)
if exc_traceback:
tb_frame = exc_traceback.tb_frame
while tb_frame:
error_details["local_variables"].update(tb_frame.f_locals)
tb_frame = tb_frame.f_back
return error_details
# 使用示例
try:
x = 10
y = 0
result = x / y
except Exception:
error_info = get_detailed_error_info()
print("错误详情:", error_info)
三、日志记录最佳实践
3.1 配置日志系统
import logging
import logging.handlers
from pathlib import Path
def setup_logging(log_file="app_error.log"):
"""设置日志配置"""
# 创建日志目录
log_path = Path(log_file)
log_path.parent.mkdir(parents=True, exist_ok=True)
# 创建logger
logger = logging.getLogger("AppLogger")
logger.setLevel(logging.DEBUG)
# 防止重复添加handler
if not logger.handlers:
# 文件handler - 记录所有级别
file_handler = logging.handlers.RotatingFileHandler(
log_file,
maxBytes=10 * 1024 * 1024, # 10MB
backupCount=5,
encoding='utf-8'
)
file_handler.setLevel(logging.DEBUG)
# 控制台handler - 只记录INFO及以上
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# 设置格式
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - '
'[%(filename)s:%(lineno)d] - %(message)s'
)
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
# 添加handler
logger.addHandler(file_handler)
logger.addHandler(console_handler)
return logger
3.2 带错误上下文的日志装饰器
import functools
import traceback
from typing import Callable, Any
def log_errors(logger: logging.Logger):
"""记录函数错误的装饰器"""
def decorator(func: Callable) -> Callable:
@functools.wraps(func)
def wrapper(*args, **kwargs) -> Any:
try:
return func(*args, **kwargs)
except Exception as e:
# 记录详细错误信息
error_details = {
"function": func.__name__,
"module": func.__module__,
"args": args,
"kwargs": kwargs,
"traceback": traceback.format_exc(),
"error": str(e)
}
logger.error(f"函数 {func.__name__} 执行失败: {error_details}")
raise # 重新抛出异常
return wrapper
return decorator
# 使用示例
logger = setup_logging()
@log_errors(logger)
def process_data(data: dict):
"""处理数据的函数"""
return data["nonexistent_key"]
四、高级错误处理模式
4.1 上下文管理器处理异常
from contextlib import contextmanager
import sys
import traceback
@contextmanager
def error_handler(context: str = ""):
"""错误处理的上下文管理器"""
try:
yield
except Exception as e:
exc_type, exc_value, exc_traceback = sys.exc_info()
error_log = {
"context": context,
"error_type": exc_type.__name__,
"error_message": str(exc_value),
"traceback": traceback.format_tb(exc_traceback),
"timestamp": datetime.now().isoformat()
}
# 这里可以添加错误上报逻辑
print(f"捕获到错误 ({context}): {error_log}")
# 可以选择是否重新抛出异常
# raise
# 使用示例
with error_handler("数据处理"):
result = 10 / 0
4.2 自定义异常类
class AppBaseError(Exception):
"""应用基础异常类"""
def __init__(self, message: str, error_code: int = 500, **kwargs):
self.message = message
self.error_code = error_code
self.context = kwargs
super().__init__(self.message)
def to_dict(self) -> dict:
"""转换为字典格式"""
return {
"error": self.__class__.__name__,
"message": self.message,
"code": self.error_code,
"context": self.context,
"traceback": traceback.format_exc()
}
class ValidationError(AppBaseError):
"""数据验证异常"""
def __init__(self, field: str, value: Any, rule: str):
message = f"字段 '{field}' 的值 '{value}' 不符合规则: {rule}"
super().__init__(message, error_code=400, field=field, value=value, rule=rule)
# 使用示例
def validate_age(age: int):
if age < 0 or age > 150:
raise ValidationError(field="age", value=age, rule="0 <= age <= 150")
return True
五、实战:完整的错误处理模块
# error_handler.py
import logging
import sys
import traceback
from datetime import datetime
from typing import Optional, Dict, Any
from pathlib import Path
import json
class ErrorHandler:
"""完整的错误处理器"""
def __init__(self,
app_name: str = "MyApp",
log_dir: str = "logs",
enable_file_log: bool = True,
enable_console_log: bool = True):
self.app_name = app_name
self.log_dir = Path(log_dir)
self.log_dir.mkdir(exist_ok=True)
# 初始化日志
self.logger = self._setup_logger(
enable_file_log=enable_file_log,
enable_console_log=enable_console_log
)
# 错误统计
self.error_stats = {
"total_errors": 0,
"error_by_type": {},
"last_error_time": None
}
def _setup_logger(self, enable_file_log: bool, enable_console_log: bool) -> logging.Logger:
"""设置logger"""
logger = logging.getLogger(self.app_name)
logger.setLevel(logging.DEBUG)
# 避免重复添加handler
if logger.handlers:
return logger
formatter = logging.Formatter(
'%(asctime)s - %(levelname)s - '
'[%(filename)s:%(lineno)d] - %(message)s'
)
if enable_file_log:
# 错误日志
error_file = self.log_dir / f"{self.app_name}_error.log"
error_handler = logging.handlers.RotatingFileHandler(
error_file, maxBytes=10 * 1024 * 1024, backupCount=5, encoding='utf-8'
)
error_handler.setLevel(logging.ERROR)
error_handler.setFormatter(formatter)
logger.addHandler(error_handler)
# 调试日志
debug_file = self.log_dir / f"{self.app_name}_debug.log"
debug_handler = logging.handlers.RotatingFileHandler(
debug_file, maxBytes=10 * 1024 * 1024, backupCount=5, encoding='utf-8'
)
debug_handler.setLevel(logging.DEBUG)
debug_handler.setFormatter(formatter)
logger.addHandler(debug_handler)
if enable_console_log:
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
return logger
def capture_exception(self,
exception: Exception,
context: Optional[Dict[str, Any]] = None,
level: str = "ERROR") -> Dict[str, Any]:
"""捕获并记录异常"""
exc_type, exc_value, exc_traceback = sys.exc_info()
error_info = {
"timestamp": datetime.now().isoformat(),
"app_name": self.app_name,
"exception": {
"type": exc_type.__name__ if exc_type else None,
"message": str(exc_value),
"module": exc_type.__module__ if exc_type else None
},
"traceback": traceback.format_exc(),
"context": context or {},
"system_info": {
"python_version": sys.version,
"platform": sys.platform
}
}
# 更新统计
self.error_stats["total_errors"] += 1
error_type = error_info["exception"]["type"]
self.error_stats["error_by_type"][error_type] = \
self.error_stats["error_by_type"].get(error_type, 0) + 1
self.error_stats["last_error_time"] = error_info["timestamp"]
# 记录日志
log_message = json.dumps(error_info, ensure_ascii=False, indent=2)
if level == "ERROR":
self.logger.error(log_message)
elif level == "WARNING":
self.logger.warning(log_message)
else:
self.logger.info(log_message)
# 保存到单独的错误文件
error_file = self.log_dir / f"error_{datetime.now().strftime('%Y%m%d')}.json"
with open(error_file, 'a', encoding='utf-8') as f:
f.write(log_message + "\n")
return error_info
def get_error_stats(self) -> Dict[str, Any]:
"""获取错误统计"""
return self.error_stats.copy()
# 使用示例
if __name__ == "__main__":
# 初始化错误处理器
handler = ErrorHandler(app_name="DataProcessor")
try:
# 模拟业务代码
data = {"items": [1, 2, 3]}
result = data["nonexistent_key"]
except Exception as e:
# 捕获并记录错误
error_info = handler.capture_exception(
e,
context={
"operation": "data_processing",
"data_structure": str(data.keys()),
"user_id": 12345
}
)
print("错误信息已记录:")
print(json.dumps(error_info, indent=2, ensure_ascii=False))
# 获取统计信息
stats = handler.get_error_stats()
print(f"\n错误统计: {stats}")
六、生产环境建议
6.1 错误监控和报警
- 集成Sentry、Rollbar等错误监控服务
- 设置关键错误的邮件/短信通知
- 插入广告:各行各业学习千款源码就上:svipm.com.cn
- 实现错误率监控和报警阈值
6.2 性能考虑
- 异步记录错误日志,避免阻塞主流程
- 合理设置日志级别,生产环境避免DEBUG级别
- 定期清理和归档旧日志
6.3 安全考虑
- 记录错误时注意敏感信息过滤
- 避免在错误信息中暴露系统路径、密钥等
- 设置适当的日志文件权限
结语
完善的错误处理机制是Python应用稳定运行的基石。通过本文介绍的技术,你可以:
- 捕获并记录详细的错误信息
- 使用专业的日志系统管理错误
- 实现可重用的错误处理组件
- 为生产环境做好准备
记住,好的错误处理不是避免错误,而是在错误发生时能够快速定位、记录和恢复,为用户提供更好的体验。