作者選擇將捐款捐贈給自由與開源基金會,作為撰寫捐贈計劃的一部分。
引言
Flask是一個輕量級的Python網頁框架,提供了一系列有用的工具和功能,用於在Python語言中創建網頁應用程序。
當您開發網頁應用程序時,不可避免地會遇到應用程序行為與您預期不符的情況。您可能會拼錯變量、誤用for
循環,或者構建一個以不當方式引發Python異常的if
語句,例如在聲明函數之前調用它,或者簡單地查找一個不存在的頁面。如果您學會如何妥善處理錯誤和異常,將會更容易且更順暢地開發您的Flask應用程序。
在本教程中,您將構建一個小型網頁應用程序,展示如何處理開發網頁應用程序時常見的錯誤。您將創建自定義錯誤頁面,使用Flask調試器來排查異常,並使用日誌記錄來追蹤應用程序中的事件。
先決條件
-
一個本地的 Python 3 編程環境。您可以根據您的發行版遵循 如何安裝和配置 Python 3 的本地編程環境 系列中的教程。在本教程中,我們將稱我們的項目目錄為
flask_app
。 -
對 Flask 的基本概念的理解,例如路由、視圖函數和模板。如果您不熟悉 Flask,請查看 如何使用 Flask 和 Python 創建您的首個 Web 應用程序 和 如何在 Flask 應用程序中使用模板。
-
對基本 HTML 概念的理解。您可以回顧我們的 如何使用 HTML 構建網站 教程系列以獲取背景知識。
步驟 1 — 使用 Flask 調試器
在這一步中,您將創建一個包含一些錯誤的應用程序,並在不使用調試模式的情況下運行它,以查看應用程序的響應方式。然後,您將在開啟調試模式的情況下運行它,並使用調試器來排查應用程序錯誤。
在您的編程環境已激活並安裝了 Flask 的情況下,打開位於 flask_app
目錄中的 app.py
文件進行編輯:
在 app.py
文件中添加以下代碼:
在上面的代碼中,您首先從 flask
包中導入了 Flask
類。然後創建了一個名為 app
的 Flask 應用實例。您使用 @app.route()
裝飾器來創建一個名為 index()
的視圖函數,該函數調用 render_template()
函數作為返回值,進而渲染一個名為 index.html
的模板。這段代碼中有兩個錯誤:第一個是您沒有導入 render_template()
函數,第二個是 index.html
模板文件不存在。
保存並關閉文件。
接下來,使用以下命令通過 FLASK_APP
環境變量告知 Flask 應用程序(在 Windows 上,使用 set
而不是 export
):
然後使用 flask run
命令運行應用程序服務器:
您將在終端中看到以下信息:
Output * Serving Flask app 'app' (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
此輸出提供了以下信息:
-
正在運行的 Flask 應用程序(在本例中為
app.py
) -
此處的環境標記為
production
。警告信息強調此伺服器不適用於生產部署。您目前使用此伺服器進行開發,因此可忽略此警告,但若需更多資訊,請參閱 Flask 文件中的部署選項頁面。您亦可查閱使用 Gunicorn 部署 Flask 的教程,或使用 uWSGI 的教程,亦可依照如何在 App Platform 上使用 Gunicorn 部署 Flask 應用教程,透過 DigitalOcean App Platform 部署您的 Flask 應用程式。 -
偵錯模式已關閉,意味著Flask偵錯器未運行,您將無法在應用程式中收到有用的錯誤訊息。在生產環境中,顯示詳細錯誤會使您的應用程式面臨安全漏洞的風險。
-
伺服器正在運行於
http://127.0.0.1:5000/
網址。要停止伺服器,請使用CTRL+C
,但現在先不要這樣做。
現在,使用您的瀏覽器訪問索引頁面:
http://127.0.0.1:5000/
您將看到一條類似以下的訊息:
OutputInternal Server Error
The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.
這是 500 Internal Server Error,這是一個伺服器錯誤回應,表明伺服器在應用程式代碼中遇到了內部錯誤。
在終端機中,您將看到以下輸出:
Output[2021-09-12 15:16:56,441] ERROR in app: Exception on / [GET]
Traceback (most recent call last):
File "/home/abd/.local/lib/python3.9/site-packages/flask/app.py", line 2070, in wsgi_app
response = self.full_dispatch_request()
File "/home/abd/.local/lib/python3.9/site-packages/flask/app.py", line 1515, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/home/abd/.local/lib/python3.9/site-packages/flask/app.py", line 1513, in full_dispatch_request
rv = self.dispatch_request()
File "/home/abd/.local/lib/python3.9/site-packages/flask/app.py", line 1499, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
File "/home/abd/python/flask/series03/flask_app/app.py", line 8, in index
return render_template('index.html')
NameError: name 'render_template' is not defined
127.0.0.1 - - [12/Sep/2021 15:16:56] "GET / HTTP/1.1" 500 -
上面的追蹤記錄通過了觸發內部伺服器錯誤的代碼。這一行 NameError: name 'render_template' is not defined
給出了問題的根本原因:render_template()
函數尚未被導入。
如您所見,您必須前往終端機來排除錯誤,這並不方便。
您可以通過在開發伺服器中啟用調試模式來獲得更好的故障排除體驗。為此,請使用 CTRL+C
停止伺服器,並將環境變數 FLASK_ENV
設置為 development
,以便您可以在開發模式下運行應用程式(這將啟用調試器),使用以下命令(在 Windows 上,使用 set
代替 export
):
運行開發伺服器:
在終端機中,您會看到類似以下的輸出:
Output * Serving Flask app 'app' (lazy loading)
* Environment: development
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 120-484-907
在此處,您可以看到環境現在設置為development
,調試模式已開啟,且調試器處於活動狀態。Debugger PIN
是您在瀏覽器中解鎖控制台所需的PIN碼(您可以通過點擊下圖中圈出的終端圖標來訪問一個交互式Python shell)。
在瀏覽器中刷新索引頁面,您將看到以下頁面:
在這裡,您可以看到錯誤信息以更易於理解的方式顯示。第一個標題給出了導致問題的Python異常名稱(在此例中為NameError
)。第二行給出了直接原因(render_template()
未定義,這意味著在此情況下未導入)。接下來,您會看到執行的內部Flask代碼的追溯路徑。從底部向上閱讀追溯信息,因為追溯的最后一行通常包含最有用的信息。
注意:
圈出的終端圖標允許您在瀏覽器中對不同框架運行Python代碼。這在您想要像在Python交互式shell中那樣檢查變量值時非常有用。當您點擊終端圖標時,您需要輸入運行服務器時獲得的調試器PIN碼。在本教程中,您不需要使用這個交互式shell。
要解決這個 NameError
問題,保持伺服器運行,打開一個新的終端視窗,激活你的環境,並打開你的 app.py
檔案:
修改檔案如下所示:
保存並關閉檔案。
這裡你導入了缺失的 render_template()
函數。
在開發伺服器運行時,刷新瀏覽器中的索引頁面。
這次你會看到一個包含如下信息的錯誤頁面:
Outputjinja2.exceptions.TemplateNotFound
jinja2.exceptions.TemplateNotFound: index.html
這個錯誤信息表明 index.html
模板不存在。
要解決這個問題,你將創建一個 base.html
模板檔案,其他模板將繼承自該模板以避免代碼重複,然後創建一個繼承自基礎模板的 index.html
模板。
創建 templates
目錄,這是 Flask 尋找模板檔案的目錄。然後使用你喜歡的編輯器打開一個 base.html
檔案:
將以下代碼添加到你的 base.html
檔案中:
保存並關閉檔案。
這個基礎模板包含了你將在其他模板中重用的所有 HTML 框架。title
區塊將被替換以設置每個頁面的標題,content
區塊將被替換為每個頁面的內容。導航欄有兩個連結,一個指向索引頁面,你使用 url_for()
輔助函數連結到 index()
視圖函數,另一個連結指向一個關於頁面(如果你選擇在應用中包含一個)。
接著,開啟一個名為 index.html
的模板檔案,它將繼承自基礎模板。
將以下程式碼添加到其中:
保存並關閉檔案。
在上面的程式碼中,您擴展了基礎模板並覆蓋了 content
區塊。然後設置頁面標題,並使用 title
區塊在 H1
標題中顯示它,並在 H2
標題中顯示問候語。
在開發伺服器運行時,刷新瀏覽器中的索引頁面。
您會看到應用程式不再顯示錯誤,索引頁面按預期顯示。
您現在已經使用了調試模式並了解了如何處理錯誤訊息。接下來,您將中止請求以回應您選擇的錯誤訊息,並了解如何回應自定義錯誤頁面。
步驟 2 — 創建自定義錯誤頁面
在這一步中,您將學習如何中止請求並回應 404 HTTP 錯誤訊息,當用戶請求伺服器上不存在的數據時。您還將學習如何為常見的 HTTP 錯誤創建自定義錯誤頁面,例如 404 Not Found
錯誤和 500 Internal Server Error
錯誤。
為了展示如何中止請求並回應自訂的 404 HTTP 錯誤頁面,您將創建一個頁面來顯示幾條訊息。如果所請求的訊息不存在,您將回應一個 404 錯誤。
首先,打開您的 app.py
檔案,新增一個用於訊息頁面的路由:
在檔案末尾添加以下路由:
儲存並關閉檔案。
在上述路由中,您有一個 URL 變數 idx
。這是將決定顯示哪條訊息的索引。例如,如果 URL 是 /messages/0
,則會顯示第一條訊息(Message Zero
)。您使用 int
轉換器來僅接受正整數,因為 URL 變數預設為字串值。
在 message()
視圖函數內部,您有一個名為 messages
的常規 Python 列表,包含三條訊息。(在實際應用場景中,這些訊息將來自資料庫、API 或其他外部數據源。)該函數返回對 render_template()
函數的調用,帶有兩個參數,message.html
作為模板文件,以及一個將傳遞給模板的 message
變數。該變數將根據 URL 中 idx
變數的值從 messages
列表中獲取一個列表項。
接下來打開一個新的 message.html
模板文件:
將以下代碼添加到其中:
保存並關閉文件。
在上述代碼中,您擴展了基礎模板並覆蓋了content
塊。您在一個H1標題中添加了標題(Messages
),並在H2標題中顯示了message
變量的值。
在開發服務器運行時,訪問瀏覽器中的以下URL:
http://127.0.0.1:5000/messages/0
http://127.0.0.1:5000/messages/1
http://127.0.0.1:5000/messages/2
http://127.0.0.1:5000/messages/3
您會看到,在前三個URL中,H2
分別包含文本Message Zero
、Message One
或Message Two
。然而,在第四個URL上,服務器將回應一個IndexError: list index out of range
錯誤信息。在生產環境中,回應本應是500 Internal Server Error
,但此處正確的回應應是404 Not Found
,以表明服務器找不到索引為3
的消息。
您可以使用Flask的abort()
輔助函數來回應404
錯誤。為此,打開app.py
文件:
編輯第一行以導入abort()
函數。然後通過添加一個try ... except
子句來編輯message()
視圖函數,如下所示:
保存並關閉文件。
在上述程式碼中,您導入了 abort()
函數,用於中止請求並回應錯誤。在 message()
視圖函數中,您使用了一個 try ... except
子句來包裝該函數。首先,您嘗試返回與 URL 中索引對應的消息的 messages
模板。如果該索引沒有對應的消息,則會引發 IndexError
異常。然後,您使用 except
子句來捕獲該錯誤,並使用 abort(404)
來中止請求並回應 404 Not Found
HTTP 錯誤。
在開發伺服器運行時,使用瀏覽器重新訪問之前回應 IndexError
的 URL(或訪問任何索引大於 2 的 URL):
http://127.0.0.1:5000/messages/3
您將看到以下回應:
Not Found
The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
現在,您有一個更好的錯誤消息,表明伺服器找不到所請求的消息。
接下來,您將為 404 錯誤頁面和 500 錯誤頁面製作模板。
首先,您將使用特殊的 @app.errorhandler()
裝飾器註冊一個函數作為 404
錯誤的處理程序。打開 app.py
文件進行編輯:
nano app.py
編輯文件,添加高亮部分如下:
保存並關閉文件。
在這裡,您使用 @app.errorhandler()
裝飾器將函數 page_not_found()
註冊為自定義錯誤處理器。該函數將錯誤作為參數,並返回對 render_template()
函數的調用,其中包含一個名為 404.html
的模板。您稍後將創建此模板,並且可以根據需要使用其他名稱。您還在 render_template()
調用之後返回整數 404
。這告訴 Flask 響應中的狀態碼應為 404
。如果您不添加它,默認的狀態碼響應將是 200
,這意味著請求已成功。
接下來,打開一個新的 404.html
模板:
將以下代碼添加到其中:
保存並關閉文件。
就像任何其他模板一樣,您擴展了基礎模板,替換了 content
和 title
塊的內容,並添加了您自己的 HTML 代碼。在這裡,您有一個 <h1>
標題作為標題,一個 <p>
標籤帶有自定義錯誤消息,告訴用戶該頁面未找到,以及一條對可能手動輸入 URL 的用戶有幫助的消息。
您可以像在其他模板中一樣,在錯誤頁面中使用任何 HTML、CSS 和 JavaScript。
在開發服務器運行時,使用瀏覽器重新訪問以下 URL:
http://127.0.0.1:5000/messages/3
您會看到該頁面現在具有基礎模板中的導航欄和自定義錯誤消息。
同樣地,您可以為 500 Internal Server Error
錯誤添加自定義錯誤頁面。打開 app.py
檔案:
在 404
錯誤處理器下方添加以下錯誤處理器:
這裡您使用與處理 404
錯誤相同的模式。您使用 app.errorhandler()
裝飾器並傳入 500
參數,將名為 internal_error()
的函數轉換為錯誤處理器。您渲染名為 500.html
的模板,並以 500
狀態碼回應。
接著,為了展示自定義錯誤的呈現方式,在檔案末尾添加一個回應 500
HTTP 錯誤的路由。無論調試器是否運行,此路由總是會給出 500 Internal Server Error
:
這裡您創建了一個 /500
路由,並使用 abort()
函數以 500
HTTP 錯誤回應。
保存並關閉檔案。
接下來,打開新的 500.html
模板:
向其中添加以下代碼:
保存並關閉檔案。
這裡,您做了與 404.html
模板相同的事情。您擴展了基礎模板,並用標題和兩條自定義消息替換了內容區塊,告知用戶發生了內部伺服器錯誤。
隨著開發伺服器運行,訪問回應 500
錯誤的路由:
http://127.0.0.1:5000/500
您的自訂頁面將出現,而非通用錯誤頁面。
您現在知道如何在 Flask 應用程式中使用自訂錯誤頁面處理 HTTP 錯誤。接下來,您將學習如何使用日誌記錄來追蹤應用程式中的事件。追蹤事件有助於您了解程式碼的行為,進而協助開發和故障排除。
步驟 3 — 使用日誌記錄追蹤應用程式中的事件
在這一步中,您將使用日誌記錄來追蹤伺服器運行和應用程式使用時發生的事件,這有助於您查看應用程式代碼中的情況,以便更容易排除錯誤。
您已經在開發伺服器運行時看到過日誌,通常看起來像這樣:
127.0.0.1 - - [21/Sep/2021 14:36:45] "GET /messages/1 HTTP/1.1" 200 -
127.0.0.1 - - [21/Sep/2021 14:36:52] "GET /messages/2 HTTP/1.1" 200 -
127.0.0.1 - - [21/Sep/2021 14:36:54] "GET /messages/3 HTTP/1.1" 404 -
在這些日誌中,您可以看到以下信息:
127.0.0.1
:伺服器運行的主機。[21/Sep/2021 14:36:45]
:請求的日期和時間。GET
:HTTP 請求方法。在此情況下,GET
用於檢索數據。/messages/2
: 使用者請求的路徑。HTTP/1.1
: HTTP版本。200
或404
: 回應的狀態碼。
這些日誌有助於您診斷應用程式中出現的問題。當您想要了解某些請求的更多詳細信息時,可以使用Flask提供的app.logger
記錄器記錄更多信息。
透過日誌記錄,您可以使用不同的函數在不同的日誌級別上報告信息。每個級別表示發生了一個具有特定嚴重程度的事件。可以使用以下函數:
app.logger.debug()
: 用於詳細的事件信息。app.logger.info()
: 確認事情按預期工作。app.logger.warning()
: 指示發生了意外情況(例如“磁盤空間不足”),但應用程式仍在按預期工作。app.logger.error()
: 應用程式的某個部分發生了錯誤。app.logger.critical()
: 一個嚴重錯誤;整個應用程式可能停止工作。
為了演示如何使用Flask記錄器,請打開您的app.py
文件進行編輯以記錄幾個事件:
編輯message()
視圖函數,使其如下所示:
保存並關閉文件。
在這裡,您在不同層級記錄了幾個事件。您使用 app.logger.info()
來記錄一個按預期工作的事件(這是 INFO
層級)。您使用 app.logger.debug()
來記錄詳細信息(DEBUG
層級),提到應用程序現在正在獲取具有特定索引的消息。然後您使用 app.logger.error()
來記錄一個 IndexError
異常已被引發,並指出了導致問題的特定索引(ERROR
層級,因為發生了錯誤)。
訪問以下 URL:
http://127.0.0.1:5000/messages/1
您將在運行服務器的終端中看到以下信息:
Output
[2021-09-21 15:17:02,625] INFO in app: Building the messages list...
[2021-09-21 15:17:02,626] DEBUG in app: Get message with index: 1
127.0.0.1 - - [21/Sep/2021 15:17:02] "GET /messages/1 HTTP/1.1" 200 -
在這裡您可以看到 app.logger.info()
記錄的 INFO
消息,以及使用 app.logger.debug()
記錄的帶有索引號的 DEBUG
消息。
現在訪問一個不存在消息的 URL:
http://127.0.0.1:5000/messages/3
您將在終端中看到以下信息:
Output[2021-09-21 15:33:43,899] INFO in app: Building the messages list...
[2021-09-21 15:33:43,899] DEBUG in app: Get message with index: 3
[2021-09-21 15:33:43,900] ERROR in app: Index 3 is causing an IndexError
127.0.0.1 - - [21/Sep/2021 15:33:43] "GET /messages/3 HTTP/1.1" 404 -
如您所見,您有之前看到的 INFO
和 DEBUG
日誌,以及一個新的 ERROR
日誌,因為索引為 3
的消息不存在。
記錄事件、詳細信息和錯誤有助於您識別哪裡出了問題,並使故障排除更容易。
您在這一步中學習了如何使用 Flask 的日誌記錄器。查看 如何在 Python 3 中使用日誌記錄 以更好地理解日誌記錄。如需深入了解日誌記錄,請參閱 Flask 日誌記錄文檔 和 Python 日誌記錄文檔。
結論
您現在知道如何在 Flask 中使用調試模式,以及如何排查和修復在開發 Flask 網絡應用程序時可能遇到的一些常見錯誤。您還為常見的 HTTP 錯誤創建了自定義錯誤頁面,並使用 Flask 日誌記錄器來跟踪應用程序中的事件,以幫助您檢查和了解應用程序的行為。
如果您想了解更多關於 Flask 的信息,請查看 Flask 主題頁面。
Source:
https://www.digitalocean.com/community/tutorials/how-to-handle-errors-in-a-flask-application