如何配置 FastAPI 日志记录,以便它在本地和生产中与 Uvicorn 一起工作?

2025-04-10 09:46:00
admin
原创
19
摘要:问题描述:我有以下 FastAPI 应用程序:from fastapi import FastAPI import logging import uvicorn app = FastAPI(title="api") LOG = logging.getLogger(__name__) L...

问题描述:

我有以下 FastAPI 应用程序:

from fastapi import FastAPI
import logging
import uvicorn

app = FastAPI(title="api")

LOG = logging.getLogger(__name__)
LOG.info("API is starting up")
LOG.info(uvicorn.Config.asgi_version)

@app.get("/")
async def get_index():
    LOG.info("GET /")
    return {"Hello": "Api"}

本地应用程序运行如下:

uvicorn api:app --reload
INFO:     Will watch for changes in these directories: ['/Users/user/code/backend/api']
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [44258] using StatReload
INFO:     Started server process [44260]
INFO:     Waiting for application startup.
INFO:     Application startup complete.

它没有记录任何启动消息。稍后向 API 发送 HTTP 请求时:

INFO:     127.0.0.1:50538 - "POST /api/v1/endpoint HTTP/1.1" 200 OK

在函数主体中,也有LOG.info("example")未记录的内容。有没有办法让 FastAPI 日志记录与 Uvicorn 配合使用,并在生产中使用(独立于 Uvicorn 等执行环境)?


解决方案 1:

  1. 设置uvicorn记录器


直接来自文档:

日志记录

  • --log-config <path>- 日志配置文件。选项dictConfig()格式:.json.yaml。任何其他格式都将使用 进行处理fileConfig()。设置formatters.default.use_colorsformatters.access.use_colors值以覆盖自动检测的行为。

    • 如果您希望使用 YAML 文件作为日志配置,则需要将 PyYAML 作为项目依赖项包含在内,或者使用[standard]可选附加功能安装 uvicorn。

  • --log-level <str>- 设置日志级别。选项:' critical ', ' error ', ' warning ', ' info ', ' debug ', ' trace '。默认值:' info '。

  • --no-access-log- 仅禁用访问日志,而不更改日志级别。

  • --use-colors/ --no-use-colors- 启用/禁用日志记录的彩色格式,如果未设置,将自动检测。如果--log-config使用 CLI 选项,则忽略此选项。

关于日志级别

如上所示,该--log-level标志指定记录器将处理的最低严重性日志消息,其中trace是最低严重性/级别,critical是最高严重性/级别。例如,如果级别设置为info,记录器将仅处理infowarningerror消息critical ,而debugtrace消息将被忽略。如果级别设置为trace,记录器将处理所有消息。

uvicorn从命令行运行

使用命令行界面运行 uvicorn 时,您可以按如下方式设置日志级别。另外,如果只想禁用“访问日志”消息而不更改日志级别,可以使用--no-access-log标志(--access-log默认情况下启用该标志)。此外,为了更改hostand/or port,可以使用--host 0.0.0.0and/or来执行此操作--port 8000。在下面的示例中,main指的是应用程序的文件名(例如main.py)—有关更多详细信息,请参阅此答案。

uvicorn main:app --log-level trace

uvicorn以编程方式运行

uvicorn要在 Python 程序中运行,您可以使用以下命令。可以使用log_level中的标志设置日志记录级别uvicorn.run(),如下所示。同样,如果只想禁用“访问日志”消息,他们可以通过将access_log参数设置为False(即access_log=False)来实现。要更改hostand/or port,可以使用,例如,host='0.0.0.0'and/or port=8000

uvicorn.run(app, log_level="trace")
  1. 使用uvicorn记录器记录自定义消息


Uvicorn 的实现见此处,它在内部使用各种记录器,例如uvicorn、和。然而,名字所代表的记录器似乎是 Uvicorn 最常用的记录器,如此处和此处所示,例如,用于记录各种警告、错误以及其他类型的信息。另一方面,记录器似乎用于记录 HTTP 请求;例如,请参阅此处。有关记录器,也请参阅此处。uvicorn.access`uvicorn.erroruvicorn.asgiuvicorn.erroruvicorn.accessuvicorn.asgi`

因此,人们可以使用uvicorn.error记录器来记录他们自己的自定义消息/错误,如下面的示例所示,以及消息(同样,可以使用中的标志uvicorn更改日志记录级别)记录器,如此处的实现所示,将默认向其祖先记录器发送一条消息,即。log_level`uvicorn.run()uvicorn.errorpropagate`uvicorn

附带说明一下,父记录器(在本例中为uvicorn)通常会将消息传递给最高级别的记录器,即root记录器,但uvicorn记录器似乎已将propagate标志设置为False(请参阅相关实现),这意味着其消息不会传播到root记录器(这完全没问题 - 如官方 Python 文档中所述,强烈建议您不要将日志记录到root库中的记录器)。为了完整起见,应该注意,为了禁用此行为(不是必须这样做)uvicorn.error,在下面的示例中,也可以将该记录器propagate的属性设置为False,例如logger.propagate = False

主程序

from fastapi import FastAPI
import uvicorn
import logging


app = FastAPI(title='api')
logger = logging.getLogger('uvicorn.error')

@app.get('/')
async def main():
    logger.info('GET /') # or logger.debug(), logger.error(), etc.
    return 'success'
    
    
if __name__ == '__main__':
    uvicorn.run(app, log_level="trace") 
  1. 使用自定义格式的uvicorn记录器来记录自定义消息


这种方法演示了如何自定义uvicorn记录器,以及如何使用它们来记录 uvicorn定义消息。

要为记录器定义自定义格式uvicorn,可以使用log_config中的属性uvicorn.run()传递日志配置字典(即dictConfig()),如下面的示例所示,包括各种架构详细信息,例如formattershandlers和。然后,您可以按照上一节中的示例在 中loggers定义记录器,并在整个应用程序中使用它。uvicorn.error`main.py`

对于下面示例中的文件处理程序,RotatingFileHandler使用,其中:

您可以使用maxBytesbackupCount值来允许文件
在预定大小时滚动。当大小即将超出时,文件将关闭并静默打开一个新文件进行输出。只要当前日志文件的
maxBytes长度接近,就会发生滚动;但如果maxBytesbackupCount为零,则永远不会发生滚动,因此您通常希望将其设置backupCount为至少 1,并且有一个非零值maxBytes(默认情况下,文件将无限增长)。

backupCount不为零时,系统将通过在文件名后附加扩展名 '.1'、'.2' 等来保存旧日志文件。例如,如果 为backupCount5 且基本文件名为app.log,则将获得app.logapp.log.1app.log.2,最多app.log.5。写入的文件始终为app.log。当此文件已满时,它将被关闭并重命名为app.log.1,并且如果文件app.log.1app.log.2等存在,则它们分别重命名为app.log.2app.log.3

主程序

from fastapi import FastAPI
import uvicorn
import logging
import settings


app = FastAPI(title='api')
logger = logging.getLogger('uvicorn.error')


@app.get('/')
async def main():
    logger.info('GET /') # or logger.debug(), logger.error(), etc.
    return 'success'
    
    
if __name__ == '__main__':
    uvicorn.run(app, log_config=settings.LOGGING_CONFIG)

设置.py

LOGGING_CONFIG = { 
    'version': 1,
    'disable_existing_loggers': True,
    'formatters': { 
        'standard': { 
            'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
        },
        'custom_formatter': { 
            'format': "%(asctime)s [%(processName)s: %(process)d] [%(threadName)s: %(thread)d] [%(levelname)s] %(name)s: %(message)s"
            
        },
    },
    'handlers': { 
        'default': { 
            'formatter': 'standard',
            'class': 'logging.StreamHandler',
            'stream': 'ext://sys.stdout',  # Default is stderr
        },
        'stream_handler': { 
            'formatter': 'custom_formatter',
            'class': 'logging.StreamHandler',
            'stream': 'ext://sys.stdout',  # Default is stderr
        },
        'file_handler': { 
            'formatter': 'custom_formatter',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': 'app.log',
            'maxBytes': 1024 * 1024 * 1, # = 1MB
            'backupCount': 3,
        },
    },
    'loggers': { 
        'uvicorn': {
            'handlers': ['default', 'file_handler'],
            'level': 'TRACE',
            'propagate': False
        },
        'uvicorn.access': {
            'handlers': ['stream_handler', 'file_handler'],
            'level': 'TRACE',
            'propagate': False
        },
        'uvicorn.error': { 
            'handlers': ['stream_handler', 'file_handler'],
            'level': 'TRACE',
            'propagate': False
        },
        'uvicorn.asgi': {
            'handlers': ['stream_handler', 'file_handler'],
            'level': 'TRACE',
            'propagate': False
        },

    },
}

自定义 JSON 格式化程序(简单)

如果愿意的话,可以使用简单的 JSON 格式显示和/或保存日志消息,例如:

设置.py

LOGGING_CONFIG = { 
    'version': 1,
    'disable_existing_loggers': True,
    'formatters': {
        'standard': ...,  # same as above or customize that as well
        'custom_formatter': { 
            'format': "{'time':'%(asctime)s', 'process_name': '%(processName)s', 'process_id': '%(process)s', 'thread_name': '%(threadName)s', 'thread_id': '%(thread)s','level': '%(levelname)s', 'logger_name': '%(name)s', 'message': '%(message)s'}"            
        },
    },
    ...  # the rest is the same as in the original settings.py above
}

自定义 JSON 格式化程序(优雅)

或者,一个更优雅的版本,如本答案中先前所示,如下所示。请参阅该答案和本答案以获取更多详细信息,以及用于日志记录RequestResponse信息的相关中间件和方法,这些方法将extra在应用程序中记录消息时进入参数,例如:

logger.info("some msg", extra={'extra_info': get_extra_info(request, response)})

如果您不需要那种信息,请随意不要使用该extra参数,以及从下面的函数extra_info中删除该部分get_log()

设置.py

import logging, json


class CustomJSONFormatter(logging.Formatter):
    def __init__(self, fmt):
        logging.Formatter.__init__(self, fmt)

    def format(self, record):
        logging.Formatter.format(self, record)
        return json.dumps(get_log(record), indent=2)


def get_log(record):
    d = {
        "time": record.asctime,
        "process_name": record.processName,
        "process_id": record.process,
        "thread_name": record.threadName,
        "thread_id": record.thread,
        "level": record.levelname,
        "logger_name": record.name,
        "pathname": record.pathname,
        "line": record.lineno,
        "message": record.message,
    }

    if hasattr(record, "extra_info"):
        d["req"] = record.extra_info["req"]
        d["res"] = record.extra_info["res"]

    return d


LOGGING_CONFIG = { 
    'version': 1,
    'disable_existing_loggers': True,
    'formatters': {
        'standard': ...,  # same as above or customize that as well
        'custom_formatter': { 
            '()':  lambda: CustomJSONFormatter(fmt='%(asctime)s')           
        },
    },
    ...  # the rest is the same as in the original settings.py above
}

输出示例:

{
  "time": "2024-10-27 11:05:00,300",
  "process_name": "MainProcess",
  "process_id": 4102,
  "thread_name": "AnyIO worker thread",
  "thread_id": 1147,
  "level": "INFO",
  "logger_name": "uvicorn.error",
  "pathname": "C:\\...",
  "line": 33,
  "message": "GET /",
  "req": {
    "url": "/",
    "headers": {
      "host": "localhost:8000",
      "user-agent": "Mozilla...",
      "accept": "text/html,application/xhtml+xml,..."
    },
    "method": "GET",
    "http_version": "1.1",
    "original_url": "/",
    "query": {}
  },
  "res": {
    "status_code": 200,
    "status": "OK"
  }
}
  1. 使用与uvicorn记录器不同的自定义 Python 记录器


如果有人希望拥有一个单独的自定义 Python 记录器而不是自定义现有的记录器,如前所示,uvicorn他们需要添加StreamHandlerand/orFileHandler并设置所需的级别,即DEBUG,,,等 - Python 模块提供的最低级别是,默认级别为(如果有兴趣添加自定义日志级别,请参阅此帖子)。INFO`WARNINGloggingDEBUG`WARNING

您可以使用dictConfig()(如前所示)或直接使用 的logging模块函数和类来执行此操作。以下示例基于此答案,它演示了如何自定义 JSON 中日志消息的格式(因此,如果您正在寻找上一节中介绍的类似格式,请参阅该答案),以及此答案,它展示了如何在后台记录请求和响应主体

更多详细信息和示例也可以在 Python 的官方文档页面中找到。您可能还想查看可用于格式化日志记录的所有可用LogRecord属性。

设置log_level="trace"uvicorn.run()会将记录器的级别设置uvicorntrace,如前所述 — 以防万一。此外,uvicorn如果愿意,仍然可以自定义记录器,使用LOGGING_CONFIG上一节提供的字典并将其传递给设置,即uvicorn.run(..., log_config=settings.LOGGING_CONFIG)。这样,就可以uvicorn以优雅的格式获取日志,并将其保存到磁盘上的文件中。

工作示例

from fastapi import FastAPI
import logging
import uvicorn
import sys

app = FastAPI()

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s [%(processName)s: %(process)d] [%(threadName)s: %(thread)d] [%(levelname)s] %(name)s: %(message)s")

stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setFormatter(formatter)
file_handler = logging.FileHandler("info.log")
file_handler.setFormatter(formatter)

logger.addHandler(stream_handler)
logger.addHandler(file_handler)

logger.info('API is starting up')


@app.get('/')
async def main():
    logger.info('GET /')
    return 'ok'


if __name__ == '__main__':
    uvicorn.run(app, log_level="trace")  # or `log_config=settings.LOGGING_CONFIG`
  1. 最后说明


在上述每种情况下,人们可能希望logger在启动时在处理程序内部初始化lifespan,然后将其添加到app.staterequest.state,以便也可以在应用程序主文件之外访问它;例如,从用于APIRouter创建端点的子模块(位于包内)访问,这通常是在构建更大的应用程序routers时的情况。为此,请查看此答案。

解决方案 2:

通过查看 uvicorn 在最新版本中如何设置其记录器,我得出了以下结论:

logger = logging.getLogger('uvicorn.error')

@app.get('/')
async def main():
    logger.debug('this is a debug message')
    return 'ok'

日志级别由 uvicorn 命令行选项控制--log-level debug并产生:

INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
DEBUG:    this is a debug message
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   2482  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1533  
  PLM(产品生命周期管理)项目对于企业优化产品研发流程、提升产品质量以及增强市场竞争力具有至关重要的意义。然而,在项目推进过程中,范围蔓延是一个常见且棘手的问题,它可能导致项目进度延迟、成本超支以及质量下降等一系列不良后果。因此,有效避免PLM项目范围蔓延成为项目成功的关键因素之一。以下将详细阐述三大管控策略,助力企业...
plm系统   0  
  PLM(产品生命周期管理)项目管理在企业产品研发与管理过程中扮演着至关重要的角色。随着市场竞争的加剧和产品复杂度的提升,PLM项目面临着诸多风险。准确量化风险优先级并采取有效措施应对,是确保项目成功的关键。五维评估矩阵作为一种有效的风险评估工具,能帮助项目管理者全面、系统地评估风险,为决策提供有力支持。五维评估矩阵概述...
免费plm软件   0  
  引言PLM(产品生命周期管理)开发流程对于企业产品的全生命周期管控至关重要。它涵盖了从产品概念设计到退役的各个阶段,直接影响着产品质量、开发周期以及企业的市场竞争力。在当今快速发展的科技环境下,客户对产品质量的要求日益提高,市场竞争也愈发激烈,这就使得优化PLM开发流程成为企业的必然选择。缺陷管理工具和六西格玛方法作为...
plm产品全生命周期管理   0  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用