如何配置 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__)
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:
设置
uvicorn
记录器
直接来自文档:
日志记录
--log-config <path>
- 日志配置文件。选项:dictConfig()
格式:.json、.yaml。任何其他格式都将使用 进行处理fileConfig()
。设置formatters.default.use_colors
和formatters.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
,记录器将仅处理info
、warning
和error
消息critical
,而debug
和trace
消息将被忽略。如果级别设置为trace
,记录器将处理所有消息。
uvicorn
从命令行运行
使用命令行界面运行 uvicorn 时,您可以按如下方式设置日志级别。另外,如果只想禁用“访问日志”消息而不更改日志级别,可以使用--no-access-log
标志(--access-log
默认情况下启用该标志)。此外,为了更改host
and/or port
,可以使用--host 0.0.0.0
and/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
)来实现。要更改host
and/or port
,可以使用,例如,host='0.0.0.0'
and/or port=8000
。
uvicorn.run(app, log_level="trace")
使用
uvicorn
记录器记录自定义消息
Uvicorn 的实现见此处,它在内部使用各种记录器,例如uvicorn
、和。然而,名字所代表的记录器似乎是 Uvicorn 最常用的记录器,如此处和此处所示,例如,用于记录各种警告、错误以及其他类型的信息。另一方面,记录器似乎用于记录 HTTP 请求;例如,请参阅此处。有关记录器,也请参阅此处。uvicorn.access
`uvicorn.erroruvicorn.asgi
uvicorn.erroruvicorn.access
uvicorn.asgi`
因此,人们可以使用uvicorn.error
记录器来记录他们自己的自定义消息/错误,如下面的示例所示,以及消息(同样,可以使用中的标志uvicorn
更改日志记录级别)记录器,如此处的实现所示,将默认向其祖先记录器发送一条消息,即。log_level
`uvicorn.run()uvicorn.error
propagate`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")
使用自定义格式的
uvicorn
记录器来记录自定义消息
这种方法演示了如何自定义uvicorn
记录器,以及如何使用它们来记录自 uvicorn
定义消息。
要为记录器定义自定义格式uvicorn
,可以使用log_config
中的属性uvicorn.run()
传递日志配置字典(即dictConfig()
),如下面的示例所示,包括各种架构详细信息,例如formatters
、handlers
和。然后,您可以按照上一节中的示例在 中loggers
定义记录器,并在整个应用程序中使用它。uvicorn.error
`main.py`
对于下面示例中的文件处理程序,RotatingFileHandler
使用,其中:
您可以使用
maxBytes
和backupCount
值来允许文件
在预定大小时滚动。当大小即将超出时,文件将关闭并静默打开一个新文件进行输出。只要当前日志文件的maxBytes
长度接近,就会发生滚动;但如果maxBytes
或backupCount
为零,则永远不会发生滚动,因此您通常希望将其设置backupCount
为至少 1,并且有一个非零值maxBytes
(默认情况下,文件将无限增长)。当
backupCount
不为零时,系统将通过在文件名后附加扩展名 '.1'、'.2' 等来保存旧日志文件。例如,如果 为backupCount
5 且基本文件名为app.log
,则将获得app.log
、app.log.1
、app.log.2
,最多app.log.5
。写入的文件始终为app.log
。当此文件已满时,它将被关闭并重命名为app.log.1
,并且如果文件app.log.1
、app.log.2
等存在,则它们分别重命名为app.log.2
等app.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 格式化程序(优雅)
或者,一个更优雅的版本,如本答案中先前所示,如下所示。请参阅该答案和本答案以获取更多详细信息,以及用于日志记录Request
和Response
信息的相关中间件和方法,这些方法将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"
}
}
使用与
uvicorn
记录器不同的自定义 Python 记录器
如果有人希望拥有一个单独的自定义 Python 记录器而不是自定义现有的记录器,如前所示,uvicorn
他们需要添加StreamHandler
and/orFileHandler
并设置所需的级别,即DEBUG
,,,等 - Python 模块提供的最低级别是,默认级别为(如果有兴趣添加自定义日志级别,请参阅此帖子)。INFO
`WARNINGlogging
DEBUG`WARNING
您可以使用dictConfig()
(如前所示)或直接使用 的logging
模块函数和类来执行此操作。以下示例基于此答案,它演示了如何自定义 JSON 中日志消息的格式(因此,如果您正在寻找上一节中介绍的类似格式,请参阅该答案),以及此答案,它展示了如何在后台记录请求和响应主体。
更多详细信息和示例也可以在 Python 的官方文档页面中找到。您可能还想查看可用于格式化日志记录的所有可用LogRecord
属性。
设置log_level="trace"
为uvicorn.run()
会将记录器的级别设置uvicorn
为trace
,如前所述 — 以防万一。此外,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`
最后说明
在上述每种情况下,人们可能希望logger
在启动时在处理程序内部初始化lifespan
,然后将其添加到app.state
或request.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
扫码咨询,免费领取项目管理大礼包!