Skip to content

FastAPI与Uvicorn日志配置

问题描述

许多开发者在本地使用uvicorn api:app --reload命令运行FastAPI应用时,发现通过Python标准logging模块创建的日志(如启动信息、路由处理中的日志)无法输出。控制台仅显示Uvicorn的内置日志,而应用自身的LOG.info("API is starting up")和端点中的LOG.info("GET /")等信息完全缺失。

问题表现为:

  1. 应用启动时自定义日志不显示
  2. 请求处理过程中的日志信息不可见
  3. 需要统一的日志配置方案,兼容本地开发和生产环境
python
# 典型的问题代码示例
from fastapi import FastAPI
import logging
import uvicorn

app = FastAPI(title="api")
LOG = logging.getLogger(__name__)  # 此日志器不会输出

@app.get("/")
async def get_index():
    LOG.info("GET /")  # 此日志不会显示
    return {"Hello": "Api"}

解决方案

方法1: 快速启用日志(推荐)

直接使用Uvicorn的内置日志器uvicorn.error,无需额外配置:

python
from fastapi import FastAPI
import uvicorn
import logging

app = FastAPI(title='api')
logger = logging.getLogger('uvicorn.error')  # 使用Uvicorn日志器

@app.get('/')
async def main():
    logger.info('GET /')  # 正常输出日志
    return 'ok'

启动命令设置日志级别:

bash
uvicorn main:app --log-level debug

方法2: 配置Uvicorn日志级别

通过命令行参数控制日志显示:

bash
# 显示所有日志(包含自定义内容)
uvicorn main:app --log-level trace

# 关闭访问日志(保留错误和应用日志)
uvicorn main:app --no-access-log

或在Python代码中配置:

python
if __name__ == '__main__':
    uvicorn.run(app, log_level="trace", access_log=False)

方法3: 完整日志系统配置

创建专业日志系统(包含控制台输出、文件轮转和JSON格式):

推荐在生产环境中使用

settings.py——日志配置文件:

python
# settings.py
LOGGING_CONFIG = {
    'version': 1,
    'disable_existing_loggers': True,
    'formatters': {
        'standard': {
            'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
        },
    },
    'handlers': {
        'console': {
            'formatter': 'standard',
            'class': 'logging.StreamHandler',
            'stream': 'ext://sys.stdout',
        },
        'file': {
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': 'app.log',
            'maxBytes': 1024 * 1024 * 10,  # 10MB
            'backupCount': 3,
            'formatter': 'standard'
        },
    },
    'loggers': {
        'uvicorn': {'handlers': ['console', 'file'], 'level': 'INFO'},
        'uvicorn.error': {'handlers': ['console', 'file'], 'level': 'INFO'},
    }
}

主程序main.py

python
from fastapi import FastAPI
import uvicorn
import settings

app = FastAPI()
logger = logging.getLogger("uvicorn.error")

@app.get("/")
async def read_root():
    logger.info("处理根路径请求")
    return {"message": "OK"}

if __name__ == "__main__":
    uvicorn.run(app, log_config=settings.LOGGING_CONFIG)

方法4: 自定义独立日志器

当需要与Uvicorn日志完全分离时:

python
from fastapi import FastAPI
import logging
import sys

app = FastAPI()

# 创建独立日志器
logger = logging.getLogger("app")
logger.setLevel(logging.DEBUG)

# 配置控制台输出
console_handler = logging.StreamHandler(sys.stdout)
console_format = logging.Formatter(
    "%(asctime)s [%(levelname)s] %(name)s: %(message)s"
)
console_handler.setFormatter(console_format)

# 配置文件输出
file_handler = logging.FileHandler("app.log")
file_format = logging.Formatter(
    "%(asctime)s [%(process)d] [%(levelname)s] %(message)s"
)
file_handler.setFormatter(file_format)

logger.addHandler(console_handler)
logger.addHandler(file_handler)

@app.get("/")
async def main():
    logger.info("处理根路径请求")
    return {"status": "OK"}

JSON日志格式化(高级)

适合ELK等日志分析系统:

python
# settings.py
import json
import logging

class JSONFormatter(logging.Formatter):
    def format(self, record):
        log_entry = {
            "timestamp": self.formatTime(record),
            "level": record.levelname,
            "logger": record.name,
            "message": record.getMessage(),
            "process": record.process,
            "thread": record.thread
        }
        return json.dumps(log_entry)

LOGGING_CONFIG = {
    # ... handlers配置同上
    'formatters': {
        'json': {
            '()': JSONFormatter
        }
    },
    'handlers': {
        'console': {
            'formatter': 'json',
            'class': 'logging.StreamHandler'
        }
    }
}

常见问题解决方案

问题:日志在容器中不显示

在Docker等容器环境中运行时:

  1. 确保设置环境变量PYTHONUNBUFFERED=1
  2. 启动命令添加--proxy-headers--forwarded-allow-ips='*'
  3. 使用--log-config指定配置文件路径

问题:双重重载导致日志重复

关闭Uvicorn自动重载,使用外部监视工具如watchfiles

bash
uvicorn main:app --reload --reload-include *.py --reload-exclude venv

问题:获取请求信息

在日志中包含请求元数据:

python
async def endpoint(request: Request):
    extra_info = {
        "path": request.url.path,
        "method": request.method,
        "client": request.client.host
    }
    logger.info("处理请求", extra={"context": extra_info})

最佳实践总结

  1. 开发环境:直接使用uvicorn.error日志器 + --log-level debug
  2. 生产环境
    • 使用RotatingFileHandler防止日志膨胀
    • 配置log_config统一管理日志格式
    • JSON格式便于日志分析
  3. 注意事项
    • 避免使用根日志器(root logger)
    • 对于长时间运行的服务,设置日志轮转
    • 敏感信息不要记录在日志中

重要提示

Uvicorn内部使用多日志器:

  • uvicorn.error:核心错误和应用日志
  • uvicorn.access:访问日志
  • uvicorn.asgi:ASGI协议日志

自定义日志应首选uvicorn.error日志器