如果你查看运行运用的终端会话,将看到stack trace(堆栈跟踪)。堆栈跟踪在调试缺点时非常有用,由于它们显示堆栈中调用的顺序,一贯到产生缺点的行:
(venv) $ flask run Serving Flask app \公众microblog\公众 Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)[2017-09-14 22:40:02,027] ERROR in app: Exception on /edit_profile [POST]Traceback (most recent call last): File \"大众/home/miguel/microblog/venv/lib/python3.6/site-packages/sqlalchemy/engine/base.py\公众, line 1182, in _execute_context context) File \公众/home/miguel/microblog/venv/lib/python3.6/site-packages/sqlalchemy/engine/default.py\"大众, line 470, in do_execute cursor.execute(statement, parameters)sqlite3.IntegrityError: UNIQUE constraint failed: user.username
堆栈跟踪指示了BUG在何处。本运用许可用户变动用户名,但却没有验证所选的新用户名与系统中已有的其他用户有没有冲突。这个缺点来自SQLAlchemy,它考试测验将新的用户名写入数据库,但数据库谢绝了它,由于username列是用unique=True定义的。
值得把稳的是,供应给用户的缺点页面并没有供应关于缺点的丰富信息,这是精确的做法。我绝对不肯望用户知道崩溃是由数据库缺点引起的,或者我正在利用什么数据库,或者是我的数据库中的一些表和字段名称。所有这些信息都该当对外保密。

但是也有一些不尽人意之处。缺点页面简陋不堪,与运用布局不匹配。终端上的日志不断刷新,导致主要的堆栈跟踪信息被淹没,但我却须要不断回顾它,以免有漏网之鱼。当然,我有一个BUG须要修复。我将办理所有的这些问题,但首先,让我们来谈谈Flask的调试模式。
02 调试模式你在上面看到的处理缺点的办法对在生产做事器上运行的系统非常有用。如果涌现缺点,用户将得到一个隐晦的缺点页面(只管我打算使这个缺点页面更友好),缺点的主要细节在做事器进程输出或存储到日志文件中。
但是当你正在开拓运用时,可以启用调试模式,它是Flask在浏览器上直接运行一个友好调试器的模式。要激活调试模式,请停滞运用程序,然后设置以下环境变量:
(venv) $ export FLASK_DEBUG=1
如果你利用Microsoft Windows,记得将export更换成set。
设置环境变量FLASK_DEBUG后,重启做事。比较之前,终端上的输出信息会有所变革:
(venv) microblog2 $ flask run Serving Flask app \"大众microblog\公众 Forcing debug mode on Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) Restarting with stat Debugger is active! Debugger PIN: 177-562-960
现在让运用再次崩溃,以在浏览器中查看交互式调试器:
该调试器许可你展开每个堆栈框来查看相应的源代码高下文。你也可以在任意堆栈框上打开Python提示符并实行任何有效的Python表达式,例如检讨变量的值。
永久不要在生产做事器上以调试模式运行Flask运用,这一点非常主要。调试器许可用户远程实行做事器中的代码,因此对付想要渗入运用或做事器的恶意用户来说,这可能是引狼入室。作为附加的安全方法,运行在浏览器中的调试器开始被锁定,并且在第一次利用时会哀求输入一个PIN码(你可以在flask run命令的输出中看到它)。
谈到调试模式的话题,我不得不提到的第二个主要的调试模式下的功能,便是重载器。这是一个非常有用的开拓功能,可以在源文件被修正时自动重启运用。如果在调试模式下运行flask run,则可以在开拓运用时,每当保存文件,运用都会重新启动以加载新的代码。
03 自定义缺点页面Flask为运用供应了一个机制来自定义缺点页面,这样用户就不必看到大略而呆板的默认页面。作为例子,让我们为HTTP的404缺点和500缺点(两个最常见的缺点页面)设置自定义缺点页面。为其他缺点设置页面的办法与之相同。
利用@errorhandler装饰器来声明一个自定义的缺点处理器。我将把我的缺点处理程序放在一个新的app/errors.py模块中。
from flask import render_templatefrom app import app, db@app.errorhandler(404)def not_found_error(error): return render_template('404.html'), 404@app.errorhandler(500)def internal_error(error): db.session.rollback() return render_template('500.html'), 500
缺点函数与视图函数非常类似。对付这两个缺点,我将返回各自模板的内容。请把稳这两个函数在模板之后返回第二个值,这是缺点代码编号。对付之前我创建的所有视图函数,我不须要添加第二个返回值,由于我想要的是默认值200(成功相应的状态码)。本处,这些是缺点页面,以是我希望相应的状态码能够反响出来。
500缺点的缺点处理程序应该在引发数据库缺点后调用,而上面的用户名重复实际上便是这种情形。为了确保任何失落败的数据库会话不会滋扰模板触发的其他数据库访问,我实行会话回滚来将会话重置为干净的状态。
404缺点的模板如下:
{% extends \公众base.html\"大众 %}{% block content %} <h1>File Not Found</h1> <p><a href=\"大众{{ url_for('index') }}\公众>Back</a></p>{% endblock %}
500缺点的模板如下:
{% extends \公众base.html\"大众 %}{% block content %} <h1>An unexpected error has occurred</h1> <p>The administrator has been notified. Sorry for the inconvenience!</p> <p><a href=\"大众{{ url_for('index') }}\"大众>Back</a></p>{% endblock %}
这两个模板都从base.html根本模板继续而来,以是缺点页面与运用的普通页面有相同的外不雅观布局。
为了让这些缺点处理程序在Flask中注册,我须要在运用实例创建后导入新的app/errors.py模块。app/__init__.py:
# ...from app import routes, models, errors
04 通过电子邮件发送缺点Flask供应的默认缺点处理机制的另一个问题是没有关照机制,缺点的堆栈跟踪只是被打印到终端,这意味着须要监视做事器进程的输出才能创造缺点。在开拓时,这是非常好的,但是一旦将运用支配在生产做事器上,没有人会关心输出,因此须要采取更强大的办理方案。
我认为对缺点创造采纳积极主动的态度是非常主要的。如果生产环境的运用发生缺点,我想急速知道。以是我的第一个办理方案是配置Flask在发生缺点之后立即向我发送一封电子邮件,邮件正文中包含缺点堆栈跟踪的正文。
第一步,添加邮件做事器的信息到配置文件中:
class Config(object): # ... MAIL_SERVER = os.environ.get('MAIL_SERVER') MAIL_PORT = int(os.environ.get('MAIL_PORT') or 25) MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS') is not None MAIL_USERNAME = os.environ.get('MAIL_USERNAME') MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD') ADMINS = ['your-email@example.com']
电子邮件的配置变量包括做事器和端口,启用加密连接的布尔标记以及可选的用户名和密码。这五个配置变量来源于环境变量。如果电子邮件做事器没有在环境中设置,那么我将禁用电子邮件功能。电子邮件做事器端口也可以在环境变量中给出,但是如果没有设置,则利用标准端口25。电子邮件做事器凭据默认不该用,但可以根据须要供应。 ADMINS配置变量是将收到缺点报告的电子邮件地址列表,以是你自己的电子邮件地址该当在该列表中。
Flask利用Python的logging包来写它的日志,而且这个包已经能够通过电子邮件发送日志了。我所须要做的便是为Flask的日志工具app.logger添加一个SMTPHandler的实例:
import loggingfrom logging.handlers import SMTPHandler# ...if not app.debug: if app.config['MAIL_SERVER']: auth = None if app.config['MAIL_USERNAME'] or app.config['MAIL_PASSWORD']: auth = (app.config['MAIL_USERNAME'], app.config['MAIL_PASSWORD']) secure = None if app.config['MAIL_USE_TLS']: secure = () mail_handler = SMTPHandler( mailhost=(app.config['MAIL_SERVER'], app.config['MAIL_PORT']), fromaddr='no-reply@' + app.config['MAIL_SERVER'], toaddrs=app.config['ADMINS'], subject='Microblog Failure', credentials=auth, secure=secure) mail_handler.setLevel(logging.ERROR) app.logger.addHandler(mail_handler)
如你所见,仅当运用未以调试模式运行,且配置中存在邮件做事器时,我才会启用电子邮件日志记录器。
设置电子邮件日志记录器的步骤由于处理安全可选项而稍显繁琐。实质上,上面的代码创建了一个SMTPHandler实例,设置它的级别,以便它只报告缺点及更严重级别的信息,而不是警告,常规信息或调试,末了将它附加到Flask的app.logger工具中。
有两种方法来测试此功能。最大略的便是利用Python的SMTP调试做事器。这是一个仿照的电子邮件做事器,它接管电子邮件,然后打印到掌握台。要运行此做事器,请打开第二个终端会话并在其上运行以下命令:
(venv) $ python -m smtpd -n -c DebuggingServer localhost:8025
要用这个仿照邮件做事器来测试运用,那么你将设置MAIL_SERVER=localhost和MAIL_PORT=8025。
译者注:本段中去除相识释设置该端口须要管理员权限的部分,由于这和实际情形不符。原文如下:To test the application with this server, then you will setMAIL_SERVER=localhost and MAIL_PORT=8025. If you are on a Linux or Mac OS system, you will likely need to prefix the command with sudo, so that it can execute with administration privileges. If you are on a Windows system, you may need to open your terminal window as an administrator. Administrator rights are needed for this command because ports below 1024 are administrator-only ports. Alternatively, you can change the port to a higher port number, say 5025, and set MAIL_PORTvariable to your chosen port in the environment, and that will not require administration rights.
保持调试SMTP做事器运行并返回到第一个终端,在环境中设置export MAIL_SERVER=localhost和MAIL_PORT=8025(如果利用的是Microsoft Windows,则利用set而不是export)。确保FLASK_DEBUG变量设置为0或者根本不设置,由于运用不会在调试模式中发送电子邮件。运行该运用并再次触发SQLAlchemy缺点,以查看运行仿照电子邮件做事器的终端会话如何显示具有完全堆栈跟踪缺点的电子邮件。
这个功能的第二个测试方法是配置一个真正的电子邮件做事器。以下是利用你的Gmail帐户的电子邮件做事器的配置:
export MAIL_SERVER=smtp.googlemail.comexport MAIL_PORT=587export MAIL_USE_TLS=1export MAIL_USERNAME=<your-gmail-username>export MAIL_PASSWORD=<your-gmail-password>
如果你利用的是Microsoft Windows,记住在每一条语句中用set更换掉export。
Gmail帐户中的安全功能可能会阻挡运用通过它发送电子邮件,除非你明确许可“安全性较低的运用程序”访问你的Gmail帐户。可以阅读此处来理解详细情形,如果你担心帐户的安全性,可以创建一个赞助邮箱帐户,配置它来仅用于测试电子邮件功能,或者你可以暂时启用许可不宁靖安的运用程序来运行此测试,完成后规复为默认值。
05 记录日志到文件中通过电子邮件来吸收缺点提示非常棒,但在其他场景下,有时候就有些不敷了。有些缺点条件既不是一个Python非常又不是重大事件,但是他们在调试的时候也是有足够用途的。为此,我将会为本运用坚持一个日志文件。
为了启用另一个基于文件类型RotatingFileHandler的日志记录器,须要以和电子邮件日志记录器类似的办法将其附加到运用的logger工具中。app/__init__.py:
# ...from logging.handlers import RotatingFileHandlerimport os# ...if not app.debug: # ... if not os.path.exists('logs'): os.mkdir('logs') file_handler = RotatingFileHandler('logs/microblog.log', maxBytes=10240, backupCount=10) file_handler.setFormatter(logging.Formatter( '%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]')) file_handler.setLevel(logging.INFO) app.logger.addHandler(file_handler) app.logger.setLevel(logging.INFO) app.logger.info('Microblog startup')
日志文件的存储路径位于顶级目录下,相对路径为logs/microblog.log,如果其不存在,则会创建它。
RotatingFileHandler类非常棒,由于它可以切割和清理日志文件,以确保日志文件在运用运行很永劫光时不会变得太大。本处,我将日志文件的大小限定为10KB,并只保留末了的十个日志文件作为备份。
logging.Formatter类为日志供应自定义格式。由于这些正在写入到一个文件,我希望它们可以存储尽可能多的信息。以是我利用的格式包括韶光戳、日志记录级别、以及日志来源的源代码文件和行号。
为了使日志记录更有用,我还将运用和文件日志记录器的日志记录级别降落到INFO级别。如果你不熟习日志记录种别,则按照严重程度递增的顺序来认识它们就行了,分别是DEBUG、INFO、WARNING、ERROR和CRITICAL。
日志文件的第一个有趣用场是,做事器每次启动时都会在日志中写入一行。当此运用在生产做事器上运行时,这些日志数据将见告你做事器何时重新启动过。
06修复用户名重复的 BUG利用用户名重复BUG这么久, 现在时候向你展示如何修复它了。
你是否还记得,RegistrationForm已经实现了对用户名的验证,但是编辑表单的哀求稍有不同。在注册期间,我须要确保在表单中输入的用户名不存在于数据库中。在编辑个人资料表单中,我必须做同样的检讨,但有一个例外。如果用户不改变原始用户名,那么验证该当许可,由于该用户名已经被分配给该用户。下面你可以看到我为这个表单实现了用户名验证:
class EditProfileForm(FlaskForm): username = StringField('Username', validators=[DataRequired()]) about_me = TextAreaField('About me', validators=[Length(min=0, max=140)]) submit = SubmitField('Submit') def __init__(self, original_username, args, kwargs): super(EditProfileForm, self).__init__(args, kwargs) self.original_username = original_username def validate_username(self, username): if username.data != self.original_username: user = User.query.filter_by(username=self.username.data).first() if user is not None: raise ValidationError('Please use a different username.')
该实现利用了一个自定义的验证方法,接管表单中的用户名作为参数。这个用户名保存为一个实例变量,并在validate_username()方法中被校验。如果在表单中输入的用户名与原始用户名相同,那么就没有必要检讨数据库是否有重复了。
为了使得新增的验证方法生效,我须要在对应视图函数中添加当前用户名到表单的username字段中:
@app.route('/edit_profile', methods=['GET', 'POST'])@login_requireddef edit_profile(): form = EditProfileForm(current_user.username) # ...
现在这个BUG已经修复了,大多数情形下,往后在编辑个人资料时涌现用户名重复的提交将被友好地阻挡。 但这不是一个完美的办理方案,由于当两个或更多进程同时访问数据库时,这可能不起浸染。如果存在验证通过的进程A和B都考试测验修正用户名为同一个,但稍后进程A考试测验重命名时,数据库已被进程B变动,无法重命名为该用户名,会再次引发数据库非常。 除了有很多做事器进程并且非常繁忙的运用之外,这种情形是不太可能的,以是现在我不会为此担心。
此时,你可以考试测验再次重现该缺点,以理解新的表单验证方法如何防止该缺点。
末了,我自己是一名从事了多年开拓的Python老程序员,辞职目前在做自己的Python私人定制课程,今年年初我花了一个月整理了一份最适宜2019年学习的Python学习干货,可以送给每一位喜好Python的小伙伴,想要获取的可以关注我的头条号并在后台私信我:01,即可免费获取。