Skip to content

12 · 错误处理、日志与配置 ​

后端不只是"能返回正确结果",还要优雅地返回错误、完整地留下痕迹、灵活地切换配置。前端在这三块最容易轻敌。

速览卡 ​

项内容
一句话定位三个"生产级"基本功:统一错误体、结构化日志、分层配置
核心内容错误体设计、异常处理机制、日志分级与结构、配置分层与密钥管理
读完能回答后端错误码怎么设计?日志怎么打才能查问题?配置怎么管理才能本地/测试/生产切换?
相关板块C02 · API 设计 | C08 · 可观测性 | C09 · 服务端语言上手

一、统一错误体 ​

前端最烦"接口返回一堆不同格式的报错"。后端要从第一天就统一:

json
{
  "error": {
    "code": "ITEM_NOT_FOUND",
    "message": "商品不存在",
    "details": { "item_id": "42" }
  }
}
字段用途约定
code机器可读的错误码大写下划线,前端据此分支
message人可读的提示面向最终用户
details调试用细节不要泄露敏感信息

错误码 vs 状态码:HTTP 状态码粗(404/400/500),错误码细(ITEM_NOT_FOUND)。前端判断逻辑用错误码,HTTP 码给网关和监控用。


二、异常处理机制 ​

python
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse

app = FastAPI()


class BusinessError(Exception):
    def __init__(self, code: str, message: str, status: int = 400):
        self.code = code
        self.message = message
        self.status = status


@app.exception_handler(BusinessError)
async def business_error_handler(request: Request, exc: BusinessError):
    return JSONResponse(
        status_code=exc.status,
        content={"error": {"code": exc.code, "message": exc.message}},
    )

套路:业务代码只抛领域异常,由统一 handler 转成错误体。业务层不碰 HTTP,错误格式集中管理。

2.1 意外异常的兜底 ​

python
@app.exception_handler(Exception)
async def unhandled(request: Request, exc: Exception):
    # 记录完整堆栈到日志,返回给客户端一个不泄露细节的 500
    return JSONResponse(status_code=500, content={"error": {"code": "INTERNAL"}})

原则:客户端永远不要看到堆栈和内部细节(信息泄露),但服务端必须完整记录堆栈用于排查。


三、日志:结构化的才可查 ​

3.1 分级 ​

级别何时用例子
DEBUG开发调试,生产默认关中间变量
INFO关键流程用户登录、下单成功
WARNING可恢复的异常重试成功、限流触发
ERROR操作失败数据库连不上
CRITICAL服务不可用启动失败

3.2 用 logging 而非 print ​

python
import logging

logger = logging.getLogger("my_api")


def create_order(...):
    logger.info("order created", extra={"order_id": oid, "user_id": uid})

为什么不用 print:logging 有级别、能进日志系统、能按环境配置;print 打到 stdout 没有结构。

3.3 结构化日志 ​

生产里推荐 JSON 日志(logger.info(..., extra={"order_id": oid}) 配合 JSON formatter),这样可被日志平台按字段检索:

json
{"ts": "...", "level": "INFO", "logger": "my_api", "msg": "order created", "order_id": "42"}

与可观测性(链路追踪、指标)的关系见 C08。


四、配置分层 ​

层来源优先级
默认值代码里 Settings 的默认最低
本地覆盖.env / config.local中
环境注入部署平台的环境变量高
密钥管理密钥服务(如云厂商 Secret Manager)最高
python
from pydantic_settings import BaseSettings


class Settings(BaseSettings):
    env: str = "dev"
    database_url: str
    redis_url: str = "redis://localhost:6379/0"
    secret_key: str = ""

    model_config = {"env_file": ".env", "extra": "ignore"}

密钥管理原则:代码仓库里永不提交密钥。本地用 .env(加入 .gitignore),生产用环境变量或密钥服务。


自检清单 ​

  • [ ] 能设计统一错误体(code/message/details),并说清与 HTTP 状态码的分工
  • [ ] 会用异常 handler 集中处理业务错误和兜底 500
  • [ ] 会正确使用日志分级,用 logging 而不用 print
  • [ ] 会按"默认值 → 本地 → 环境 → 密钥服务"分层管理配置
  • [ ] 确认密钥没有提交进 git

常见误区 ​

  • 误区:错误码就是 HTTP 状态码。 状态码粗、错误码细,两个都要有,前端按错误码分支。
  • 误区:把堆栈返回给客户端。 这是信息泄露;完整堆栈只进服务端日志。
  • 误区:print 够用。 没有级别、没有结构、进不了日志系统,排查会非常痛苦。
  • 误区:配置写在代码里。 环境差异(本地/测试/生产)靠配置切换,硬编码一换环境就炸。

动手练习 ​

练习 A:给 Todo API 加统一错误体 ​

给 C09 的 Todo API 增加 ITEM_NOT_FOUND 错误码和兜底 handler,前端能根据错误码分支。

练习 B:结构化日志 ​

给下单流程打结构化 INFO 日志(含 order_id/user_id),配 JSON formatter,在日志里检索一个订单。

练习 C:三层配置 ​

用 BaseSettings 实现 dev/test/prod 三套配置,并把密钥全部移出代码。


相关文档 ​

本区 ​

其他域 ​