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

如何为FastAPI的Uvicorn访问日志添加响应时间?

在FastAPI的Uvicorn日志中添加响应时间的解决方案

你遇到的问题很常见——Uvicorn的默认AccessFormatter确实没有内置响应时间的日志字段,因为Uvicorn本身的访问日志机制没有记录这个指标的逻辑。不过我们有两种可靠的方法来实现需求:

方案一:自定义FastAPI中间件,手动记录带响应时间的访问日志

这种方法最直观,不需要修改Uvicorn的日志配置结构,而是通过中间件拦截请求和响应,计算耗时后自行输出符合格式的日志。

步骤1:编写计时中间件

import time
import logging
from fastapi import FastAPI, Request
from starlette.middleware.base import BaseHTTPMiddleware

# 复用你现有的uvicorn.access日志记录器
access_logger = logging.getLogger("uvicorn.access")

app = FastAPI()

class TimingMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        # 记录请求开始时间
        start_time = time.time()
        # 处理请求并获取响应
        response = await call_next(request)
        # 计算响应耗时(秒,保留3位小数)
        process_time = time.time() - start_time
        
        # 按照你现有日志格式输出内容
        access_logger.info(
            '%s::%s %s - "%s" %.3f %d',
            time.strftime("%Y-%m-%dT%H:%M:%S%z"),
            "INFO",  # 对应原配置中的levelprefix
            request.client.host if request.client else "-",
            f"{request.method} {request.url.path} HTTP/{request.scope.get('http_version')}",
            process_time,
            response.status_code
        )
        return response

# 注册中间件到FastAPI应用
app.add_middleware(TimingMiddleware)

步骤2:避免重复日志

因为我们的中间件已经会输出访问日志,需要调整原log_config中的uvicorn.access日志级别,防止Uvicorn默认的访问日志重复输出:

"loggers": { 
    "uvicorn":{"handlers": ["default"], "level": "INFO", "propagate": False}, 
    "uvicorn.access":{"handlers": ["access"], "level": "WARNING", "propagate": False},  # 提升级别到WARNING,关闭默认日志
}

方案二:自定义AccessFormatter,注入响应时间字段

如果你想保留Uvicorn原有的日志触发逻辑,可以通过继承AccessFormatter并配合日志过滤器来注入响应时间。

步骤1:自定义日志格式化器

from uvicorn.logging import AccessFormatter

class CustomAccessFormatter(AccessFormatter):
    def format(self, record):
        # 给日志记录添加响应时间字段,默认值为"-"
        if hasattr(record, 'process_time'):
            # 格式化为保留3位小数的字符串
            record.process_time = f"{record.process_time:.3f}"
        else:
            record.process_time = "-"
        return super().format(record)

步骤2:修改日志配置中的access格式化器

把原log_config里的access formatter替换为我们自定义的类:

"access": { 
    "()": "app.main.CustomAccessFormatter",  # 替换成你的Formatter所在的模块路径
    "datefmt": "%Y-%m-%dT%H:%M:%S%z", 
    "fmt": '%(asctime)s::%(levelprefix)s %(client_addr)s - "%(request_line)s" %(process_time)s %(status_code)s', 
    "use_colors": False, 
}, 

步骤3:添加日志过滤器注入响应时间

通过中间件计算耗时,并通过日志过滤器把时间注入到Uvicorn的日志记录中:

import time
import logging
from fastapi import FastAPI, Request
from starlette.middleware.base import BaseHTTPMiddleware

app = FastAPI()

class TimingFilter(logging.Filter):
    def __init__(self, process_time):
        self.process_time = process_time
        super().__init__()
    
    def filter(self, record):
        record.process_time = self.process_time
        return True

class TimingMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        start_time = time.time()
        response = await call_next(request)
        process_time = time.time() - start_time
        
        # 获取uvicorn.access日志器并添加过滤器
        access_logger = logging.getLogger("uvicorn.access")
        # 添加一次性过滤器,避免重复注入
        access_logger.addFilter(TimingFilter(process_time))
        return response

app.add_middleware(TimingMiddleware)

推荐方案

我更推荐方案一,它逻辑简单、易于调试,不需要对Uvicorn的内部日志机制做太多修改,而且你可以完全控制日志的输出格式和内容。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 11:57:45