Flask 应用如何配置日志:app.logger、dictConfig 与 JSON 结构化实战
Flask 日志实战中文指南:app.logger 默认 WARNING 级别与工作原理、dictConfig 集中配置、FileHandler/RotatingFileHandler 文件输出、请求上下文与 request_id 关联、异常堆栈记录、JSON 结构化日志,以及如何用观测云 DataKit 采集 Flask 日志并配置告警与长期存储。
Flask 是基于 Werkzeug 和 Jinja2 的轻量级 Python Web 框架,它通过内置的 app.logger 提供一个开箱即用的标准库 logging 实例。本文完整讲解 Flask 日志的默认行为、自定义格式、文件轮转、请求级日志、JSON 结构化输出,以及如何接入观测云实现统一存储、检索与告警。
核心要点速览
- Flask 的
app.logger默认级别是 WARNING:DEBUG 和 INFO 日志不会输出,必须显式setLevel或提前用 dictConfig 配置。 - dictConfig 要在应用创建之前调用:Flask 只在
app.logger第一次被访问且没有挂任何 handler 时才添加默认 handler,提前配置好 handler 后 Flask 就不会再动它。 - 记录请求与响应:在视图里通过
flask.request记录请求细节,用@app.after_request统一记录响应状态码和耗时。 - 生产环境落地:JSON 结构化输出到控制台或文件,观测云 DataKit 自动采集解析,日志与 APM 链路通过 trace_id 双向关联。
Flask 的默认日志机制是怎样的?
新建一个最小应用:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
app.logger.debug("这是调试信息")
app.logger.info("收到首页请求")
app.logger.warning("配置项缺失,使用默认值")
app.logger.error("数据库连接失败")
return "Hello"
if __name__ == "__main__":
app.run(debug=True)
启动后访问首页,控制台只会看到 WARNING 及以上的消息:
[2026-08-25 14:30:22,123] WARNING in app: 配置项缺失,使用默认值
[2026-08-25 14:30:22,124] ERROR in app: 数据库连接失败
原因是 app.logger 是标准库 logging 的 Logger,默认级别被设为 WARNING,并且 Flask 给它挂了一个输出到 stderr 的 StreamHandler。想让 INFO/DEBUG 也输出,最简单的方式:
import logging
app.logger.setLevel(logging.INFO)
如何用 dictConfig 集中配置 Flask 日志?
生产环境推荐在创建应用之前用 dictConfig 完整定义日志体系——这会让 Flask 跳过默认 handler 的添加,完全由你掌控:
from logging.config import dictConfig
from flask import Flask
dictConfig({
"version": 1,
"formatters": {
"default": {
"format": "[%(asctime)s] %(levelname)s %(name)s: %(message)s",
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"stream": "ext://sys.stdout",
"formatter": "default",
}
},
"root": {"level": "INFO", "handlers": ["console"]},
})
app = Flask(__name__)
@app.route("/")
def index():
app.logger.info("首页被访问")
return "Hello"
可用的格式占位符来自 LogRecord 属性,常用的有:%(asctime)s(时间)、%(levelname)s(级别)、%(name)s(logger 名)、%(module)s、%(funcName)s、%(lineno)d、%(message)s,异常时用 %(exc_info)s 输出堆栈。
如何把 Flask 日志写入文件并轮转?
把 handler 换成 RotatingFileHandler(按大小轮转)或 TimedRotatingFileHandler(按时间轮转):
"handlers": {
"file": {
"class": "logging.handlers.RotatingFileHandler",
"filename": "logs/flask-app.log",
"maxBytes": 20 * 1024 * 1024, # 20MB
"backupCount": 10,
"formatter": "default",
"encoding": "utf-8",
}
},
"root": {"level": "INFO", "handlers": ["file"]},
日志会写到 flask-app.log,超过 20MB 后轮转为 flask-app.log.1、.2……最多保留 10 个历史文件。
如何记录请求与响应日志?
在视图中记录请求上下文
Flask 的 request 对象提供了丰富的请求信息:
from flask import request
@app.route("/login", methods=["POST"])
def login():
app.logger.info(
"登录请求: ip=%s method=%s path=%s agent=%s",
request.remote_addr, request.method, request.path,
request.headers.get("User-Agent", "-"),
)
...
用 after_request 统一记录响应
与其在每个视图里重复记录,不如用 after_request 钩子统一输出请求访问日志:
import time
from flask import g, request
@app.before_request
def start_timer():
g.start_time = time.time()
@app.after_request
def log_response(response):
duration = round((time.time() - g.start_time) * 1000, 2)
app.logger.info(
"request: %s %s status=%s duration_ms=%s ip=%s",
request.method, request.path, response.status_code,
duration, request.remote_addr,
)
return response
每个请求自动产生一条带状态码和耗时的访问日志,是后续排查慢请求和 5xx 的基础数据。
用 session 实现请求 ID 关联
要在一次请求的多条日志之间建立关联,可以在请求开始时生成 request_id 存入 g 或 session,并在每条日志中带上:
import uuid
from flask import g
@app.before_request
def assign_request_id():
g.request_id = request.headers.get("X-Request-ID") or uuid.uuid4().hex[:12]
@app.route("/order")
def order():
app.logger.info("开始处理订单 rid=%s", g.request_id)
...
app.logger.info("订单处理完成 rid=%s", g.request_id)
更优雅的做法是自定义 logging.Filter 把 request_id 注入每条 LogRecord,然后把它加进格式串 %(request_id)s,业务代码里就不用每次手动传了。
如何记录异常堆栈?
在异常处理中务必带上堆栈信息:
@app.route("/pay")
def pay():
try:
process_payment()
except Exception:
app.logger.exception("支付处理失败") # 自动附加完整堆栈
return {"error": "internal"}, 500
logger.exception() 等价于 logger.error(..., exc_info=True),会把完整的 Traceback 写进日志——没有堆栈的错误日志几乎没有排查价值。
如何让 Flask 输出 JSON 结构化日志?
安装 python-json-logger:
pip install python-json-logger
然后在 dictConfig 中使用它的 JsonFormatter:
"formatters": {
"json": {
"()": "pythonjsonlogger.jsonlogger.JsonFormatter",
"format": "%(asctime)s %(levelname)s %(name)s %(message)s",
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "json",
}
},
输出效果:
{"asctime": "2026-08-25 14:35:10,552", "levelname": "INFO", "name": "app", "message": "request: GET /api/users status=200 duration_ms=12.4"}
观测云落地:Flask 日志采集与分析
Flask 应用接入观测云的路径非常直接:
- 应用侧只做两件事:输出 JSON 格式、写到控制台或固定日志文件(如
logs/flask-app.log)。 - DataKit 采集:主机部署在 DataKit 的
conf.d/log/logging.conf中配置文件采集,logfiles指向 Flask 日志文件路径,source设为flask-app、service设为对应服务名;容器化部署则直接采集 stdout。JSON 日志自动解析为字段。 - Pipeline 标准化:用 Pipeline 提取响应状态码映射为标准
status字段(5xx →error),并确保time字段正确,便于按状态聚合统计。 - 检索与分析:日志查看器中按
service、status过滤,用图表模式统计状态码分布和错误趋势。 - 告警:监控 > 监控器 > 新建日志检测监控器,如"5xx 日志 5 分钟内超过 20 条"触发告警,告警策略绑定钉钉/企业微信/飞书通知对象。
- 链路关联:接入观测云 APM 后,trace_id 自动注入日志,错误日志一键跳转完整调用链。
- 成本治理:访问日志走低频索引,错误日志走标准索引并延长保留;历史数据通过数据转发归档到对象存储。
常见问题(FAQ)
Flask 的 app.logger 为什么不输出 INFO 日志?
app.logger 默认级别是 WARNING。解决:调用 app.logger.setLevel(logging.INFO),或更推荐在应用创建前用 dictConfig 统一配置 root logger 的级别和 handler。
Flask 日志时区不对怎么办?
标准库 logging 默认使用本地时间。如需 UTC,可以给 Formatter 设置 converter = time.gmtime(自定义 Formatter 类),或输出 JSON 时用 ISO-8601 带时区的格式。观测云 Pipeline 也会以 time 字段为准重新解析时间。
生产环境应该用 app.run() 启动吗?
不应该。app.run() 只用于开发。生产用 Gunicorn/uWSGI 等 WSGI 服务器启动,并让应用日志输出到 stdout/stderr 或文件,再由观测云 DataKit 采集。Gunicorn 自身的访问日志也可以用 --access-logfile - 输出到控制台一并采集。
多个 Flask 应用的日志如何区分?
在 DataKit 采集配置中为每个应用设置不同的 source 和 service;或在日志 JSON 中增加 app 字段。观测云按 service 维度过滤和建索引即可清晰拆分。
系列阅读
- 上一篇:Structlog 结构化日志实战
- 下一篇:Python 日志最佳实践十条
- 相关阅读:Python logging 模块详解 | 什么是日志管理