Python 现代开发学习笔记

(二十三)用请求编号追踪日志

第六章已经用 logging 代替零散的 print。组合项目后,日志还应能回答“这条错误对应哪一次请求”。给每次请求一个编号,并在响应中返回它,客户端就能带着编号复查。

# 项目文件:catalog_project/src/cataloghub/telemetry.py
import json
import logging
import os
import re
import time
from uuid import uuid4


def install_logging(app, name):
    logging.basicConfig(level=os.getenv("LOG_LEVEL", "INFO"), format="%(message)s")
    logger = logging.getLogger(name)

    @app.middleware("http")
    async def trace(request, call_next):
        incoming = request.headers.get("X-Request-ID", "")
        request_id = (
            incoming if re.fullmatch(r"[A-Za-z0-9_-]{1,64}", incoming) else uuid4().hex
        )
        started, status = time.perf_counter(), 500
        try:
            response = await call_next(request)
            status = response.status_code
            response.headers["X-Request-ID"] = request_id
            return response
        finally:
            # 用同一编号关联一次请求,不把整份输入正文写入请求日志。
            logger.info(
                json.dumps(
                    {
                        "event": "request",
                        "service": name,
                        "request_id": request_id,
                        "method": request.method,
                        "path": request.url.path,
                        "status": status,
                        "elapsed_ms": round((time.perf_counter() - started) * 1000, 3),
                    }
                )
            )

中间件在路由前开始计时,调用 call_next() 后取得状态码;finally 记录路径、状态与耗时。客户端传来合适的 X-Request-ID 就沿用,其他情况生成新编号。模型项目使用 同样的日志实现。

# 指定编号,比较响应头中的 X-Request-ID 与服务终端的一行日志。
curl -i -H "X-Request-ID: reading-demo-1" "http://127.0.0.1:8781/api/books?q=Python"

日志行会包含 request_id="reading-demo-1"、路径、状态码与本次耗时。启动、停止和工作进程信息仍由 Uvicorn 输出,应用的请求记录则是一行 JSON。查问题时先按编号定位,再比较参数校验、查询或预测实际走到了哪一步。

同一请求编号从客户端进入中间件,经过路由,再同时进入响应头与日志,标出计时前后的动作

笔记正文与全部题解 · Ctrl / ⌘ K