Uvicorn默认仅终端输出、无时间戳、不写文件,必须通过--log-config加载自定义JSON/YAML配置实现双输出;推荐JSON格式,需设"version":1和"disable_existing_loggers":false,并为"uvicorn"和"uvicorn.access"同时配置"default"与"file"两个handler。

Uvicorn 默认只输出到终端,不写文件、不带时间戳、无法按模块分级控制——必须显式配置日志才能满足生产需求。
用 --log-config 加载自定义 JSON/YAML 配置最稳妥
Uvicorn 原生支持 json、yaml、ini 三种日志配置格式,推荐用 JSON:它结构清晰、易读、兼容性好,且不会因缩进或注释引发解析失败。
- 创建
uvicorn_config.json放在项目根目录 - 必须包含
"version": 1,否则 uvicorn 会忽略整个配置 -
"disable_existing_loggers": false是关键:设为true会禁用所有已有 logger(包括你自定义的"fastapi"),导致日志消失 - 要同时输出到终端和文件,需定义两个 handler:
"default"(终端)和"file"(文件),再分别挂到"uvicorn"和"uvicorn.access"logger 上
示例最小可用配置(仅添加文件输出 + 时间戳):
{
"version": 1,
"disable_existing_loggers": false,
"formatters": {
"default": {
"()": "uvicorn.logging.DefaultFormatter",
"fmt": "%(asctime)s | %(levelprefix)s %(message)s",
"use_colors": null
},
"access": {
"()": "uvicorn.logging.AccessFormatter",
"fmt": "%(asctime)s | %(levelprefix)s %(client_addr)s - \"%(request_line)s\" %(status_code)s"
}
},
"handlers": {
"default": {
"formatter": "default",
"class": "logging.StreamHandler",
"stream": "ext://sys.stderr"
},
"file": {
"formatter": "default",
"class": "logging.FileHandler",
"filename": "uvicorn.log",
"encoding": "utf-8"
}
},
"loggers": {
"uvicorn": {
"handlers": ["default", "file"],
"level": "INFO"
},
"uvicorn.access": {
"handlers": ["default", "file"],
"level": "INFO"
}
}
}启动时指定:uvicorn main:app --log-config uvicorn_config.json
用 Python 脚本动态修改 LOGGING_CONFIG 更灵活
当需要运行时计算日志路径、注入环境变量、或复用已有 logging 配置时,Python 脚本比静态文件更可控。
- 必须从
uvicorn.config导入LOGGING_CONFIG,不能自己构造字典 - 直接修改
LOGGING_CONFIG["handlers"]和LOGGING_CONFIG["loggers"]即可,无需调用dictConfig - 注意:
"file"handler 的"filename"必须是绝对路径,否则可能写入到工作目录意外位置(比如 systemd 启动时工作目录是/) - 如果同时启用
--reload,脚本会被重复执行多次,但LOGGING_CONFIG是模块级变量,只加载一次,所以安全
示例 logging_config.py:
Uvicorn 0.52.1 是 Python ASGI Web 服务器,提供异步应用服务及 WebSocket 支持,常用于 FastAPI、Starlette 等应用的开发和部署。本下载为官方 PyPI 通用 wheel 包,可使用 pip 安装,并由包管理器处理所需依赖。
import logging
from uvicorn.config import LOGGING_CONFIG
<p>LOG_FILE = "/var/log/myapp/uvicorn.log" # 推荐绝对路径</p><p>LOGGING_CONFIG["formatters"]["default"]["fmt"] = "%(asctime)s | %(levelprefix)s %(message)s"
LOGGING_CONFIG["handlers"]["file"] = {
"class": "logging.FileHandler",
"formatter": "default",
"filename": LOG_FILE,
"encoding": "utf-8"
}
LOGGING_CONFIG["loggers"]["uvicorn"]["handlers"].append("file")
LOGGING_CONFIG["loggers"]["uvicorn.access"]["handlers"].append("file")启动:uvicorn main:app --log-config logging_config.py
--log-level 只控制级别,不改变输出目标或格式
这个参数常被误用。它只会统一设置所有 logger 的最低输出级别,但不会新增 handler、不会改 formatter、也不会让日志写入文件。
-
uvicorn main:app --log-level warning:只让uvicorn、uvicorn.error、uvicorn.access全部降到WARNING级别,但依然只打屏 - 若已通过
--log-config配置了文件输出,--log-level仍只影响“是否输出”,不影响“输出到哪”或“长什么样” - 想单独调低访问日志级别?必须在配置文件里写:
"uvicorn.access": {"level": "WARNING"}
常见错误:以为加了 --log-level info 就能看见时间戳——其实时间戳由 formatter 控制,跟 level 无关。
避免日志重复和无限递归写入
这是生产环境最常踩的坑:日志既在终端刷屏,又疯狂往文件里写,甚至一条请求触发多条相同日志。
- 根本原因:多个 handler 挂到了同一个 logger,且没关掉
propagate;或自定义 logger 与 uvicorn logger 冲突 -
"uvicorn.access"默认"propagate": false,但如果你手动加了 handler 到"root"或""logger,就会重复 - 最稳妥做法:确保
"uvicorn"和"uvicorn.access"的"propagate"都为false,且只把 handler 挂给它们自身 - 调试时发现日志翻倍?用
logging.getLogger("uvicorn").handlers打印当前 handlers 列表,确认没重复添加
真正生效的日志配置,往往不是“写得最多”的那个,而是“最先被 uvicorn 加载且没被覆盖”的那个。路径、权限、JSON 语法、handler 名冲突——任何一个细节出错,Uvicorn 都会静默退回到默认配置。

















