Python异常处理try-except用法:怎么捕获错误并记录日志

桃夭
桃夭
发布时间:2026-10-31 | 8人浏览过
原创

深入讲解Python异常处理try-except机制及如何规范记录日志。详细解析try-except-else-finally执行逻辑、标准库logging模块接入、logger.exception堆栈记录技巧及生产环境最佳实践。

假设你编写了一个自动化脚本,每天凌晨从多个远程接口抓取数据、清洗字段并存入数据库。如果代码中没有任何错误处理,一旦某个接口发生网络短暂超时或返回了格式异常的 JSON,整个程序就会瞬间崩溃中断,后续所有任务随之瘫痪。相反,如果随手加上一句简陋的 try ... except: pass,程序虽然不会报错中断,但数据静默丢失,你在排查数据不一致时将看不到任何报错轨迹。编写稳健程序的正确路径,是通过 try-except 明确圈定高危操作,同时接入标准库 logging 模块,把发生异常时的入参上下文与完整调用栈准确写入日志。

Python异常处理try-except用法:怎么捕获错误并记录日志
图 1:Python 异常拦截、上下文提取与日志持久化系统架构图

一、try-except 的完整结构与执行时序

在 Python 中,完整的异常处理语法由四个关键字协同完成:try、except、else 和 finally。很多初学者往往只使用前两者,忽视了后两者的关键职责:

  • try:放置需要监视的代码。这里的代码只要抛出异常,Python 执行引擎就会立刻中断后续语句,转入异常匹配流程。保持 try 代码块的简短精炼至关重要。
  • except:用于声明与捕获特定类型的异常。一旦 try 抛出的异常与当前分支类型吻合,便执行该分支内的恢复逻辑或日志记录。
  • else:在 try 块没有发生任何异常、完全正常执行完毕时触发。把依赖 try 成功结果的常规后续业务放在 else 块中,能有效防止把新的错误误判为 try 块中的错误。
  • finally:无论是否发生异常、无论异常是否被捕获,甚至在 try 或 except 中遇到了 return,finally 块中的代码都一定会执行。它常用于执行不可或缺的清理工作,如关闭文件、释放数据库连接或解除线程锁。
Python异常处理try-except用法:怎么捕获错误并记录日志
图 2:try-except-else-finally 结构状态转移与执行控制流程
代码编写原则:保持 try 块的作用域尽可能小。只将真正可能抛出异常的 I/O、网络请求或数据类型转换放入 try,将后续依赖成功结果的数据清洗与展示逻辑放入 else。

二、异常捕获规范:为什么坚决拒绝裸写 except

在编写异常捕获时,最容易犯且危害最大的错误就是直接书写裸 except: 或 except: pass。在 Python 的异常继承体系中,顶层基类是 BaseException,所有常规业务错误继承自 Exception,而系统级信号(如用户在终端按 Ctrl+C 触发的 KeyboardInterrupt,以及调用退出函数的 SystemExit)则直接继承自 BaseException。

反面教材:危险的裸 except
# 错误写法:裸 except 会捕获系统中断信号,导致终端无法正常终止程序
try:
    process_critical_data()
except:
    pass  # 吞没所有错误与中断信号,造成僵尸进程与数据黑洞

规范的捕获策略包含以下三个核心准则:

  1. 明确指定异常类型:根据调用的函数特性,精确捕获可预期的异常,例如文件操作捕获 FileNotFoundError,网络请求捕获 requests.RequestException,数据类型转换捕获 ValueError。
  2. 提取异常实例对象:使用 except SpecificError as e 语法,将异常实例绑定到变量 e,以便提取错误消息 str(e) 或类型名称。
  3. 兜底捕获必须使用 Exception:如果确实需要在一个保护区内防止任何未知业务崩溃,显式声明 except Exception as e:。这样既能兜底所有常规运行时错误,又不会误伤 KeyboardInterrupt 和 SystemExit。

三、规范记录日志:标准库 logging 接入四步法

很多开发者在刚开始排查问题时习惯使用 print(e)。然而在生产环境或后台守护进程中,print() 存在致命缺陷:它没有时间戳、无法按严重级别分流、不支持自动切分文件、无法提供行号,且多线程并发时输出极易交错混乱。Python 标准库自带的 logging 模块是解决这一问题的专业工具。

Python异常处理try-except用法:怎么捕获错误并记录日志
图 3:捕获异常并规范记录日志的四步操作流程

步骤一:创建模块级 Logger 并配置文件输出

避免在全局反复调用 logging.info(),应当在每个模块的开头通过 logging.getLogger(__name__) 获取具有层级命名空间的 Logger 实例,并为其配置带时间戳和行号的输出格式。

步骤 1 示例代码:初始化配置 Logger
import logging
from logging.handlers import RotatingFileHandler

# 1. 获取以当前模块名为标识的 logger 实例
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)  # 允许捕获的最低日志级别

# 2. 定义统一的日志格式(时间、级别、模块名、所在行号、消息)
log_formatter = logging.Formatter(
    fmt="%(asctime)s [%(levelname)s] [%(name)s:%(lineno)d] - %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S"
)

# 3. 创建输出到控制台的 Handler
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_handler.setFormatter(log_formatter)

# 4. 创建自动滚动的文件 Handler(单文件最大 5MB,保留 3 个备份)
file_handler = RotatingFileHandler(
    filename="app_error.log",
    maxBytes=5 * 1024 * 1024,
    backupCount=3,
    encoding="utf-8"
)
file_handler.setLevel(logging.ERROR)
file_handler.setFormatter(log_formatter)

# 5. 绑定到 logger
if not logger.handlers:
    logger.addHandler(console_handler)
    logger.addHandler(file_handler)

步骤二与步骤三:划定最小范围并注入业务上下文

当异常发生时,单看错误类型往往不够,排查人员需要知道“究竟是在处理哪一个用户、哪张订单、哪个文件时报的错”。在 except 代码块中,应立即将当前迭代的关键参数拼接到日志文本中。

步骤四:使用 logger.exception 记录完整调用栈

这是异常处理中最核心的技巧:在 except 分支内部,直接调用 logger.exception(...)。该方法会自动调用 sys.exc_info(),在输出错误信息的同时,将完整的异常堆栈回溯(Traceback)完整写入日志。它等价于 logger.error(..., exc_info=True)。

四、信息维度对比:print vs logger.error vs logger.exception

理解为什么生产代码必须使用 logger.exception(),最直接的方法是对比它们在实际发生异常时记录的信息完整度。

Python异常处理try-except用法:怎么捕获错误并记录日志
图 4:print 与不同 logging 方法在错误记录维度上的详细对比

我们以一个除以零的计算异常为例,直观观察三者的输出差异:

记录方式 调用代码示例 实际日志输出效果 排查评价
print(e) print(f"出错: {e}") 出错: division by zero 丢失时间、级别、文件名和行号,无法在生产中定位故障点。
logger.error(e) logger.error(f"出错: {e}") 2026-10-30 10:05:01 [ERROR] [calc:42] - 出错: division by zero 包含时间和级别,但无调用链回溯,若错误发生在深层子函数中将无从查找。
logger.exception() logger.exception("计算失败") 2026-10-30 10:05:01 [ERROR] [calc:42] - 计算失败 Traceback (most recent call last): File "worker.py", line 18, in run val = divide(a, b) ZeroDivisionError: division by zero 包含完整时间戳、模块元数据与多层函数回溯调用链,秒级定位报错代码行。

五、生产级实战完整代码示例

下面是一个符合生产规范的完整 Python 示例,模拟从本地磁盘批量读取 JSON 数据文件、执行解析并更新系统状态的过程。该示例演示了如何结合使用 try-except-else-finally 与 logging 记录详细上下文与堆栈。

完整可执行代码:batch_data_loader.py
import json
import logging
import os
import sys

# -------------------------------------------------------------
# 1. 初始化标准日志配置
# -------------------------------------------------------------
logger = logging.getLogger("BatchProcessor")
logger.setLevel(logging.DEBUG)

formatter = logging.Formatter(
    fmt="%(asctime)s [%(levelname)s] [%(name)s] - %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S"
)

console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(formatter)
if not logger.handlers:
    logger.addHandler(console_handler)

# -------------------------------------------------------------
# 2. 核心业务处理函数(带完整异常拦截与日志)
# -------------------------------------------------------------
def process_user_record(file_path: str, record_id: int) -> dict:
    """
    读取并解析指定用户的记录文件。
    演示精准捕获、业务参数注入、日志记录与资源释放。
    """
    file_obj = None
    logger.info("开始处理用户记录任务: RecordID=%d, FilePath=%s", record_id, file_path)

    try:
        # 仅将易发生 I/O 与解析错误的代码放入 try 块
        file_obj = open(file_path, "r", encoding="utf-8")
        data = json.load(file_obj)

        # 模拟可能出现的数据格式校验
        if "score" not in data:
            raise KeyError("数据包缺失必要字段: 'score'")

        score_value = float(data["score"])
        normalized_ratio = 100.0 / score_value  # 若 score_value 为 0 将引发 ZeroDivisionError

    except FileNotFoundError:
        # 精确捕获特定已知异常:记录 WARNING 级日志并返回默认占位数据
        logger.warning("目标记录文件不存在,跳过本次解析: RecordID=%d, Path=%s", record_id, file_path)
        return {"record_id": record_id, "status": "skipped", "reason": "file_not_found"}

    except (json.JSONDecodeError, KeyError) as e:
        # 数据格式畸变:记录 ERROR 级日志并附带完整堆栈
        logger.exception("数据格式畸变导致解析失败: RecordID=%d, ErrorType=%s", record_id, type(e).__name__)
        return {"record_id": record_id, "status": "failed", "reason": str(e)}

    except Exception as e:
        # 未预期的严重运行时异常:完整记录堆栈并重新抛出,防止脏数据入库
        logger.exception("处理记录时遭遇未知致命异常: RecordID=%d", record_id)
        raise

    else:
        # 当且仅当 try 内无任何异常时执行的逻辑
        logger.info("用户记录解析成功: RecordID=%d, NormalizedRatio=%.2f", record_id, normalized_ratio)
        return {"record_id": record_id, "status": "success", "ratio": normalized_ratio}

    finally:
        # 确保打开的文件句柄一定得到关闭释放
        if file_obj and not file_obj.closed:
            file_obj.close()
            logger.debug("已安全关闭文件资源: Path=%s", file_path)
示例运行输出(当传入损坏的 JSON 格式文件时)
2026-10-30 10:06:12 [INFO] [BatchProcessor] - 开始处理用户记录任务: RecordID=8041, FilePath=test_bad.json
2026-10-30 10:06:12 [ERROR] [BatchProcessor] - 数据格式畸变导致解析失败: RecordID=8041, ErrorType=JSONDecodeError
Traceback (most recent call last):
  File "batch_data_loader.py", line 33, in process_user_record
    data = json.load(file_obj)
  File "/usr/lib/python3.9/json/__init__.py", line 293, in load
    return loads(fp.read(),
  File "/usr/lib/python3.9/json/__init__.py", line 346, in loads
    return _default_decoder.decode(s)
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
2026-10-30 10:06:12 [DEBUG] [BatchProcessor] - 已安全关闭文件资源: Path=test_bad.json

六、异常处理与日志记录的最佳实践准则

在长期运行的 Python 系统中,异常处理与日志记录不仅关乎单一函数的正确性,更直接决定了系统的稳定性与运维可观测性。以下三项准则应当作为团队开发的固定基准:

Python异常处理try-except用法:怎么捕获错误并记录日志
图 5:Python 异常处理与日志记录最佳实践准则与防线架构

1. 捕获与控制原则

  • 拒绝盲目吞没(Swallowing Exceptions):捕获异常后如果什么都不做(如 except: pass),不仅会让业务在不一致的状态下继续执行,更会切断所有排查线索。无论如何都必须至少记录一行包含上下文的日志。
  • 捕获层级明确:底层库应当专注于抛出有意义的特定异常;业务层捕获后将其转换为带有上下文的错误日志或业务错误响应;应用最外层通过统一中间件进行最终 Exception 兜底。

2. 日志记录原则

  • 单一职责记录,避免堆栈风暴:如果在调用链的每一层都使用 logger.exception() 记录同一处异常并层层向上 raise,日志文件中就会出现数段完全相同的 Traceback 刷屏。原则是:要么在捕获处记录堆栈并就地解决/降级,要么向上传播交由上层统一记录,不要两处重复记录。
  • 使用占位符而非字符串格式化拼接:记录日志时推荐使用 logger.error("用户 %s 登录失败", user_id),而不是 logger.error(f"用户 {user_id} 登录失败")。前者由 logging 引擎在真正满足日志输出级别时才进行字符串插值计算,性能更优。

3. 资源治理原则

  • 优先使用上下文管理器(with 语句):对于文件、网络套接字、互斥锁等资源,优先使用 with 语句管理其生命周期。with 内部自动封装了 try...finally 机制,即使在代码块中发生异常也能确保资源被可靠释放。
相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表