现场是这样的。FastAPI 里用 BackgroundTasks 起异步任务,日志里的 trace_id 全是空,中间件设置的 contextvar 在任务内读不到,同一请求的两段日志串不起来。
中间走过弯路,把「日志处理器在 import 时就把字段绑死了」当成主因,改完现象没变。
真正麻烦的地方在于,几个原因的现象几乎一样。后台任务在新的 task 里跑,中间件设置的 context 没有带过去;没有显式调用 copy_context 传递上下文;丢进线程池执行后彻底脱离 asyncio 上下文。
复现一次后台任务里的取值
python -c "import contextvars;v=contextvars.ContextVar('t');v.set('abc');print(v.get())"
然后用 copy_context 把值带进新任务
python -c "import contextvars,asyncio;v=contextvars.ContextVar('t');v.set('abc');ctx=contextvars.copy_context();print(ctx.run(v.get))"
随后在线程池入口手动重新绑定
python -c "import contextvars;v=contextvars.ContextVar('t');v.set('x');print(v.get())"
日志里同时打印 request_id 做兜底关联
python -c "print('log request_id and trace_id')"
别只看报错消失,验证要多做一步。后台任务的日志里 trace_id 与主请求一致,同一 trace 下能查到一起请求的 4 条日志,链路不再断开。
contextvars 不会自动穿过 task 和线程边界,谁创建新执行单元谁负责复制上下文,这活儿中间件替不了。如果同一现象隔几天又出现,说明根因没找到,别当偶发处理。
