You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Pyinstrument分析FastAPI接口遇同步代码栈异常的通用方案问询

解决Pyinstrument 4.x + FastAPI 同步代码栈追踪仅显示[await]的通用方案

问题根源

Pyinstrument 4.x官方异步中间件是为纯异步场景设计的。当FastAPI接口执行同步阻塞代码时,这些代码会被自动放到线程池运行,而默认中间件的Profiler未追踪线程池内的同步调用上下文,导致栈追踪仅显示[await]占位符。

通用解决方案:改进全局中间件

无需逐个接口嵌入Profiler,只需修改中间件逻辑,让Pyinstrument自动捕获线程池中的同步代码调用栈。核心是通过线程本地存储绑定请求生命周期的Profiler会话,覆盖从请求开始到结束的全执行流程。

修改后的中间件代码

from fastapi import Request, Response
from pyinstrument import Profiler
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
import threading

class PyinstrumentMiddleware(BaseHTTPMiddleware):
    def __init__(self, app, **profiler_kwargs):
        super().__init__(app)
        self.profiler_kwargs = profiler_kwargs
        self.local = threading.local()  # 线程本地存储,关联请求与Profiler会话

    async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
        # 初始化Profiler,启用异步模式同时兼容同步代码追踪
        profiler = Profiler(async_mode="enabled", **self.profiler_kwargs)
        session = profiler.start()
        self.local.session = session

        try:
            response = await call_next(request)
        finally:
            profiler.stop()
            # 输出追踪结果到控制台,可改为写入文件/返回响应头
            print(profiler.output_text(unicode=True, color=True))
        
        return response

关键改进点

  • 用threading.local()存储当前请求的Profiler会话,确保线程池中的同步代码能关联到对应的追踪上下文
  • 显式调用start()/stop()覆盖请求全生命周期,包括线程池内的同步执行阶段
  • 保留async_mode="enabled",同时兼容异步、同步接口的追踪需求

注册中间件到FastAPI

from fastapi import FastAPI

app = FastAPI()
app.add_middleware(PyinstrumentMiddleware)

# 测试同步接口(原问题场景)
@app.get("/sync-block")
def sync_block():
    import time
    time.sleep(1)  # 同步阻塞操作,现在会显示完整调用栈
    return {"status": "done"}

# 测试异步接口(原有正常场景)
@app.get("/async-wait")
async def async_wait():
    import asyncio
    await asyncio.sleep(1)
    return {"status": "done"}

额外优化:按需开启追踪

如果不需要每次请求都追踪,可通过请求参数或环境变量控制开关:

async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
    # 仅当请求携带?profile=1参数时开启追踪
    if request.query_params.get("profile") != "1":
        return await call_next(request)
    
    # 后续Profiler逻辑...

内容的提问来源于stack exchange,提问作者Erik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 05:32:00