【Flask/跟着學習】Flask大型教程項目#06:錯誤處理

跟着學習(新版):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的方法,還有郵件發送的方法,關於郵件發送我也是特別感興趣,脫離這個【跟着學習】系列後我會針對我自己的博客再重新弄一遍郵件發送的功能!這篇就沒有效果圖啦~蛤蛤

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章