Django搭建我的博客:日誌記錄

上一章學習了自動化測試,很好,如今咱們能夠絞盡腦汁寫出一份全面的測試,來保證代碼永遠健康了。html

話雖如此,可是做爲一個獨立開發者很難寫出真正全面的測試代碼。這是由於用戶在使用你的網站時可不會循規蹈矩,而是會以各類怪異的姿式瀏覽網頁、上傳數據。但這也不是壞事,用戶就是自然的測試人員,他們會很可愛的幫你找出一大堆的bug,陪你度過難眠的夜晚(伴隨着編程能力的提高)。python

如今的問題是,開發者如何得知用戶到底遇到了哪些問題?用戶們大部分都與你素昧生平,分部在世界各地。更糟糕的是,部署在線上時因爲配置了DEBUG = False,出錯時並不會出現報錯頁面,連用戶本身都不清楚究竟是哪裏有bug。git

Django給你的答案:日誌github

日誌的組成

日誌是指程序在運行過程當中,對狀態、時間、錯誤等的記錄。即把運行過程當中產生的信息輸出或保存起來,供開發者查閱。sql

Django使用Python內置的logging模塊處理日誌。關於該模塊的使用,Python文檔裏有很是詳細的討論。若是你從未用過,本文提供一個快速入門。數據庫

日誌事件的信息流程以下:django

這個圖看不懂也不要緊。之後你須要深度使用日誌時,會回來仔細研究它的。編程

一份日誌配置由LoggersHandlersFiltersFormatters四部分組成。瀏覽器

Loggers

Logger記錄器,是日誌系統的入口。它有三個重要的工做bash

  • 嚮應用程序(也就是你的項目)公開幾種方法,以便運行時記錄消息
  • 根據傳遞給Logger的消息的嚴重性,肯定出須要處理的消息
  • 將須要處理的消息傳遞給全部感興趣的處理器(Handler

每一條寫入logger的消息都是一條日誌記錄。每一條日誌記錄也包含級別,表明對應消息的嚴重程度。經常使用的級別以下:

  • DEBUG:排查故障時使用的低級別系統信息,一般開發時使用
  • INFO:通常的系統信息,並不算問題
  • WARNING:描述系統發生的小問題的信息,但一般不影響功能
  • ERROR:描述系統發生的大問題的信息,可能會致使功能不正常
  • CRITICAL:描述系統發生嚴重問題的信息,應用程序有崩潰風險

當logger處理一條消息時,會將本身的日誌級別和這條消息的日誌級別作對比。若是消息的級別匹配或者高於logger的日誌級別,它就會被進一步處理;不然這條消息就會被忽略掉。

當logger肯定了一條消息須要處理以後,會把它傳給Handler

Handlers

Handler處理器,它的主要功能是決定如何處理logger中每一條消息,好比把消息輸出到屏幕、文件或者Email中。

和logger同樣,handler也有級別的概念。若是一條日誌記錄的級別不匹配或者低於handler的日誌級別,則會被handler忽略。

一個logger能夠有多個handler,每個handler能夠有不一樣的日誌級別。這樣就能夠根據消息的重要性不一樣,來提供不一樣類型的輸出。例如,你能夠添加一個handler把ERRORCRITICAL消息發到你的Email,再添加另外一個 handler把全部的消息(包括ERRORCRITICAL消息)保存到文件裏。

Filters

Filter過濾器。在日誌記錄從logger傳到handler的過程當中,使用Filter作額外的控制。例如只容許某個特定來源的ERROR消息輸出。

Filter還被用來在日誌輸出以前對日誌記錄作修改。例如當知足必定條件時,把日誌記錄從 ERROR 降到 WARNING 級別。

Filter在logger和handler中均可以添加;多個filter能夠連接起來使用,來作多重過濾操做。

Formatters

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指明是否禁止默認配置的記錄器。這兩項一般不須要去改動,重點看下loggershandlers的配置:

  • 如前面說,一條消息首先傳遞給logger。Django中內置了幾種記錄器,好比這裏用到的Django記錄器,它會接收Django層次結構中的全部消息。而後咱們定義了須要處理DEBUG以上級別的消息,並把這些消息傳遞給名叫file的處理器。'propagate': True意思是本記錄器處理過的消息其餘處理器也能夠繼續處理。
  • 如今消息來到名叫filehandlers中了。這個處理器定義了消息處理級別仍然爲DEBUG,在class中定義將消息輸出到文件中去,文件地址爲項目目錄的logs/debug.log
  • 由於這裏沒有配置filtersformatters,所以會採用默認的設置。

須要注意的是日誌的輸出文件的目錄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:將網絡請求相關消息轉交給filemail_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()),玩出各類花樣。

關於日誌的入門介紹就到此爲止了,想深刻學習的讀者請繼續閱讀本文的參考文章:

總結

和上章相似,本章的內容也是概念偏多,但願讀者儘量去理解,最起碼要囫圇吞棗的把日誌成功移植到你的項目中去。獲取一份好的日誌,有時候遠比開發一個可有可無的新功能更重要。

比較起來博主認爲對博客項目來講,日誌比測試還重要,畢竟用戶的使用體驗是最佳的實踐。

但請不要誤會個人意思。測試和日誌就像兩兄弟,測試解決開發中的問題,日誌解決維護中的問題。有機的結合起來,你的項目纔可以長期穩定健康。


相關文章
相關標籤/搜索