上一章學習了自動化測試,很好,如今咱們能夠絞盡腦汁寫出一份全面的測試,來保證代碼永遠健康了。html
話雖如此,可是做爲一個獨立開發者很難寫出真正全面的測試代碼。這是由於用戶在使用你的網站時可不會循規蹈矩,而是會以各類怪異的姿式瀏覽網頁、上傳數據。但這也不是壞事,用戶就是自然的測試人員,他們會很可愛的幫你找出一大堆的bug,陪你度過難眠的夜晚(伴隨着編程能力的提高)。python
如今的問題是,開發者如何得知用戶到底遇到了哪些問題?用戶們大部分都與你素昧生平,分部在世界各地。更糟糕的是,部署在線上時因爲配置了DEBUG = False
,出錯時並不會出現報錯頁面,連用戶本身都不清楚究竟是哪裏有bug。git
Django給你的答案:日誌。github
日誌是指程序在運行過程當中,對狀態、時間、錯誤等的記錄。即把運行過程當中產生的信息輸出或保存起來,供開發者查閱。sql
Django使用Python內置的logging
模塊處理日誌。關於該模塊的使用,Python文檔裏有很是詳細的討論。若是你從未用過,本文提供一個快速入門。數據庫
日誌事件的信息流程以下:django
這個圖看不懂也不要緊。之後你須要深度使用日誌時,會回來仔細研究它的。
一份日誌配置由Loggers
、Handlers
、Filters
、Formatters
四部分組成。編程
Logger
即記錄器,是日誌系統的入口。它有三個重要的工做:瀏覽器
Handler
)每一條寫入logger的消息都是一條日誌記錄。每一條日誌記錄也包含級別,表明對應消息的嚴重程度。經常使用的級別以下:bash
DEBUG
:排查故障時使用的低級別系統信息,一般開發時使用INFO
:通常的系統信息,並不算問題WARNING
:描述系統發生的小問題的信息,但一般不影響功能ERROR
:描述系統發生的大問題的信息,可能會致使功能不正常CRITICAL
:描述系統發生嚴重問題的信息,應用程序有崩潰風險當logger處理一條消息時,會將本身的日誌級別和這條消息的日誌級別作對比。若是消息的級別匹配或者高於logger的日誌級別,它就會被進一步處理;不然這條消息就會被忽略掉。
當logger肯定了一條消息須要處理以後,會把它傳給Handler
。
Handler
即處理器,它的主要功能是決定如何處理logger中每一條消息,好比把消息輸出到屏幕、文件或者Email中。
和logger同樣,handler也有級別的概念。若是一條日誌記錄的級別不匹配或者低於handler的日誌級別,則會被handler忽略。
一個logger能夠有多個handler,每個handler能夠有不一樣的日誌級別。這樣就能夠根據消息的重要性不一樣,來提供不一樣類型的輸出。例如,你能夠添加一個handler把ERROR
和CRITICAL
消息發到你的Email,再添加另外一個 handler把全部的消息(包括ERROR
和CRITICAL
消息)保存到文件裏。
Filter
即過濾器。在日誌記錄從logger傳到handler的過程當中,使用Filter
來作額外的控制。例如只容許某個特定來源的ERROR
消息輸出。
Filter還被用來在日誌輸出以前對日誌記錄作修改。例如當知足必定條件時,把日誌記錄從 ERROR
降到 WARNING
級別。
Filter在logger和handler中均可以添加;多個filter能夠連接起來使用,來作多重過濾操做。
Formatter
即格式化器,主要功能是肯定最終輸出的形式和內容。
說了這麼多腦袋都說暈了,接下來看兩個示例。
在Django中能夠經過字典的形式對整個項目的日誌進行配置,配置的位置固然是在settings.py
中了。一個簡單的配置以下:
my_blog/settings.py ... import os LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'file': { 'level': 'DEBUG', 'class': 'logging.FileHandler', 'filename': os.path.join(BASE_DIR, 'logs/debug.log'), }, }, 'loggers': { 'django': { 'handlers': ['file'], 'level': 'DEBUG', 'propagate': True, }, }, }
字典中的version
指明瞭配置的版本;disable_existing_loggers
指明是否禁止默認配置的記錄器。這兩項一般不須要去改動,重點看下loggers
和handlers
的配置:
Django
記錄器,它會接收Django層次結構中的全部消息。而後咱們定義了須要處理DEBUG
以上級別的消息,並把這些消息傳遞給名叫file
的處理器。'propagate': True
意思是本記錄器處理過的消息其餘處理器也能夠繼續處理。file
的handlers
中了。這個處理器定義了消息處理級別仍然爲DEBUG,在class中定義將消息輸出到文件中去,文件地址爲項目目錄的logs/debug.log
。filters
和formatters
,所以會採用默認的設置。須要注意的是日誌的輸出文件的目錄logs/
必定要提早建立好,而且確保項目擁有此目錄的寫入權限。
這個日誌系統就配置好了!接下來運行項目,隨便刷新幾個頁面看看debug.log
中有沒有寫入消息:
logs/debug.log (0.001) SELECT name, type FROM sqlite_master WHERE type in ('table', 'view') AND NOT name='sqlite_sequence' ORDER BY name; args=None (0.000) SELECT "django_migrations"."app", "django_migrations"."name" FROM "django_migrations"; args=() ... ... ...
debug.log
文件中出現了一大堆冗長的信息,由於DEBUG
級別會包含全部的數據庫查詢記錄。
默認狀況下,僅在調試模式下才會顯示DEBUG
級別的消息日誌,部署在線上時只會將INFO
或以上的信息進行記錄。
再試試別的。把上面代碼中記錄器和處理器的日誌級別都改成INFO
:
LOGGING = { ... 'handlers': { 'file': { 'level': 'INFO', ... }, }, 'loggers': { 'django': { 'level': 'INFO', ... }, }, }
再刷新幾回界面,看看輸出的內容:
"GET /article/article-list/ HTTP/1.1" 200 14438 "GET /article/article-detail/32/ HTTP/1.1" 200 33364 "GET /accounts/login/ HTTP/1.1" 200 7180 ...
此次清爽多了,輸出的主要是頁面的拉取信息。
讓咱們再看看ERROR
信息長什麼樣的。在地址欄輸入一個不存在的文章詳情頁面地址:
http://127.0.0.1:8000/article/article-detail/9999/
很明顯這會獲得一個數據不存在的報錯:
Internal Server Error: /article/article-detail/9999/ Traceback (most recent call last): File "E:\django_project\env\lib\site-packages\django\core\handlers\exception.py", line 34, in inner response = get_response(request) ... article.models.ArticlePost.DoesNotExist: ArticlePost matching query does not exist. "GET /article/article-detail/9999/ HTTP/1.1" 500 80792
ERROR
日誌輸出了整個bug的回溯,和你在瀏覽器中的報錯是徹底同樣的,這些信息就很是的有用了。基本上ERROR
信息可以暴露出用戶在使用你的網站過程當中的大部分問題;也就是說每個ERROR
都是須要你去解決掉的。ERROR
信息的錯誤碼一般都是「500」,也就是服務器內部錯誤的代碼。
不過仔細想一想,彷佛找不到對應的資源在不少時候並非bug,而是用戶在輸入url時本身犯了錯誤。因此咱們把文章詳情視圖的ArticlePost.objects.get(id=id)
改爲get_object_or_404(ArticlePost, id=id)
試試:
article/views.py ... from django.shortcuts import get_object_or_404 def article_detail(request, id): # 取出相應的文章 # article = ArticlePost.objects.get(id=id) article = get_object_or_404(ArticlePost, id=id) ...
服務器重啓後再次刷新一個不存在的頁面,看看日誌:
Not Found: /article/article-detail/9999/ "GET /article/article-detail/9999/ HTTP/1.1" 404 1780
如今它不是一條ERROR
信息了,而是變爲了WARNING
,因此也沒有了錯誤回溯(錯誤碼也由 500 變成了 404)。這裏就能看出這兩個方法的重要區別了;在項目中到底選擇哪一個沒有定論,仍是以你的具體需求決定。
接下來再看一個複雜的日誌配置:
LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'verbose': { 'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}', 'style': '{', }, 'simple': { 'format': '{levelname} {message}', 'style': '{', }, }, 'filters': { 'require_debug_true': { '()': 'django.utils.log.RequireDebugTrue', }, }, 'handlers': { 'console': { 'level': 'INFO', 'filters': ['require_debug_true'], 'class': 'logging.StreamHandler', 'formatter': 'simple' }, 'mail_admins': { 'level': 'ERROR', 'class': 'django.utils.log.AdminEmailHandler', 'formatter': 'verbose', }, 'file': { 'level': 'WARNING', 'class': 'logging.FileHandler', 'filename': os.path.join(BASE_DIR, 'logs/debug.log'), 'formatter': 'verbose', }, }, 'loggers': { 'django': { 'handlers': ['console'], 'propagate': True, }, 'django.request': { 'handlers': ['file', 'mail_admins'], 'level': 'WARNING', 'propagate': False, }, } }
讓咱們來分解一下此配置。
配置中定義了兩個格式化器:
verbose
:詳細的格式化器,依次輸出:消息級別、發生時間、拋出模塊、進程ID、線程ID、提示信息simple
:簡要的格式化器,僅輸出消息級別和提示信息一個過濾器:
require_debug_true
:使用此過濾器的消息僅在調試時纔會生效三個處理器:
console
:處理INFO
以上級別消息,輸出簡要信息到命令行中;此處理器僅在調試模式生效mail_admins
:處理ERROR
以上級別消息,輸出詳細信息到Email中file
:處理WARNING
以上級別消息,輸出詳細信息到文件中兩個記錄器:
django
:將django產生的全部消息轉交給console
處理器django.request
:將網絡請求相關消息轉交給file
、mail_admins
這兩個處理器。注意這裏的'propagate': False
使得此記錄器處理過的消息就再也不讓django
記錄器再次處理了讀者能夠嘗試製造不一樣級別的消息,看看日誌系統是否正常工做。固然最重要的,跟Email有關的配置必定要事先把Email給設置好,即下面的內容填成你的:
# SMTP服務器 EMAIL_HOST = 'your smtp' # 郵箱名 EMAIL_HOST_USER = 'your email' # 郵箱密碼 EMAIL_HOST_PASSWORD = 'your password' # 發送郵件的端口 EMAIL_PORT = 25 # 是否使用 TLS EMAIL_USE_TLS = True # 默認的發件人 DEFAULT_FROM_EMAIL = 'your email'
如今咱們已經能夠愉快的記錄日誌了,接下來一個問題是如何去分割日誌?假設你的網站可以有幸運行十年時間,若是不間斷的往同一個文件中寫日誌信息,最終它會變成一個拖垮服務器的龐然大物。
最好是日誌可以按天然天進行記錄和分割。好在這個問題也不須要你去費腦筋,Python幫你搞定了。
只須要把處理器稍稍改一下:
my_blog/settings.py ... LOGGING = { ... 'handlers': { ... 'file': { ... # 註釋掉 class # 'class': 'logging.FileHandler', #新增內容 'class': 'logging.handlers.TimedRotatingFileHandler', 'when': 'midnight', 'backupCount': 30, }, }, ... }
TimedRotatingFileHandler
:Python內置的隨時間分割日誌文件的模塊when
:分割時間爲凌晨backupCount
:日誌文件保存日期爲30天接下來把系統時間日後調一天,而後從新啓動服務器:
python manage.py runserver --noreload
注意此次啓動有點不同,後面有個--noreload
後綴。這是由於一般Django的調試服務器運行時會順帶啓動重載器,因此每當重載器檢測到代碼有變化後,會自動重啓服務器,至關的方便。但問題是分割文件與重載器同時操做日誌文件會產生衝突,所以這裏必定要用--noreload
暫時將重載器禁止掉。
而後就能夠愉快的刷幾條消息到文件中啦。你會發現老日誌已經改名爲debug.log.2019-07-17
了,而剛刷的新消息則保存在debug.log
中。
除了上面介紹的TimedRotatingFileHandler
,Python還提供了一個按照文件大小分割的RotatingFileHandler
。有興趣的看 Python官方文檔
內置配置實際上已經可以知足90%以上的日誌需求了,但總有時候你想在一些奇怪的地方進行記錄,這就須要你本身在代碼中插入自定義的日誌記錄代碼了。
自定義日誌用起來也是至關方便的:
from my_blog.settings import LOGGING import logging logging.config.dictConfig(LOGGING) logger = logging.getLogger('django.request') def whatever(request): # do something logger.warning('Something went wrong!') # do something else
導入剛纔寫的日誌框架並將django.request
配置到logger
對象中。而後你就能夠在任何地方安插任何級別的消息了。消息內容能夠用字符串的格式化方法(str.format()
),玩出各類花樣。
關於日誌的入門介紹就到此爲止了,想深刻學習的讀者請繼續閱讀本文的參考文章:
和上章相似,本章的內容也是概念偏多,但願讀者儘量去理解,最起碼要囫圇吞棗的把日誌成功移植到你的項目中去。獲取一份好的日誌,有時候遠比開發一個可有可無的新功能更重要。
比較起來博主認爲對博客項目來講,日誌比測試還重要,畢竟用戶的使用體驗是最佳的實踐。
但請不要誤會個人意思。測試和日誌就像兩兄弟,測試解決開發中的問題,日誌解決維護中的問題。有機的結合起來,你的項目纔可以長期穩定健康。