跟着學習(新版):https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-vii-error-handling
從這章開始摒棄舊版,蛤蛤
回顧上一章:https://blog.csdn.net/weixin_41263513/article/details/85015311
本章內容
- 自定義錯誤界面
- 通過email發送錯誤
- log錯誤日誌文件
- 修復重複用戶名的bug
如果你是按照上一章一步一步往下做的,你也許不用測試也會有一個非常非常大的疑問,如果用戶名在編輯的時候恰好重名了,將會發生什麼呢,那當然是error啦!
那麼怎麼解決呢?可能有些小機靈鬼想到了,用戶名跟郵箱分開,標明用郵箱登陸不就行了?巧了,我也是這樣想的,不過我也見過不少是用用戶名登陸的,所以我也就耐着性子把大大的英文給啃完了,下面的是我跟着大大的思維走的解決途徑
自定義錯誤界面
flask爲應用程序提供了一種自定義錯誤頁面的機制,這樣用戶就看不到普通無聊的默認錯誤頁面,下面讓我們爲HTTP錯誤404和500定義自定義錯誤頁面,這是兩個最常見的錯誤頁面。 爲其他錯誤定義頁面的工作方式相同。
文件:/app/errors.py
from flask import render_template
from 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界面:
文件:/app/templates/404.html
{% extends "base.html" %}
{% block content %}
<h1>File Not Found</h1>
<p><a href="{{ url_for('index') }}">Back</a></p>
{% endblock %}
505界面:
文件:/app/templates/500.html
{% 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 %}
不要忘了最重要的最後一部,把errors.py的模塊導入到app初始化代碼中:
文件:/app/–init–.py
# ...
from app import routes, models, errors
可能有些朋友會覺得,把上面的app.errorhandler()裝飾的錯誤視圖函數放到routes.py不行嗎?
那當然是,可以啦!不過錯誤有很多種,不知有404和500,把這種分開來也是方便工作嘛
通過email發送錯誤
Flask提供的默認錯誤處理的另一個問題是沒有通知,錯誤的堆棧跟蹤被打印到終端,這意味着需要監視服務器進程的輸出以發現錯誤。 當您在開發期間運行應用程序時,這非常好,但是一旦將應用程序部署在生產服務器上,就沒有人會查看輸出,因此需要採用更強大的解決方案。
其中一個就是將Flask配置爲在發生錯誤後立即向我發送電子郵件,並在電子郵件正文中顯示錯誤的堆棧跟蹤。
第一步是將電子郵件服務器詳細信息添加到配置文件中
文件:/config.py
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 = ['[email protected]']
五個配置變量來自它們的環境變量對應物。 如果未在環境中設置電子郵件服務器,那麼我將使用它作爲需要禁用電子郵件錯誤的標誌。 電子郵件服務器端口也可以在環境變量中給出,但如果未設置,則使用標準端口25。 默認情況下不使用電子郵件服務器憑據,但可以根據需要提供。 ADMINS配置變量是將接收錯誤報告的電子郵件地址列表,因此您自己的電子郵件地址應該在該列表中。
Flask使用Python的日誌包來編寫日誌,這個包能夠通過電子郵件發送日誌。需要做的就是發送有關錯誤日誌log到電子郵件
文件:/app/–init–.py
import logging
from 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)
不要忘了我們00章強調過的!!!千萬別在生產服務器中啓用調試模式!!!
那麼在debug=False的時候,就執行以上代碼,創建一個SMTPHandler實例,設置其級別以便它只報告錯誤而不是警告,信息或調試消息,最後將它從Flask附加到app.logger對象。
關於郵件這一章大家先不要着急測試,可以先看看這篇大佬的技術展示:https://blog.csdn.net/qq_42239520/article/details/80368733
以後我再參照其他資料重新寫一遍收發郵件蛤~
log錯誤日誌文件
通過電子郵件接收錯誤很好,但有時這還不夠。 有一些失敗條件不會在Python異常中結束並且不是主要問題,但它們可能仍然足夠有趣,可以保存以用於調試目的。 出於這個原因,我還要爲應用程序維護一個日誌文件。
要啓用基於文件的日誌,要啓動一個處理程序(類型爲RotatingFileHandler)需要以與電子郵件處理程序類似的方式附加到應用程序記錄器。
文件:/app/–init–.py
# ...
from logging.handlers import RotatingFileHandler
import 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')
把日誌全填進名爲microblog.log的文件,如果它不存在,就創建它。
RotatingFileHandler類可以確保在應用程序運行很長時間後日志文件不會變得太大。而且我們可以將日誌文件的大小限制爲10KB,並且我們會留最後十個日誌文件爲備份。
logging.Formatter類爲日誌消息提供自定義格式。由於這些消息將轉到文件,我希望它們擁有儘可能多的信息。所以我使用的格式包括時間戳,日誌記錄級別,消息和源文件以及日誌條目所在的行號。
爲了使日誌記錄更有用,我還將日誌記錄級別降低到INFO類別,包括應用程序記錄器和文件記錄器處理程序。根據日誌記錄的類別,嚴重程度將逐漸增加,即DEBUG,INFO,WARNING,ERROR和CRITICAL。
作爲日誌文件的第一個有趣用途,服務器每次啓動時都會在日誌中寫入一行。當此應用程序在生產服務器上運行時,這些日誌條目將告訴您服務器何時重新啓動。
修復重複用戶名的bug
上面說了很多關於錯誤的處理,還點了點email的用法,現在開始真正的處理目前的最大boss
在註冊過程中,我需要確保數據庫中不存在表單中輸入的用戶名。 在編輯個人資料表單上,我必須執行相同的檢查,但有一個例外。 如果用戶保持原始用戶名不變,則驗證應允許,因爲該用戶名已分配給該用戶
文件:/app/forms.py
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.')
上述其實跟RegistrationForm類內的validate_username方法一致呢
要使用這個新的驗證方法,我需要在視圖函數中添加原始用戶名參數:
文件:/app/routes.py
@app.route('/edit_profile', methods=['GET', 'POST'])
@login_required
def edit_profile():
form = EditProfileForm(current_user.username)
# ...
結束!是的,這一章最大的功績也就解決了一個bug,但關鍵是,這個bug並不是什麼大難題,不過因此牽出了處理error的方法,還有郵件發送的方法,關於郵件發送我也是特別感興趣,脫離這個【跟着學習】系列後我會針對我自己的博客再重新弄一遍郵件發送的功能!這篇就沒有效果圖啦~蛤蛤