当构建 Web 应用时,处理错误是至关重要的一部分。Flask 提供了灵活的错误处理机制,允许开发者自定义错误页面、捕获异常并提供友好的用户体验。在这篇博客中,我们将探讨 Flask 中的错误处理机制以及如何有效地处理各种错误情况。
Flask 使用装饰器 @app.errorhandler(code)
来处理特定的 HTTP 错误码。
from flask import Flask, render_template
app = Flask(__name__)
@app.errorhandler(404)
def handle_page_not_found(e):
return render_template('404.html'), 404
@app.errorhandler(werkzeug.exceptions.BadRequest)
def handle_bad_request(e):
return render_template('400.html'), 400
Werkzeug 无法识别非标准 HTTP 错误,需要自定义一个 HTTPException 子类。
class InsufficientStorage(werkzeug.exceptions.HTTPException):
code = 507
description = 'Not enough storage space.'
app.register_error_handler(InsufficientStorage, handle_507)
raise InsufficientStorage()
可以将异常基类注册到异常处理器,例如 HTTPException 基类或者甚至 Exception 基类。
from flask import json
from werkzeug.exceptions import HTTPException
@app.errorhandler(HTTPException)
def handle_exception(e):
response = e.get_response()
response.data = json.dumps({
"code": e.code,
"message": e.description,
})
response.content_type = "application/json"
return response
@app.errorhandler(Exception)
def handle_exception(e):
return {
"code": 500,
"message": str(e)
}
异常处理器遵循异常类的继承层次。如果同时基于 HTTPException 和 Exception 注册了异常处理器, Exception 处理器不会处理 HTTPException 子类,因为 HTTPException 更有针对性。
在使用蓝图进行应用模块化中,大多数错误处理器会按预期工作,但是处理 404 和 405 错误的处理器比较特殊。这是因为蓝图不“拥有”一定的 URL 空间,所以应用实例无法知道非法 URL 访问应当调用哪个蓝图的错误处理器。如果需要基于 URL 前缀配置不同的处理策略, 那么可以使用 rquest 代理对象在应用层面进行配置。
from flask import jsonify, render_template
@app.errorhandler(404)
def page_not_found(e):
if request.path.startswith('/blog/'):
return render_template("blog/404.html"), 404
else:
return render_template("404.html"), 404
@app.errorhandler(405)
def method_not_allowed(e):
if request.path.startswith('/api/'):
return jsonify(message="Method Not Allowed"), 405
else:
return render_template("405.html"), 405
当使用 Flask-WTF 处理表单时,可能会遇到表单验证错误。Flask 提供了一个特殊的装饰器 @app.errorhandler(wtforms.ValidationError)
来处理这类错误:
from flask import Flask, render_template
from wtforms import ValidationError
app = Flask(__name__)
@app.errorhandler(ValidationError)
def handle_validation_error(e):
return render_template('validation_error.html', error=str(e)), 400
这个例子中的 400
是 Bad Request 错误的 HTTP 状态码。
日志记录是一种重要的开发和运维工具,有助于解决以下问题:
故障排查: 当应用出现问题时,日志可以提供详细的信息,有助于定位和修复错误。
性能监控: 通过记录请求处理时间、数据库查询次数等信息,可以评估应用的性能状况。
安全性: 记录登录尝试、异常请求等信息有助于检测潜在的安全问题。
业务分析: 记录用户行为、访问量等信息,有助于分析用户行为和应用的使用情况。
在 Flask 中,日志记录是基于 Python 内置的 logging
模块实现的。Flask 应用对象(Flask
类的实例)上有一个 logger
属性,通过该属性可以获得日志记录器。通常,我们使用该日志记录器来记录应用的活动和错误。
常用的日志级别包括 'DEBUG'
、'INFO'
、'WARNING'
、'ERROR'
和 'CRITICAL'
。通过设置日志级别,可以控制记录哪些级别以上的日志。
@app.route('/')
def index():
app.logger.info('访问首页')
return 'Hello, World!'
from logging.handlers import RotatingFileHandler
# 创建文件处理程序
file_handler = RotatingFileHandler('app.log', maxBytes=1024 * 1024, backupCount=10)
file_handler.setLevel(logging.INFO)
# 添加处理程序到日志记录器
app.logger.addHandler(file_handler)
这样配置的日志处理程序将在应用根目录下创建一个 app.log
文件,并保留最多 10 个旧的日志文件备份。maxBytes
参数设置单个日志文件的最大大小。
from logging.config import dictConfig
dictConfig({
'version': 1,
'formatters': {
'default': {
'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s'
}
},
'handlers': {
'file_handler': {
'class': 'logging.handlers.RotatingFileHandler',
'filename': 'app.log',
'maxBytes': 1024 * 1024,
'backupCount': 10,
'formatter': 'default'
},
'console_handler': {
'class': 'logging.StreamHandler',
'formatter': 'default'
}
},
'root': {
'level': 'DEBUG',
'handlers': ['file_handler', 'console_handler']
}
})
日志格式说明
日志格式是通过格式化字符串来定义的,这个字符串中包含特定的占位符,这些占位符在实际日志记录时会被替换为相应的值。以下是一些常见的日志格式占位符和它们的含义:
%(asctime)s
: 记录的时间
YYYY-MM-DD HH:MM:SS,mmm
(毫秒部分)2023-03-15 12:45:30,123
%(levelname)s
: 日志级别
DEBUG
, INFO
, WARNING
, ERROR
, CRITICAL
%(message)s
: 日志消息文本
%(name)s
: 记录器的名称
%(module)s
: 所在模块的名称
%(funcName)s
: 所在函数的名称
%(lineno)d
: 所在代码的行号
%(pathname)s
: 所在文件的完整路径
%(filename)s
: 所在文件的文件名
%(process)d
: 进程ID
%(thread)d
: 线程ID
如果要看到web请求中如 IP 地址等信息,可以继承 logging.Formatter 来注入自己的内容,以显示在日志消息中。
from flask import has_request_context, request
from flask.logging import default_handler
class RequestFormatter(logging.Formatter):
def format(self, record):
if has_request_context():
record.url = request.url
record.remote_addr = request.remote_addr
else:
record.url = None
record.remote_addr = None
return super().format(record)
formatter = RequestFormatter(
'[%(asctime)s] %(remote_addr)s requested %(url)s\n'
'%(levelname)s in %(module)s: %(message)s'
)
default_handler.setFormatter(formatter)
mail_handler.setFormatter(formatter)
通过灵活的错误处理机制,Flask 提供了强大的工具来处理各种错误情况。有效的错误处理不仅能够提高用户体验,还有助于快速定位和修复问题,使得 Web 应用更加稳定和可靠。在开发过程中,合理利用 Flask 的错误处理机制将为你的应用增色不少。