前言

flask默认会在控制台输出非结构化的请求日志,如果要输出json格式的日志,并且要把请求日志写到单独的文件中,可以通过先禁用默认请求日志,然后在钩子函数中自行记录请求的方式来实现。

定义日志器

下面代码定义了两个JSON日志格式化器,JsonFormatter 的日志格式是给普通代码内使用的,会记录调用函数、调用文件等信息,AccessLogFormatter的日志格式用于记录请求日志,记录请求路径、响应状态码、响应时间等信息。

FlaskLogger通过继承logging.Logger来实现一些自定义功能,比如指定格式化器、创建日志目录等。

class JsonFormatter(logging.Formatter):
def format(self, record: logging.LogRecord):
log_record = {
"@timestamp": self.formatTime(record, "%Y-%m-%dT%H:%M:%S%z"), # format iso 8601
"level": record.levelname,
"name": record.name,
"file": record.filename,
"lineno": record.lineno,
"func": record.funcName,
"message": record.getMessage(),
}
return json.dumps(log_record) class AccessLogFormatter(logging.Formatter):
def format(self, record: logging.LogRecord):
log_record = {
"@timestamp": self.formatTime(record, "%Y-%m-%dT%H:%M:%S%z"), # format iso 8601
"remote_addr": getattr(record, "remote_addr", ""),
"scheme": getattr(record, "scheme", ""),
"method": getattr(record, "method", ""),
"host": getattr(record, "host", ""),
"path": getattr(record, "path", ""),
"status": getattr(record, "status", ""),
"response_length": getattr(record, "response_length", ""),
"response_time": getattr(record, "response_time", 0),
}
return json.dumps(log_record) class FlaskLogger(logging.Logger):
"""自定义日志类, 设置请求日志和普通日志两个不同的日志器 Args:
name: str, 日志器名称, 默认为 __name__
level: int, 日志级别, 默认为 DEBUG
logfile: str, 日志文件名, 默认为 app.log
logdir: str, 日志文件目录, 默认为当前目录
access_log: bool, 是否用于记录访问日志, 默认为 False
console: bool, 是否输出到控制台, 默认为 True
json_log: bool, 是否使用json格式的日志, 默认为 True
"""
def __init__(
self,
name: str = __name__,
level: int = logging.DEBUG,
logfile: str = "app.log",
logdir: str = "",
access_log: bool = False,
console: bool = True,
json_log: bool = True,
):
super().__init__(name, level)
self.logfile = logfile
self.logdir = logdir
self.access_log = access_log
self.console = console
self.json_log = json_log
self.setup_logpath()
self.setup_handler() def setup_logpath(self):
"""设置日志文件路径, 如果创建日志器时未指定日志目录, 则使用当前目录"""
if not self.logdir:
return p = Path(self.logdir)
if not p.exists():
try:
p.mkdir(parents=True, exist_ok=True)
except Exception as e:
print(f"Failed to create log directory: {e}")
sys.exit(1) self.logfile = p / self.logfile def setup_handler(self):
if self.json_log:
formatter = self.set_json_formatter()
else:
formatter = self.set_plain_formatter()
handler_file = self.set_handler_file(formatter)
handler_stdout = self.set_handler_stdout(formatter)
self.addHandler(handler_file)
if self.console:
self.addHandler(handler_stdout) def set_plain_formatter(self):
fmt = "%(asctime)s | %(levelname)s | %(name)s | %(filename)s:%(lineno)d | %(funcName)s | %(message)s"
datefmt = "%Y-%m-%dT%H:%M:%S%z"
return logging.Formatter(fmt, datefmt=datefmt) def set_json_formatter(self):
"""设置json格式的日志"""
if self.access_log:
return AccessLogFormatter()
return JsonFormatter() def set_handler_stdout(self, formatter: logging.Formatter):
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(formatter)
return handler def set_handler_file(self, formatter: logging.Formatter):
handler = TimedRotatingFileHandler(
filename=self.logfile,
when="midnight",
interval=1,
backupCount=7,
encoding="utf-8",
)
handler.setFormatter(formatter)
return handler

实例化示例

access_logger = FlaskLogger("access", logdir="logs", access_log=True, logfile="access.log")
logger = FlaskLogger(logdir="logs")

钩子函数内记录请求日志

借助flask内置的钩子函数和全局对象,可以记录到每个请求的信息。

from flask import g, request, Response
import time @app.before_request
def start_timer():
# 通过全局对象 g 来记录请求开始时间
g.start_time = time.time() @app.after_request
def log_request(response: Response):
"""记录每次请求的日志"""
response_length = (
response.content_length if response.content_length is not None else "-"
)
log_message = {
"remote_addr": request.remote_addr,
"method": request.method,
"scheme": request.scheme,
"host": request.host,
"path": request.path,
"status": response.status_code,
"response_length": response_length,
"response_time": round(time.time() - g.start_time, 4),
}
access_logger.info("", extra=log_message)
return response

基本使用示例

实例化Flask对象,禁用默认日志,定义路由等

from flask import Flask
import traceback app = Flask(__name__) @app.errorhandler(Exception)
def handle_exception(e):
"""全局拦截异常"""
logger.error(f"An exception occurred, {traceback.format_exc()}", exc_info=e)
return "An error occurred", 500 @app.get("/")
def hello():
# 普通请求
logger.info("Hello World")
return "hello world" @app.get("/error")
def raise_error():
# 模拟错误请求,观察是否全局捕获
raise Exception("Error") @app.get("/slow")
def slow():
# 模拟慢请求,观察请求日志的响应时间
time.sleep(5)
return "slow" if __name__ == "__main__":
# 禁用默认的日志器
default_logger = logging.getLogger("werkzeug")
default_logger.disabled = True app.run(host="127.0.0.1", port=5000)

访问测试,logs目录会生成access.logapp.log文件,控制台输出示例

{"@timestamp": "2025-04-26T00:26:20+0800", "level": "INFO", "name": "__main__", "file": "app.py", "lineno": 162, "func": "hello", "message": "Hello World"}
{"@timestamp": "2025-04-26T00:26:20+0800", "remote_addr": "127.0.0.1", "scheme": "http", "method": "GET", "host": "127.0.0.1:5000", "path": "/", "status": 200, "response_length": 11, "response_time": 0.0003}
{"@timestamp": "2025-04-26T00:26:20+0800", "level": "INFO", "name": "__main__", "file": "app.py", "lineno": 162, "func": "hello", "message": "Hello World"}
{"@timestamp": "2025-04-26T00:26:20+0800", "remote_addr": "127.0.0.1", "scheme": "http", "method": "GET", "host": "127.0.0.1:5000", "path": "/", "status": 200, "response_length": 11, "response_time": 0.0003}
{"@timestamp": "2025-04-26T00:29:47+0800", "remote_addr": "127.0.0.1", "scheme": "http", "method": "GET", "host": "127.0.0.1:5000", "path": "/slow", "status": 200, "response_length": 4, "response_time": 5.0002}
{"@timestamp": "2025-04-26T00:31:02+0800", "level": "ERROR", "name": "__main__", "file": "app.py", "lineno": 129, "func": "handle_exception", "message": "An exception occurred, Traceback (most recent call last):\n File \"/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/lib/python3.11/site-packages/flask/app.py\", line 917, in full_dispatch_request\n rv = self.dispatch_request()\n ^^^^^^^^^^^^^^^^^^^^^^^\n File \"/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/lib/python3.11/site-packages/flask/app.py\", line 902, in dispatch_request\n return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) # type: ignore[no-any-return]\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/demo1/app.py\", line 168, in raise_error\n raise Exception(\"Error\")\nException: Error\n"}
{"@timestamp": "2025-04-26T00:31:02+0800", "remote_addr": "127.0.0.1", "scheme": "http", "method": "GET", "host": "127.0.0.1:5000", "path": "/error", "status": 500, "response_length": 17, "response_time": 0.0011}

完整使用示例

from flask import Flask, request, g, Response
import logging
import sys
from logging.handlers import TimedRotatingFileHandler
import json
from pathlib import Path
import traceback
import time app = Flask(__name__) class JsonFormatter(logging.Formatter):
def format(self, record: logging.LogRecord):
log_record = {
"@timestamp": self.formatTime(record, "%Y-%m-%dT%H:%M:%S%z"), # format iso 8601
"level": record.levelname,
"name": record.name,
"file": record.filename,
"lineno": record.lineno,
"func": record.funcName,
"message": record.getMessage(),
}
return json.dumps(log_record) class AccessLogFormatter(logging.Formatter):
def format(self, record: logging.LogRecord):
log_record = {
"@timestamp": self.formatTime(record, "%Y-%m-%dT%H:%M:%S%z"), # format iso 8601
"remote_addr": getattr(record, "remote_addr", ""),
"scheme": getattr(record, "scheme", ""),
"method": getattr(record, "method", ""),
"host": getattr(record, "host", ""),
"path": getattr(record, "path", ""),
"status": getattr(record, "status", ""),
"response_length": getattr(record, "response_length", ""),
"response_time": getattr(record, "response_time", 0),
}
return json.dumps(log_record) class FlaskLogger(logging.Logger):
"""自定义日志类, 设置请求日志和普通日志两个不同的日志器 Args:
name: str, 日志器名称, 默认为 __name__
level: int, 日志级别, 默认为 DEBUG
logfile: str, 日志文件名, 默认为 app.log
logdir: str, 日志文件目录, 默认为当前目录
access_log: bool, 是否用于记录访问日志, 默认为 False
console: bool, 是否输出到控制台, 默认为 True
json_log: bool, 是否使用json格式的日志, 默认为 True
"""
def __init__(
self,
name: str = __name__,
level: int = logging.DEBUG,
logfile: str = "app.log",
logdir: str = "",
access_log: bool = False,
console: bool = True,
json_log: bool = True,
):
super().__init__(name, level)
self.logfile = logfile
self.logdir = logdir
self.access_log = access_log
self.console = console
self.json_log = json_log
self.setup_logpath()
self.setup_handler() def setup_logpath(self):
"""设置日志文件路径, 如果创建日志器时未指定日志目录, 则使用当前目录"""
if not self.logdir:
return p = Path(self.logdir)
if not p.exists():
try:
p.mkdir(parents=True, exist_ok=True)
except Exception as e:
print(f"Failed to create log directory: {e}")
sys.exit(1) self.logfile = p / self.logfile def setup_handler(self):
if self.json_log:
formatter = self.set_json_formatter()
else:
formatter = self.set_plain_formatter()
handler_file = self.set_handler_file(formatter)
handler_stdout = self.set_handler_stdout(formatter)
self.addHandler(handler_file)
if self.console:
self.addHandler(handler_stdout) def set_plain_formatter(self):
fmt = "%(asctime)s | %(levelname)s | %(name)s | %(filename)s:%(lineno)d | %(funcName)s | %(message)s"
datefmt = "%Y-%m-%dT%H:%M:%S%z"
return logging.Formatter(fmt, datefmt=datefmt) def set_json_formatter(self):
"""设置json格式的日志"""
if self.access_log:
return AccessLogFormatter()
return JsonFormatter() def set_handler_stdout(self, formatter: logging.Formatter):
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(formatter)
return handler def set_handler_file(self, formatter: logging.Formatter):
handler = TimedRotatingFileHandler(
filename=self.logfile,
when="midnight",
interval=1,
backupCount=7,
encoding="utf-8",
)
handler.setFormatter(formatter)
return handler access_logger = FlaskLogger("access", logdir="logs", access_log=True, logfile="access.log")
logger = FlaskLogger(logdir="logs") @app.errorhandler(Exception)
def handle_exception(e):
"""全局拦截异常"""
logger.error(f"An exception occurred, {traceback.format_exc()}", exc_info=e)
return "An error occurred", 500 @app.before_request
def start_timer():
# 通过全局对象 g 来记录请求开始时间
g.start_time = time.time() @app.after_request
def log_request(response: Response):
"""记录每次请求的日志"""
response_length = (
response.content_length if response.content_length is not None else "-"
)
log_message = {
"remote_addr": request.remote_addr,
"method": request.method,
"scheme": request.scheme,
"host": request.host,
"path": request.path,
"status": response.status_code,
"response_length": response_length,
"response_time": round(time.time() - g.start_time, 4),
}
access_logger.info("", extra=log_message)
return response @app.get("/")
def hello():
# 普通请求
logger.info("Hello World")
return "hello world" @app.get("/error")
def raise_error():
# 模拟错误请求,观察是否全局捕获
raise Exception("Error") @app.get("/slow")
def slow():
# 模拟慢请求,观察请求日志的响应时间
time.sleep(5)
return "slow" if __name__ == "__main__":
# 禁用默认的日志器
default_logger = logging.getLogger("werkzeug")
default_logger.disabled = True app.run(host="127.0.0.1", port=5000)

参考

[flask]自定义请求日志的更多相关文章

  1. Spring Boot 自定义注解,AOP 切面统一打印出入参请求日志

    其实,小哈在之前就出过一篇关于如何使用 AOP 切面统一打印请求日志的文章,那为什么还要再出一篇呢?没东西写了? 哈哈,当然不是!原因是当时的实现方案还是存在缺陷的,原因如下: 不够灵活,由于是以所有 ...

  2. SpringBoot学习笔记(七):SpringBoot使用AOP统一处理请求日志、SpringBoot定时任务@Scheduled、SpringBoot异步调用Async、自定义参数

    SpringBoot使用AOP统一处理请求日志 这里就提到了我们Spring当中的AOP,也就是面向切面编程,今天我们使用AOP去对我们的所有请求进行一个统一处理.首先在pom.xml中引入我们需要的 ...

  3. python 全栈开发,Day139(websocket原理,flask之请求上下文)

    昨日内容回顾 flask和django对比 flask和django本质是一样的,都是web框架. 但是django自带了一些组件,flask虽然自带的组件比较少,但是它有很多的第三方插件. 那么在什 ...

  4. Spring Boot 2.0 教程 | AOP 切面统一打印请求日志

    欢迎关注微信公众号: 小哈学Java 文章首发于个人网站 https://www.exception.site/springboot/spring-boot-aop-web-request 本节中,您 ...

  5. Flask的错误日志处理和|ORM操作

    flask有个很人性化的处理就是 你的错误的输出是可以通过错误日志来自定义  ,让你输入的错误不再是“大黄页”, 通过 errorhandler()来装饰函数之后你的所有的输入错误的函数你都会进入这个 ...

  6. Spring Boot中使用log4j实现http请求日志入mongodb

    之前在<使用AOP统一处理Web请求日志>一文中介绍了如何使用AOP统一记录web请求日志.基本思路是通过aop去切web层的controller实现,获取每个http的内容并通过log4 ...

  7. 如何从Serilog请求日志记录中排除健康检查终结点

    这是在ASP.NET Core 3.X中使用Serilog.AspNetCore系列文章的第四篇文章:. 第1部分-使用Serilog RequestLogging减少日志详细程度 第2部分-使用Se ...

  8. 用SignalR实现实时查看WebAPI请求日志

    实现的原理比较直接,定义一个MessageHandler记录WebAPI的请求记录,然后将这些请求日志推送到客户端,客户端就是一个查看日志的页面,实时将请求日志展示在页面中. 这个例子的目的是演示如何 ...

  9. (Unity)Unity自定义Debug日志文件,利用VS生成Dll文件并使用Dotfuscated进展混淆,避免被反编译

    Unity自定义Debug日志文件,利用VS生成Dll文件并使用Dotfuscated进行混淆,避免被反编译. 1.打开VS,博主所用版本是Visual Studio 2013. 2.新建一个VC项目 ...

  10. nginx记录响应与POST请求日志

    生产环境中的某些api出现故障,但是问题无法重现,但是又很想解决掉问题以及我们新项目上线,需要跟踪请求与响应的信息,可以预先找到一些bug,减少大面积的损失. 安装nginx与ngx_lua 响应日志 ...

随机推荐

  1. Linux安装Jenkins指南

    Linux安装Jenkins指南 Jenkins,作为一款开源的自动化服务器,广泛用于持续集成和持续部署(CI/CD)流程中.它提供了强大的插件生态系统,使得集成各种开发工具.版本控制系统和构建工具变 ...

  2. 2025年值得推荐的 8 款 WPF UI 控件库

    前言 今天大姚给大家分享 8 款开源.美观.功能强大.简单易用的WPF UI控件库,希望可以帮助到有需要的同学. WPF介绍 WPF 是一个强大的桌面应用程序框架,用于构建具有丰富用户界面的 Wind ...

  3. 烟草行业如何用低代码+ BI 实现数字化转型?

    在数字经济的大潮中,烟草行业正迎来重大的发展契机.国家层面的政策引导和战略规划为行业的数字化转型提供了明确的方向.<数字中国>的愿景逐步变为现实,国家信息化发展战略的深入推进,为烟草行业的 ...

  4. C#/.NET/.NET Core技术前沿周刊 | 第 26 期(2025年2.10-2.16)

    前言 C#/.NET/.NET Core技术前沿周刊,你的每周技术指南针!记录.追踪C#/.NET/.NET Core领域.生态的每周最新.最实用.最有价值的技术文章.社区动态.优质项目和学习资源等. ...

  5. Jenkins - [01] 概述

    "持续集成并不能消除Bug,而是让它们非常容易发现和改正." -- Martin Fowler 一.概述 1.1.持续集成(CI)   持续集成(Continuous integr ...

  6. 借Processing语言及IDE做DOS批处理的事务( 批量修改文件夹或文件的名字 )

    一直想用Processing语言做一些批处理的事务,因为其自带的IDE功能紧凑易用,极度轻量,又加上Java语言的生态极具友好,处理一些windows相关操作完全可行,简单快捷. 这次就是用它做[批量 ...

  7. 如何用Forest方便快捷地在SpringBoot项目中对接DeepSeek

    ​ 一. 环境要求 JDK 8 / 17 SpringBoot 2.x / 3.x Forest 1.6.4+ Fastjson2 依赖配置 除了 SpringBoot 和 Lombok 等基础框架之 ...

  8. [tldr]windows使用scoop安装make工具辅助程序编译

    make是一个好用的GNU工具,用来辅助我们进行自动化的程序编译,只需要一个Makefile文件,即可实现一行指令自动编译 scoop是windows的一个包管理工具 安装 scoop bucket ...

  9. Manus,没有秘密「注解版」

    近来Manus走红,「争论」不断,我也在前文<Manus爆火,是硬核还是营销?>中阐述过自Manus发布后,行业讨论以及开源复刻的信息,以及我们如何结合蚂蚁图计算(TuGraph)技术,实 ...

  10. 5个步骤完成 Vue3 开发调试工具安装教程

    Vue3 开发调试工具安装教程 5个步骤 第一步:点击浏览器右上角,更多工具 – 扩展程序 第二步:点击右上角 – 开发者模式 开关 第三步:点击 "添加已解压的扩展程序" 第四步 ...