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

如何在Uvicorn/FastAPI的访问日志中添加User-Agent等自定义变量

如何在Uvicorn访问日志中添加User-Agent等请求头信息

核心问题

Uvicorn默认的AccessFormatter拿到的日志记录(record对象)里没有请求头数据,因为uvicorn.access日志的默认生成逻辑未传递这些信息。要实现需求,有两种可行方案:


方案一:自定义中间件记录访问日志(推荐)

通过FastAPI/Starlette的中间件拦截请求与响应,直接获取请求头信息并构造自定义格式的访问日志,无需修改Uvicorn的日志组件逻辑。

示例代码:

import logging
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import Response
from datetime import datetime

access_logger = logging.getLogger("uvicorn.access")

class CustomAccessLogMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next) -> Response:
        response = await call_next(request)
        
        # 提取日志所需字段
        client_addr = request.client.host if request.client else "-"
        request_line = f"{request.method} {request.url.path} HTTP/{request.scope.get('http_version', '1.1')}"
        status_code = response.status_code
        user_agent = request.headers.get("user-agent", "-")
        x_forwarded_for = request.headers.get("x-forwarded-for", "-")
        
        # 构造并写入日志
        log_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        log_message = f'[{log_time}] - {client_addr} - "{request_line}" {status_code} "{user_agent}" "{x_forwarded_for}"'
        access_logger.info(log_message)
        
        return response

在FastAPI应用中注册中间件:

from fastapi import FastAPI

app = FastAPI()
app.add_middleware(CustomAccessLogMiddleware)

方案二:重写Uvicorn的AccessLogger与AccessFormatter

直接修改Uvicorn生成访问日志的逻辑,把请求头信息注入到日志记录对象中,让Formatter可以直接调用这些字段。

  1. 自定义AccessLogger,扩展日志字段:
from uvicorn.logging import AccessLogger

class CustomAccessLogger(AccessLogger):
    def log_message(self, message: str, scope, status_code: int, response_length: int) -> None:
        # 从请求scope中解析请求头
        headers = dict(scope.get("headers", []))
        user_agent = headers.get(b"user-agent", b"-").decode("utf-8")
        x_forwarded_for = headers.get(b"x-forwarded-for", b"-").decode("utf-8")
        
        # 将自定义字段传入日志记录的extra参数
        extra = {
            "client_addr": self.get_client_addr(scope),
            "request_line": message,
            "status_code": status_code,
            "user_agent": user_agent,
            "x_forwarded_for": x_forwarded_for
        }
        self.logger.info(message, extra=extra)
  1. 自定义AccessFormatter,支持新字段:
from uvicorn.logging import AccessFormatter

class CustomAccessFormatter(AccessFormatter):
    def formatMessage(self, record):
        # 确保自定义字段存在,避免报错
        record.user_agent = getattr(record, "user_agent", "-")
        record.x_forwarded_for = getattr(record, "x_forwarded_for", "-")
        return super().formatMessage(record)
  1. 更新日志配置,使用自定义组件:
import logging
from logging.config import dictConfig

log_config = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "default": {
            "()": "uvicorn.logging.DefaultFormatter",
            "fmt": "[%(asctime)s] - %(levelname)s - %(message)s",
            "datefmt": "%Y-%m-%d %H:%M:%S",
        },
        "access": {
            "()": "my_app.logger.CustomAccessFormatter",  # 替换为你的类实际路径
            "fmt": '[%(asctime)s] - %(client_addr)s - "%(request_line)s" %(status_code)s "%(user_agent)s" "%(x_forwarded_for)s"',
            "datefmt": "%Y-%m-%d %H:%M:%S",
            "use_colors": False,
        },
    },
    "handlers": {
        "default": {
            "formatter": "default",
            "class": "logging.StreamHandler",
            "stream": "ext://sys.stderr",
        },
        "access": {"class": "logging.StreamHandler", "formatter": "access", "stream": "ext://sys.stdout"},
    },
    "loggers": {
        "my_app": {"handlers": ["default"], "level": "DEBUG"},
        "uvicorn": {"handlers": ["default"], "level": "DEBUG", "propagate": True},
        "uvicorn.access": {"handlers": ["access"], "level": "INFO", "propagate": False},
        "uvicorn.error": {"level": "INFO", "propagate": False},
    },
}
dictConfig(log_config)
  1. 启动Uvicorn时指定自定义AccessLogger:
uvicorn my_app.app:app --host 0.0.0.0 --port 8000 --proxy-headers --access-logger-class my_app.logger.CustomAccessLogger

注意事项

  • 若启用--proxy-headers,客户端真实IP需从X-Forwarded-For请求头获取,而非request.client.host(后者是代理服务器IP)。
  • 方案一兼容性更强,无需依赖Uvicorn内部实现;方案二更贴近原生日志逻辑,但需注意Uvicorn版本更新可能带来的API变动。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 15:15:42