Flask 应用如何配置日志:app.logger、dictConfig 与 JSON 结构化实战

Flask 日志实战中文指南:app.logger 默认 WARNING 级别与工作原理、dictConfig 集中配置、FileHandler/RotatingFileHandler 文件输出、请求上下文与 request_id 关联、异常堆栈记录、JSON 结构化日志,以及如何用观测云 DataKit 采集 Flask 日志并配置告警与长期存储。

最佳实践
Flask 应用如何配置日志:app.logger、dictConfig 与 JSON 结构化实战技术指南封面

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 输出到控制台或文件后,检查采集端的时间、级别和请求标识;关联 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 日志并验证字段

主机上的日志可通过 DataKit 日志采集器接入观测云:配置 logfiles 指向实际文件,并设置来源和服务标签。容器部署则选择标准输出采集。

JSON 输出不等于字段已被解析。可启用文档中的 json_as_fields,或用 Pipeline处理时间、级别和业务字段。先发送正常与异常请求,检查状态码、耗时和堆栈;HTTP 状态码应保留为独立字段,便于区分响应码与日志级别。

需要在链路详情查看日志时,先完成应用链路采集,再配置日志记录当前 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 带时区的格式。采集端解析时间时,要用带时区的样本核对结果。

生产环境应该用 app.run() 启动吗?

不应该。app.run() 只用于开发。生产用 Gunicorn/uWSGI 等 WSGI 服务器启动,并让应用日志输出到 stdout/stderr 或文件,再由观测云 DataKit 采集。Gunicorn 自身的访问日志也可以用 --access-logfile - 输出到控制台一并采集。

多个 Flask 应用的日志如何区分?

在 DataKit 采集配置中为每个应用设置不同的 source 和 service;或在日志 JSON 中增加 app 字段。采集后按 service 查询,确认不同应用的记录没有混淆。

系列阅读


获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

在线开通,按量计费,真正的云服务!

立即开始

选择观测云版本

代码托管平台