2853 字
14 分钟
Python 异常处理与日志系统完全指南:从 try-except 到结构化日志实战
异常处理和日志记录是编写健壮 Python 程序的基础技能。良好的错误处理让程序优雅降级,完善的日志系统让问题排查有迹可循。本文从基础语法到生产实践,全面讲解 Python 异常处理与日志系统。

本文内容包括:
- try-except-else-finally 完整语法
- 异常类型与继承体系
- 自定义异常类设计
- raise 与异常链
- logging 模块深度配置
- Handler、Formatter、Filter 详解
- Loguru 现代日志库
- 结构化日志与 JSON 输出
- 生产环境日志最佳实践
- 异常处理设计模式
一、异常处理基础
1.1 try-except 基本语法
# 基础异常捕获try: result = 10 / 0except ZeroDivisionError: print("不能除以零")
# 捕获多个异常try: with open("data.txt", "r") as f: data = json.load(f)except FileNotFoundError: print("文件不存在")except json.JSONDecodeError: print("JSON 格式错误")
# 捕获多个异常(统一处理)try: risky_operation()except (ValueError, TypeError) as e: print(f"参数错误: {e}")1.2 try-except-else-finally
def read_config(path): try: # 尝试执行 with open(path, "r") as f: content = f.read() config = json.loads(content) except FileNotFoundError: # 异常时执行 print(f"配置文件 {path} 不存在") return {} except json.JSONDecodeError as e: # 异常时执行 print(f"配置格式错误: {e}") return {} else: # 无异常时执行 print("配置加载成功") return config finally: # 无论是否异常都执行 print("读取操作完成")
# 执行顺序测试def test_order(): try: print("1. try") raise ValueError("测试异常") except ValueError: print("2. except") else: print("3. else (不会执行)") finally: print("4. finally")
test_order()# 输出:# 1. try# 2. except# 4. finally1.3 捕获所有异常
# 方法1:捕获 Exception(推荐)try: risky_operation()except Exception as e: print(f"发生错误: {type(e).__name__}: {e}")
# 方法2:捕获 BaseException(包含系统退出信号,慎用)try: risky_operation()except BaseException as e: print(f"发生错误: {e}")
# 方法3:空 except(不推荐,捕获所有包括 KeyboardInterrupt)try: risky_operation()except: print("发生错误")1.4 异常信息获取
try: 1 / 0except ZeroDivisionError as e: # 异常对象 print(f"异常类型: {type(e).__name__}") # ZeroDivisionError print(f"异常信息: {e}") # division by zero
# 完整堆栈 import traceback print("堆栈追踪:") traceback.print_exc()
# 格式化为字符串 stack_trace = traceback.format_exc() print(f"完整追踪:\n{stack_trace}")二、异常类型与继承体系
2.1 Python 内置异常层次
BaseException├── SystemExit # sys.exit() 触发├── KeyboardInterrupt # Ctrl+C 触发├── GeneratorExit # 生成器关闭时└── Exception # 所有常规异常的基类 ├── ArithmeticError │ └── ZeroDivisionError ├── LookupError │ ├── IndexError │ └── KeyError ├── TypeError ├── ValueError │ └── UnicodeDecodeError ├── RuntimeError │ └── RecursionError ├── OSError │ ├── FileNotFoundError │ ├── PermissionError │ └── TimeoutError ├── ImportError │ └── ModuleNotFoundError ├── AttributeError ├── NameError │ └── UnboundLocalError ├── AssertionError ├── StopIteration └── Warning ├── UserWarning ├── DeprecationWarning └── RuntimeWarning2.2 常见异常场景
# ValueError:值不符合要求try: int("abc")except ValueError as e: print(f"无法转换: {e}")
# TypeError:类型错误try: len(123)except TypeError as e: print(f"类型错误: {e}")
# KeyError:字典键不存在data = {"name": "Alice"}try: age = data["age"]except KeyError: age = data.get("age", 0) # 安全获取
# IndexError:索引越界items = [1, 2, 3]try: item = items[10]except IndexError: item = None
# AttributeError:属性不存在try: obj.nonexistent_method()except AttributeError: print("方法不存在")三、自定义异常类
3.1 基础自定义异常
# 基础异常类class BusinessError(Exception): """业务逻辑异常基类""" pass
class ValidationError(BusinessError): """数据验证失败""" pass
class NotFoundError(BusinessError): """资源不存在""" pass
class PermissionDeniedError(BusinessError): """权限不足""" pass
# 使用class UserService: def get_user(self, user_id: int): if user_id <= 0: raise ValidationError("用户ID必须为正整数")
user = self.db.query(user_id) if not user: raise NotFoundError(f"用户 {user_id} 不存在")
return user
# 捕获try: user = service.get_user(-1)except ValidationError as e: print(f"参数错误: {e}")except NotFoundError as e: print(f"未找到: {e}")except BusinessError as e: print(f"业务错误: {e}")3.2 带状态码的异常
class APIError(Exception): """API 异常,包含状态码和详情"""
def __init__(self, message: str, status_code: int = 500, details: dict = None): super().__init__(message) self.status_code = status_code self.details = details or {}
def to_dict(self) -> dict: return { "error": str(self), "status_code": self.status_code, "details": self.details }
class BadRequestError(APIError): def __init__(self, message: str, details: dict = None): super().__init__(message, 400, details)
class UnauthorizedError(APIError): def __init__(self, message: str = "未授权"): super().__init__(message, 401)
class ForbiddenError(APIError): def __init__(self, message: str = "禁止访问"): super().__init__(message, 403)
class NotFoundError(APIError): def __init__(self, resource: str): super().__init__(f"{resource} 不存在", 404)
# 使用def get_user(user_id: int): if not current_user.is_authenticated: raise UnauthorizedError()
user = db.query(user_id) if not user: raise NotFoundError("用户")
return user
# 异常处理装饰器def handle_api_errors(func): def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except APIError as e: return jsonify(e.to_dict()), e.status_code except Exception as e: logger.exception("未预期的错误") return jsonify({"error": "服务器内部错误"}), 500 return wrapper3.3 异常链(Exception Chaining)
# 保留原始异常信息def parse_config(path: str): try: with open(path, "r") as f: content = f.read() return json.loads(content) except FileNotFoundError as e: raise ConfigError(f"配置文件缺失: {path}") from e except json.JSONDecodeError as e: raise ConfigError(f"配置文件格式错误: {path}") from e
# 使用try: config = parse_config("app.json")except ConfigError as e: print(f"配置错误: {e}") print(f"原始错误: {e.__cause__}") # 查看原始异常
# 显式链式异常try: risky_operation()except ValueError: raise BusinessError("业务处理失败") from None # 不保留原始异常四、上下文管理器与异常
4.1 自定义上下文管理器
from contextlib import contextmanager
@contextmanagerdef database_connection(): """数据库连接上下文管理器""" conn = create_connection() try: yield conn except Exception: conn.rollback() raise finally: conn.close()
# 使用with database_connection() as conn: conn.execute("INSERT INTO users ...") conn.commit()
# 带重试的上下文管理器import timefrom functools import wraps
@contextmanagerdef retry_on_error(max_retries=3, delay=1, exceptions=(Exception,)): """出错时自动重试""" for attempt in range(max_retries): try: yield attempt return except exceptions as e: if attempt == max_retries - 1: raise print(f"第 {attempt + 1} 次失败: {e},{delay}秒后重试...") time.sleep(delay)
# 使用with retry_on_error(max_retries=3, exceptions=(ConnectionError,)): fetch_data_from_api()4.2 suppress 上下文管理器
from contextlib import suppress
# 忽略特定异常with suppress(FileNotFoundError): os.remove("temp_file.txt")
# 等价于try: os.remove("temp_file.txt")except FileNotFoundError: pass五、logging 模块深度配置
5.1 基础配置
import logging
# 基础配置logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
logger = logging.getLogger("myapp")logger.info("应用启动")logger.warning("配置项缺失,使用默认值")logger.error("数据库连接失败")5.2 日志级别
| 级别 | 数值 | 用途 |
|---|---|---|
| DEBUG | 10 | 调试信息,开发时使用 |
| INFO | 20 | 正常运行信息 |
| WARNING | 30 | 警告,程序仍正常运行 |
| ERROR | 40 | 错误,某项功能失败 |
| CRITICAL | 50 | 严重错误,程序可能崩溃 |
# 设置不同级别的日志logger.debug("调试信息: %s", debug_data)logger.info("用户 %s 登录", username)logger.warning("磁盘空间不足: %d%%", disk_usage)logger.error("请求失败: %s", error_msg)logger.critical("系统内存耗尽,即将退出")5.3 高级配置(代码方式)
import loggingimport logging.handlersimport sys
def setup_logging(name: str = "app", level: int = logging.INFO): """配置日志系统"""
logger = logging.getLogger(name) logger.setLevel(level)
# 清除已有处理器 logger.handlers = []
# 格式 formatter = logging.Formatter( "%(asctime)s | %(name)s | %(levelname)-8s | %(filename)s:%(lineno)d | %(message)s", datefmt="%Y-%m-%d %H:%M:%S" )
# 1. 控制台输出 console_handler = logging.StreamHandler(sys.stdout) console_handler.setLevel(logging.INFO) console_handler.setFormatter(formatter) logger.addHandler(console_handler)
# 2. 文件输出(按大小轮转) file_handler = logging.handlers.RotatingFileHandler( "app.log", maxBytes=10 * 1024 * 1024, # 10MB backupCount=5 ) file_handler.setLevel(logging.DEBUG) file_handler.setFormatter(formatter) logger.addHandler(file_handler)
# 3. 错误日志(单独文件) error_handler = logging.handlers.RotatingFileHandler( "error.log", maxBytes=10 * 1024 * 1024, backupCount=10 ) error_handler.setLevel(logging.ERROR) error_handler.setFormatter(formatter) logger.addHandler(error_handler)
# 4. 按时间轮转(每天一个文件) daily_handler = logging.handlers.TimedRotatingFileHandler( "daily.log", when="midnight", interval=1, backupCount=30 ) daily_handler.setLevel(logging.INFO) daily_handler.setFormatter(formatter) logger.addHandler(daily_handler)
return logger
# 使用logger = setup_logging()logger.info("应用启动")5.4 配置文件方式
version: 1disable_existing_loggers: false
formatters: standard: format: "%(asctime)s | %(name)s | %(levelname)-8s | %(message)s" datefmt: "%Y-%m-%d %H:%M:%S" detailed: format: "%(asctime)s | %(name)s | %(levelname)-8s | %(filename)s:%(lineno)d | %(funcName)s | %(message)s" datefmt: "%Y-%m-%d %H:%M:%S"
handlers: console: class: logging.StreamHandler level: INFO formatter: standard stream: ext://sys.stdout
file: class: logging.handlers.RotatingFileHandler level: DEBUG formatter: detailed filename: logs/app.log maxBytes: 10485760 # 10MB backupCount: 5
error_file: class: logging.handlers.RotatingFileHandler level: ERROR formatter: detailed filename: logs/error.log maxBytes: 10485760 backupCount: 10
loggers: myapp: level: DEBUG handlers: [console, file, error_file] propagate: false
root: level: WARNING handlers: [console]import logging.configimport yaml
# 从 YAML 加载配置with open("logging.yaml", "r") as f: config = yaml.safe_load(f) logging.config.dictConfig(config)
logger = logging.getLogger("myapp")六、Loguru 现代日志库
6.1 Loguru 基础用法
# 安装: pip install logurufrom loguru import logger
# 开箱即用,无需配置logger.info("这是一条信息")logger.warning("这是一条警告")logger.error("这是一条错误")
# 格式化输出logger.info("用户 {} 执行了 {}", "Alice", "delete")
# 结构化日志logger.info("用户登录", user_id=123, ip="192.168.1.1")6.2 Loguru 高级配置
from loguru import loggerimport sys
# 移除默认处理器logger.remove()
# 添加控制台输出logger.add( sys.stdout, format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | " "<level>{level: <8}</level> | " "<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> | " "<level>{message}</level>", level="INFO", colorize=True)
# 添加文件输出(自动轮转)logger.add( "logs/app_{time:YYYY-MM-DD}.log", rotation="00:00", # 每天轮转 retention="30 days", # 保留30天 compression="zip", # 压缩旧日志 format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function}:{line} | {message}", level="DEBUG")
# 添加错误日志(单独文件)logger.add( "logs/error.log", rotation="10 MB", retention="10 days", level="ERROR", backtrace=True, # 详细回溯 diagnose=True # 变量诊断)
# 使用logger.info("应用启动")logger.error("发生错误", exc_info=True)6.3 Loguru 异常捕获
from loguru import logger
# 自动捕获异常并记录@logger.catchdef risky_function(x, y): return x / y
# 调用时出错会自动记录risky_function(1, 0) # 自动记录完整异常信息
# 自定义异常处理@logger.catch(reraise=True)def must_raise(): raise ValueError("必须抛出的异常")
# 上下文管理器with logger.catch(): 1 / 0 # 自动记录
# 异步异常捕获@logger.catchasync def async_risky(): raise RuntimeError("异步错误")6.4 结构化日志与上下文
from loguru import logger
# 添加上下文绑定logger = logger.bind(request_id="req-123", user="Alice")logger.info("处理请求") # 自动包含 request_id 和 user
# 上下文管理器with logger.contextualize(task_id="task-456"): logger.info("执行任务") # 包含 task_id logger.info("任务完成")
# JSON 结构化输出(生产环境推荐)logger.remove()logger.add("logs/app.json", serialize=True)
logger.info("用户登录", user_id=123, ip="192.168.1.1")# 输出:# {# "text": "用户登录",# "record": {# "time": {"repr": "2026-07-23 10:00:00", "timestamp": 1753236000},# "level": {"name": "INFO", "no": 20},# "extra": {"user_id": 123, "ip": "192.168.1.1"}# }# }七、生产环境最佳实践
7.1 异常处理模式
# 模式1:提前返回(Guard Clause)def process_user(user_id: int) -> dict: if user_id <= 0: raise ValueError("用户ID必须为正整数")
user = db.get_user(user_id) if not user: raise NotFoundError(f"用户 {user_id} 不存在")
if not user.is_active: raise ForbiddenError("用户已被禁用")
return user.to_dict()
# 模式2:异常转换def fetch_data(): try: response = requests.get(API_URL, timeout=5) response.raise_for_status() return response.json() except requests.Timeout: raise APIError("请求超时", status_code=504) except requests.HTTPError as e: raise APIError(f"HTTP错误: {e.response.status_code}", status_code=e.response.status_code)
# 模式3:回退策略def get_data_with_fallback(): try: return fetch_from_primary() except PrimaryError: logger.warning("主数据源失败,使用备用数据源") return fetch_from_backup()7.2 日志记录规范
import loggingfrom contextvars import ContextVar
# 请求ID上下文request_id_var: ContextVar[str] = ContextVar("request_id", default="")
class RequestIdFilter(logging.Filter): """自动添加请求ID到日志""" def filter(self, record): record.request_id = request_id_var.get() return True
# 配置logger = logging.getLogger("app")handler = logging.StreamHandler()handler.setFormatter(logging.Formatter( "%(asctime)s | %(request_id)s | %(levelname)s | %(message)s"))handler.addFilter(RequestIdFilter())logger.addHandler(handler)
# 使用from contextlib import contextmanager
@contextmanagerdef set_request_id(request_id: str): token = request_id_var.set(request_id) try: yield finally: request_id_var.reset(token)
# 在请求处理中with set_request_id("req-123"): logger.info("处理请求") # 输出: 2026-07-23 10:00:00 | req-123 | INFO | 处理请求7.3 性能优化
# 避免字符串拼接开销# ❌ 不好(每次都会执行字符串拼接)logger.debug("Data: " + expensive_operation())
# ✅ 好(使用延迟求值)logger.debug("Data: {}", expensive_operation())
# ✅ 更好(先检查级别)if logger.isEnabledFor(logging.DEBUG): logger.debug("Data: %s", expensive_operation())
# Loguru 自动优化(使用 {} 格式化)logger.debug("Data: {}", expensive_operation()) # 自动延迟求值八、总结
异常处理检查清单
✅ 捕获具体异常,不要用裸 except✅ 使用 try-except-else-finally 完整结构✅ 自定义异常继承合适的基类✅ 使用 raise ... from ... 保留异常链✅ 异常信息要包含上下文✅ 不要吞掉异常(至少记录日志)✅ 资源清理用 finally 或上下文管理器日志系统检查清单
✅ 使用结构化日志(JSON)便于解析✅ 添加请求ID便于追踪✅ 不同级别输出到不同目的地✅ 配置日志轮转防止磁盘占满✅ 敏感信息(密码、token)不要记录✅ 异常使用 logger.exception() 自动记录堆栈✅ 生产环境考虑异步日志避免阻塞logging vs Loguru 对比
| 特性 | logging | Loguru |
|---|---|---|
| 配置复杂度 | 复杂 | 简单 |
| 结构化日志 | 需手动实现 | 原生支持 |
| 异常捕获 | 手动 | @logger.catch |
| 异步支持 | 需额外配置 | 内置 |
| 日志轮转 | 需 Handler | 内置 |
| 依赖 | 标准库 | 第三方 |
建议:新项目优先使用 Loguru,legacy 项目逐步迁移。核心原则是:良好的异常处理让程序优雅降级,完善的日志让问题有迹可循。
Python 异常处理与日志系统完全指南:从 try-except 到结构化日志实战
https://971918.xyz/posts/python-guide/python-exception-logging-guide/