
在开发和维护 Flask API 时,请求日志是诊断问题、监控性能和理解用户行为的关键工具。然而,当 API 面临大量无效或恶意请求(如爬虫、扫描器等)时,日志文件可能会迅速膨胀,充满无用信息,严重影响日志的可读性和后续分析。为了解决这一问题,一种有效的策略是采用“白名单”机制,即只记录特定、已知的 API 端点的请求日志,而忽略其他所有请求。
本教程将指导您如何通过定制 Flask 底层的 WSGI 请求处理器,实现这一白名单日志过滤功能。
Flask 默认使用 werkzeug.serving.WSGIRequestHandler 来处理 HTTP 请求并记录日志。我们可以通过重写其 log_request 方法来插入自定义的日志过滤逻辑。
首先,我们需要一个函数来修改 WSGIRequestHandler.log_request 方法。这个函数将保存原始的 log_request 方法,然后用我们自己的逻辑替换它。
import re
from flask import Flask
from werkzeug import serving
# 假设您的Flask应用实例名为app
app = Flask(__name__)
def restrict_access_logs(app_instance):
"""
修改WSGIRequestHandler的log_request方法,实现基于白名单的日志过滤。
"""
# 保存原始的log_request方法
parent_log_request = serving.WSGIRequestHandler.log_request
# 动态获取所有已注册的端点名称
# 注意:这里获取的是端点名称(endpoint),而不是完整的URL路径
permitted_endpoints = [rule.endpoint for rule in app_instance.url_map.iter_rules()]
def log_request(self, *args, **kwargs):
"""
自定义的log_request方法,根据白名单判断是否记录日志。
"""
# 检查请求路径是否匹配白名单中的任一端点
# 假设所有API路径都以 /api/v1/ 开头,且端点名称与路径的最后一部分对应
# 例如,如果端点是 'hello',则匹配 '/api/v1/hello' 或 '/api/v1/hello/anything'
is_whitelisted = False
for endpoint in permitted_endpoints:
# 排除Flask自带的'static'端点,通常不需要记录其日志
if endpoint == 'static':
continue
# 构建正则表达式来匹配请求路径
# 这里以 '/api/v1/' 作为前缀示例,请根据您的实际API路径结构调整
# 确保正则表达式能正确匹配您的URL结构
pattern = rf"/api/v1/{re.escape(endpoint)}(/.*)?$"
if re.match(pattern, self.path):
is_whitelisted = True
break
# 如果请求路径在白名单中,则调用原始的log_request方法记录日志
if is_whitelisted:
parent_log_request(self, *args, **kwargs)
# 将WSGIRequestHandler的log_request方法替换为我们自定义的函数
serving.WSGIRequestHandler.log_request = log_request代码解析:
为了测试上述逻辑,我们创建一些示例 API 端点:
# 示例API路由
@app.route('/api/v1/hello', methods=['GET'])
def hello():
return "Hello, Flask!"
@app.route('/api/v1/getEvidencesByProductID/<int:product_id>', methods=['GET'])
def getEvidencesByProductID(product_id):
return f"Fetching evidences for product ID: {product_id}"
@app.route('/api/v1/testpoint', methods=['GET'])
def testpoint():
ep_list = [rule.endpoint for rule in app.url_map.iter_rules()]
ep_str = ", ".join(ep_list)
return f"Available Endpoints: {ep_str}"
@app.route('/api/v1/unlisted', methods=['GET'])
def unlisted_endpoint():
return "This endpoint should not be logged."一个非常重要的注意事项是 restrict_access_logs() 函数的调用时机。 如果在所有路由定义之前调用它,app.url_map.iter_rules() 将无法获取到完整的端点列表,导致白名单为空或不完整。
正确的做法是,在所有 app.route 装饰器定义完毕之后,再调用 restrict_access_logs() 函数。
if __name__ == '__main__':
# ... (上面定义的 app 实例和路由) ...
# 在所有路由定义完成后,调用日志限制函数
restrict_access_logs(app)
# 运行Flask应用
app.run(debug=True)通过将 restrict_access_logs(app) 放在所有 @app.route 装饰器之后,可以确保 app.url_map 包含了所有已注册的路由信息,从而动态生成的白名单是完整的。
import re
from flask import Flask
from werkzeug import serving
app = Flask(__name__)
def restrict_access_logs(app_instance):
"""
修改WSGIRequestHandler的log_request方法,实现基于白名单的日志过滤。
"""
parent_log_request = serving.WSGIRequestHandler.log_request
# 动态获取所有已注册的端点名称
permitted_endpoints = [rule.endpoint for rule in app_instance.url_map.iter_rules()]
print(f"Whitelisted Endpoints: {permitted_endpoints}") # 调试输出
def log_request(self, *args, **kwargs):
"""
自定义的log_request方法,根据白名单判断是否记录日志。
"""
is_whitelisted = False
for endpoint in permitted_endpoints:
if endpoint == 'static': # 排除Flask自带的'static'端点
continue
# 根据您的API路径结构调整正则表达式
# 例如,如果您的API前缀是/api/v1/
pattern = rf"/api/v1/{re.escape(endpoint)}(/.*)?$"
if re.match(pattern, self.path):
is_whitelisted = True
break
if is_whitelisted:
parent_log_request(self, *args, **kwargs)
serving.WSGIRequestHandler.log_request = log_request
# 示例API路由定义
@app.route('/api/v1/hello', methods=['GET'])
def hello():
return "Hello, Flask!"
@app.route('/api/v1/getEvidencesByProductID/<int:product_id>', methods=['GET'])
def getEvidencesByProductID(product_id):
return f"Fetching evidences for product ID: {product_id}"
@app.route('/api/v1/testpoint', methods=['GET'])
def testpoint():
ep_list = [rule.endpoint for rule in app.url_map.iter_rules()]
ep_str = ", ".join(ep_list)
return f"Available Endpoints: {ep_str}"
@app.route('/api/v1/unlisted', methods=['GET'])
def unlisted_endpoint():
return "This endpoint should not be logged."
@app.route('/no-api-prefix', methods=['GET'])
def no_api_prefix():
return "This endpoint has no /api/v1/ prefix."
if __name__ == '__main__':
# 确保在所有路由定义之后调用此函数
restrict_access_logs(app)
app.run(debug=True)
测试方法:
关于unlisted_endpoint的日志行为说明: 在上述示例中,@app.route('/api/v1/unlisted', methods=['GET']) 的端点名称默认为函数名 unlisted_endpoint。而我们的正则表达式 rf"/api/v1/{re.escape(endpoint)}(/.*)?$" 会尝试匹配 /api/v1/unlisted_endpoint。因此,访问 /api/v1/unlisted 将不会被匹配,从而不会记录日志。 如果希望 /api/v1/unlisted 被记录,您有两种选择:
通过重写 werkzeug.serving.WSGIRequestHandler.log_request 方法并结合动态端点白名单,我们可以有效地过滤 Flask API 的请求日志,从而提高日志的质量和可读性。关键在于理解 app.url_map 的工作原理,并确保在所有路由注册完成后再进行日志过滤器的初始化。同时,也要充分考虑生产环境的部署差异和性能影响,并在必要时探索更适合的替代方案。正确的日志管理策略对于任何生产级应用都是至关重要的。
以上就是Flask API 日志过滤:通过白名单机制优化请求日志管理的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号