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 结构化输出到控制台或文件,观测云 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 存入 gsession,并在每条日志中带上:

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.Filterrequest_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 应用接入观测云的路径非常直接:

  1. 应用侧只做两件事:输出 JSON 格式、写到控制台或固定日志文件(如 logs/flask-app.log)。
  2. DataKit 采集:主机部署在 DataKit 的 conf.d/log/logging.conf 中配置文件采集,logfiles 指向 Flask 日志文件路径,source 设为 flask-appservice 设为对应服务名;容器化部署则直接采集 stdout。JSON 日志自动解析为字段。
  3. Pipeline 标准化:用 Pipeline 提取响应状态码映射为标准 status 字段(5xx → error),并确保 time 字段正确,便于按状态聚合统计。
  4. 检索与分析:日志查看器中按 servicestatus 过滤,用图表模式统计状态码分布和错误趋势。
  5. 告警:监控 > 监控器 > 新建日志检测监控器,如"5xx 日志 5 分钟内超过 20 条"触发告警,告警策略绑定钉钉/企业微信/飞书通知对象。
  6. 链路关联:接入观测云 APM 后,trace_id 自动注入日志,错误日志一键跳转完整调用链。
  7. 成本治理:访问日志走低频索引,错误日志走标准索引并延长保留;历史数据通过数据转发归档到对象存储。

常见问题(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 采集配置中为每个应用设置不同的 sourceservice;或在日志 JSON 中增加 app 字段。观测云按 service 维度过滤和建索引即可清晰拆分。

系列阅读


获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

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

立即开始

选择观测云版本

代码托管平台