Skip to content

为什么要用 logging 而不是 print ​

项目里到处用 print() 记录过程信息,会带来三个问题:

  1. 说不清什么时间、什么位置输出的——终端里只有一行文字,排错时很难定位
  2. 想关掉这些日志怎么办? 得把每一行 print 找出来删掉或注释掉
  3. 没法按重要性过滤——调试信息、正常信息、错误信息混在一起

用 logging 模块 ​

python
from fastapi import FastAPI
from fastapi.responses import FileResponse
import logging

# 配置日志的基本信息
logging.basicConfig(
    level=logging.INFO,   # 日志级别
    format="%(asctime)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s"
)

app = FastAPI()

@app.get("/")
def root():
    logging.info("访问首页~")
    return FileResponse("static/index.html")

format 里的占位符:

占位符含义
%(asctime)s时间
%(levelname)s日志级别
%(filename)s文件名
%(lineno)d行号
%(message)s日志信息

输出的样子:

text
2026-09-20 15:04:12,301 - INFO - [main.py:29] - 访问首页~

日志级别 ​

日志级别是给日志信息贴上的"重要性标签",常见级别从低到高:

text
DEBUG  <  INFO  <  WARNING  <  ERROR  <  FATAL

basicConfig(level=...) 设成哪个级别,就只输出该级别及以上的日志。比如设 level=logging.ERROR,那些 logging.info(...) 就都不会打印了——一行配置就关掉了所有低级别日志,这正是 logging 比 print 强的地方。

级别用途
DEBUG调试细节(开发时用)
INFO正常运行的记录
WARNING警告(还能跑,但要注意)
ERROR出错了
FATAL致命错误

统一异常处理 ​

项目功能多,不可能在每个接口里都写 try/except。FastAPI 提供了统一异常处理器:

python
from starlette.requests import Request
from starlette.responses import JSONResponse

@app.exception_handler(Exception)
def handle_exception(request: Request, exc: Exception):
    logging.error(f"处理异常, 请求路径: {request.url}, 异常信息: {exc}")
    return JSONResponse(content={"code": 500, "message": "服务器内部错误, 请联系管理员~", "data": None})
要点说明
@app.exception_handler(Exception)注册"捕获所有异常"的处理器(Exception 是所有异常的父类)
request: Request出事的那次请求(可以从里面拿到 request.url)
exc: Exception捕获到的异常对象
JSONResponse(...)返回的对象类型必须是 Response,用 JSONResponse 返回 JSON

处理前后的对比:

json
{"code": 200, "message": "AI回复成功", "data": "..."}
{"code": 500, "message": "服务器内部异常", "data": null}

NOTE

处理器里返回的 JSONResponse 没有设置 HTTP 状态码,所以浏览器/客户端拿到的 HTTP 状态码还是 200,只有响应体里的 code 是 500。这是本项目的约定(统一响应体里的业务码);如果想让 HTTP 状态码也是 500,写成 JSONResponse(status_code=500, content={...}) 即可。

TIP

统一异常处理的价值:接口报错时不再返回一大段 Python 堆栈,而是返回和正常响应同样格式的 JSON。前端只要判断 code 就能优雅提示用户,日志里也留下了具体是哪个路径、什么异常。

相关 ​

练习题 ​

一、知识回顾(读完直接做下面的实践题) ​

  1. 用 print 记录日志的问题:说不清时间和位置、想关掉要一行行删、无法按重要性过滤
  2. 配置日志:logging.basicConfig(level=..., format=...)
  3. format 里 %(asctime)s 是时间、%(levelname)s 是日志级别、%(filename)s 是文件名、%(lineno)d 是行号
  4. 日志级别从低到高:DEBUG、INFO、WARNING、ERROR、FATAL;设置 level 后只输出该级别及以上的日志
  5. 注册统一异常处理器:@app.exception_handler(Exception),函数形参一般是 request: Request 和 exc: Exception
  6. 异常处理器返回的对象类型必须是 Response,所以用 JSONResponse 返回 JSON

二、裸写题 ​

  • - 提示(先自己想,实在想不出再点开)

    一级 · 思路:先 basicConfig 一次(放在程序开头),之后到处用 logging.info/error二级 · 方法:logging.basicConfig(level=logging.INFO, format=...)三级 · 骨架:format 用 "%(asctime)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s"

    - 参考答案(做完再点开)

    python
    # 2-1 配置日志
    import logging
    from fastapi import FastAPI
    from fastapi.testclient import TestClient
    
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s"
    )
    
    app = FastAPI(title="优化练习")
    
    @app.get("/")
    def root():
        logging.info("访问首页")
        return {"message": "ok"}
    
    if __name__ == "__main__":
        client = TestClient(app)
        print(client.get("/").json())
  • - 提示(先自己想,实在想不出再点开)

    一级 · 思路:级别设高了,低级别日志就被过滤掉 二级 · 方法:level=logging.ERROR vs level=logging.INFO三级 · 骨架:INFO < ERROR,所以设 ERROR 后 INFO 不输出

    - 参考答案(做完再点开)

    python
    # 2-2:把 level 改成 logging.ERROR 再跑,
    #      就会看到 "访问首页" 这类 INFO 日志不再输出(INFO 低于 ERROR)
  • - 提示(先自己想,实在想不出再点开)

    一级 · 思路:对比"有/没有异常处理器"两次的响应 二级 · 方法:@app.exception_handler(Exception) + JSONResponse三级 · 骨架:处理器里用 logging.error 记下 request.url 和异常信息

    - 参考答案(做完再点开)

    python
    # 2-3 统一异常处理器(日志配置与 / 接口见 2-1)
    import logging
    from fastapi import FastAPI
    from fastapi.testclient import TestClient
    from starlette.requests import Request
    from starlette.responses import JSONResponse
    
    app = FastAPI(title="优化练习")
    
    @app.get("/boom")
    def boom():
        logging.info("准备触发异常")
        return {"value": 1 / 0}     # 故意抛异常
    
    @app.exception_handler(Exception)
    def handle_exception(request: Request, exc: Exception):
        logging.error(f"处理异常, 请求路径: {request.url}, 异常信息: {exc}")
        return JSONResponse(content={"code": 500, "message": "服务器内部错误", "data": None})
    
    if __name__ == "__main__":
        # TestClient 默认会把服务端异常直接抛出来(方便调试),关掉才能看到处理器的返回
        client = TestClient(app, raise_server_exceptions=False)
        print(client.get("/boom").json())     # 被异常处理器兜住,返回 {code:500,...}