运维需异常含可定位上下文,如req_id、trace_id及具体字段;须保留异常链、区分类型、结构化日志并透传trace_id。

异常信息里必须包含可定位的上下文
运维最怕看到 ValueError: invalid literal for int() 这种没上下文的报错——根本不知道是哪个字段、哪条数据、哪个服务实例出的问题。关键不是“抛没抛异常”,而是异常里有没有让运维能立刻查日志、翻监控、定位到具体请求的线索。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 在捕获并重新抛出异常时,用
raise ValueError(f"parse_user_id failed for request_id={req_id}, raw_data={raw}") from e补全上下文,而不是直接raise e - 避免裸字符串拼接敏感字段(如用户手机号),可用
repr(data.get('user_id'))或脱敏函数处理 - 确保
req_id、trace_id、service_name等标识已注入当前 logger 或异常链中
不要吞掉原始异常链(raise ... from e 不能少)
常见错误是写成 except Exception as e: log.error("xxx"); raise Exception("业务失败"),这直接切断了异常栈,运维看不到底层是数据库超时还是 JSON 解析失败。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 所有二次包装异常必须显式使用
raise NewException(...) from e,Python 3 默认保留__cause__和 traceback - 用
logging.exception()记录时,它会自动打印完整 traceback;但若用了logging.error(str(e))就只剩一行字符串 - 检查中间件或装饰器是否无意中吃掉了异常(比如某些 Flask 错误处理器返回 500 响应却不 re-raise)
区分异常类型,别全用 Exception 或 RuntimeError
运维排查时依赖异常类型做聚合和告警分级。如果所有错误都抛 RuntimeError,监控系统就无法区分“第三方 API 临时不可用”和“配置文件缺失”这两类完全不同的处置优先级。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 定义清晰的业务异常基类,如
class ExternalServiceError(Exception)、class ConfigLoadError(Exception) - 对可预期的失败(如参数校验不通过),抛
ValueError或自定义ValidationError;对不可控故障(如 Redis 连接中断),抛带重试语义的TransientNetworkError - 避免在 except 子句里用太宽泛的
except Exception:,优先捕获具体类型,防止掩盖真正的 bug
日志格式要兼容 ELK / Loki 的结构化解析
运维通常靠 error_type: "DatabaseTimeout"、error_code: "DB_CONN_002" 这类字段快速筛选。如果异常信息全塞在 message 字段里,就得靠正则硬抠,效率低还容易漏。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 在 logger 配置中启用 structured logging(如用
structlog或python-json-logger),把异常类型、错误码、关键变量作为独立字段输出 - 在异常对象里加属性,例如
exc.error_code = "AUTH_TOKEN_EXPIRED",再通过 formatter 注入日志 - 确保 traceback 被完整记录为单个字段(如
exc_info=True),而非被截断或拆成多行导致解析失败










