01-綜述
可以使用Python內置的logging模塊來實現Django項目的日志記錄。
所以與其說這篇文章在講Django的“日志功能-日志模塊-日志輸出”,不如說是在講Pthon的“日志功能-日志模塊-日志輸出”,即Python的logging模塊。
下面用一個實例來進行講解。
02-實例代碼及運行效果
現在我要在Django的視圖函數index()中輸出之前用print()輸出的信息。
用logging模塊改寫前的視圖函數index()的代碼如下:
def index(request):year = 2023month = 11day = 22day_of_week = 'Wednesday'print(f"Today's date is:{year}-{month}-{day}-{day_of_week}")return render(request, 'index.html') # 將渲染結果輸出到index.html模板中
在上面的代碼中,print()語句根據上面設置的相關變量值輸出下面的字符串:
Today's date is:2023-11-22-Wednesday
接下來,我們就根據Python內置的logging模塊的使用方法來將上面的這個字符串輸出到日志文件 logfile666.log 中。
首先我把完整的代碼給出來,然后再慢慢講。
視圖函數index()的完整代碼如下:
from django.shortcuts import render # 默認導入的模塊
import logging # 導入日志記錄模塊# 創建一個名為'index_log'的日志記錄器
logger01 = logging.getLogger('index_log')# Create your views here.def index(request):year = 2023month = 11day = 22day_of_week = 'Wednesday'logger01.debug(f"Today's date is:{year}-{month}-{day}-{day_of_week}")return render(request, 'index.html') # 將渲染結果輸出到index.html模板中
setting.py中添加如下代碼:
# 設置日志記錄器
LOGGING = {'version': 1,'disable_existing_loggers': True,'handlers': {'file01': {'level': 'DEBUG','class': 'logging.FileHandler','filename': 'log/logfile666.log', # 指定日志文件的路徑,相對路徑時以Django項目的根目錄為此路徑的根路徑,當然也可用絕對路徑,比如E:/log/logfile666.log},},'loggers': {'index_log': {'handlers': ['file01'],'level': 'DEBUG','propagate': False,},},
}
用下面的命令開啟Django的web服務后:
python manage.py runserver 127.0.0.1:8010
訪問URL:
http://127.0.0.1:8010/index/
發現控制臺沒有字符串:Today's date is:2023-11-22-Wednesday
的輸出。
而在目錄 BASE_DIR下出現了日志文件:logfile666.log
其內容如下:
可見,實現了我們的需求。
接下來,對上面這個實例中的相關代碼進行詳解。
03-整個例子的思路
首先用語句import logging
導入日志記錄模塊,然后利用語句logger01 = logging.getLogger('index_log')
創建一個名為index_log
的logger對象,這個logger對象的實例化變量名為logger01
。
然后使用語句logger01.debug(f"Today's date is:{year}-{month}-{day}-{day_of_week}")
進行日志信息的輸出。
語句logger01.debug(f"Today's date is:{year}-{month}-{day}-{day_of_week}")
在進行日志信息輸出時,因為這是一個名叫index_log
的logger對象,所以去調用名叫index_log
的logger的配置,具體代碼如下:
'loggers': {'index_log': {'handlers': ['file01'],'level': 'DEBUG','propagate': False,},}
而名叫index_log
的logger具體在執行日志信息的輸出時,調用的是句柄file01
,句柄file01
的設置如下:
'handlers': {'file01': {'level': 'DEBUG','class': 'logging.FileHandler','filename': 'log/logfile666.log', # 指定日志文件的路徑,相對路徑時以Django項目的根目錄為此路徑的根路徑,當然也可用絕對路徑,比如E:/log/logfile666.log},},
整個過程的大致介紹如上。
接下來對關鍵代碼進行詳細解釋。
04-視圖函數view.py中的關鍵代碼詳解
視圖函數index()的完整代碼如下:
from django.shortcuts import render # 默認導入的模塊
import logging # 導入日志記錄模塊# 創建一個名為'index_log'的日志記錄器
logger01 = logging.getLogger('index_log')# Create your views here.def index(request):year = 2023month = 11day = 22day_of_week = 'Wednesday'logger01.debug(f"Today's date is:{year}-{month}-{day}-{day_of_week}")return render(request, 'index.html') # 將渲染結果輸出到index.html模板中
第01句關鍵代碼:
logger01 = logging.getLogger('index_log')
在這里面,注意參數'index_log'
,這是我們在setting.py中設置的記錄器的名字,setting.py的相關截圖如下:
第02句關鍵代碼:
logger01.debug(f"Today's date is:{year}-{month}-{day}-{day_of_week}")
logging模塊一共有五個日志輸出方法,對應于五個日志級別,分別如下:
logger.debug() # 調試級別的日志輸出語句
logger.info() # 信息級別的日志輸出語句
logger.warning() # 警告級別的日志輸出語句
logger.error() # 錯誤級別的日志輸出語句
logger.critical() # 嚴重錯誤級別的日志輸出語句
上面這個五個日志級別的級別由低到高的順序為:
debug→info→warning→error→critical
一條日志,該用哪個級別,由用戶自己定義。
值得注意的是:
方法級別越高,那么要想輸出相應的日志信息,那么對應的logger的級別應等于或小于其級別,而logger的級別又應比相應的hander級別高才行。
舉個例子:
假如用方法logger.warning()輸出日志信息,那么logger的級別(level)可以為WARNING或比WARNING小于的INFO、DEBUG,但不能為ERROR、CRITICAL。
假如logger的級別(level)設置為INFO,那么要先想其對應的handler能最終輸出日志信息到日志文件,那么就需要handler的級別為INFO或比INFO級別小的DEBUG,但不能為WARNING、ERROR、CRITICAL。
另外,上面提到的五個日志輸出方法:
logger.debug() # 調試級別的日志輸出語句
logger.info() # 信息級別的日志輸出語句
logger.warning() # 警告級別的日志輸出語句
logger.error() # 錯誤級別的日志輸出語句
logger.critical() # 嚴重錯誤級別的日志輸出語句
其用法和print()一模一樣。
05-setting.py中對日志器的設置語句詳解
相關代碼如下:
# 設置日志記錄器
LOGGING = {'version': 1,'disable_existing_loggers': True,'handlers': {'file01': {'level': 'DEBUG','class': 'logging.FileHandler','filename': 'log/logfile666.log', # 指定日志文件的路徑,相對路徑時以Django項目的根目錄為此路徑的根路徑,當然也可用絕對路徑,比如E:/log/logfile666.log},},'loggers': {'index_log': {'handlers': ['file01'],'level': 'DEBUG','propagate': False,},},
}
第01句代碼:'disable_existing_loggers': False
'disable_existing_loggers': False,
'disable_existing_loggers'
是 Django 中配置日志的一個選項。它是一個布爾值,用于指定是否禁用已經存在的日志記錄器(loggers)。
當 'disable_existing_loggers'
設置為 True
時,Django 將禁用所有已經存在的根記錄器(root logger)和在 'loggers'
部分中未明確指定的其他記錄器。這樣可以確保日志記錄器的配置是全新的,不受之前的全局配置的影響。
而當 'disable_existing_loggers'
設置為 False
時,Django 會保留已經存在的日志記錄器,不禁用它們。這意味著在 'loggers'
部分中配置的記錄器只是添加到已經存在的記錄器列表中,而不是替換它們。這樣的配置可能會導致全局的日志配置不夠清晰,因為它們可能受到之前配置的影響。
以下是一個示例,演示了 'disable_existing_loggers'
設置為 True
和 False
時的不同行為:
LOGGING = {'version': 1,'disable_existing_loggers': True, # 或者 False'handlers': {'console': {'class': 'logging.StreamHandler',},},'loggers': {'django': {'handlers': ['console'],'level': 'INFO',},'my_app': {'handlers': ['console'],'level': 'DEBUG',},},
}
如果 'disable_existing_loggers'
設置為 True
,那么所有已經存在的根記錄器和未明確指定的其他記錄器將被禁用。只有 'django'
和 'my_app'
記錄器會生效。而如果設置為 False
,那么已經存在的記錄器仍然有效,所有的記錄器都會生效,可能受到全局配置的影響。
在這里,我們設置為False,以避免影響別的日志輸出,實踐證明,如果這里設置為True,那么除了這里設置的日志輸出,別的日志輸出全沒有了。
第02句代碼:'level': 'DEBUG'
handlers里面的level設置語句:
'level': 'DEBUG',
這個level的作用已經在本文的“04-視圖函數view.py中的關鍵代碼詳解”里進行了詳細的說明,這里就不再敘述了。
第03句代碼:'class': 'logging.FileHandler'
handlers里面的class設置語句:
'class': 'logging.FileHandler'
'class': 'logging.FileHandler'
是配置 logging
模塊中的處理器(handler)的一部分。這個配置指定了使用 FileHandler
類來處理日志消息,并將這些消息寫入到文件中。
在 Python 的 logging
模塊中,處理器是用于指定日志消息的輸出目的地的對象。FileHandler
是一種處理器(handler),它將日志消息寫入到文件中。
讓我們來詳細解釋這個配置:
'class'
: 這個鍵指定了使用的處理器類的名稱。在這里,'logging.FileHandler'
表示我們使用FileHandler
類。FileHandler
類是logging
模塊提供的標準處理器之一,專門用于將日志消息寫入到文件。
使用 FileHandler
處理器的一個典型配置可能如下:
'handlers': {'file': {'class': 'logging.FileHandler','filename': 'logfile.log', # 指定日志文件的路徑'level': 'DEBUG', # 指定處理器的日志級別},
},
在上面的配置中,我們創建了一個名為 ‘file’ 的處理器,指定了使用 FileHandler
類。配置還包括了 'filename'
鍵,用于指定日志文件的路徑,以及 'level'
鍵,用于指定處理器的日志級別。 'level'
的設置將影響處理器接受的日志消息的最低級別,低于該級別的消息將被忽略。
總體而言,'class': 'logging.FileHandler'
是用于將日志消息寫入文件的標準配置。如果你需要將日志記錄到文件中,可以使用這個配置,并根據需要添加其他相關的設置,比如 'filename'
和 'level'
。
問:除了FileHandler類,還有哪些類,請分別介紹。
在 Python 的 logging
模塊中,除了 FileHandler
類之外,還有一些其他常用的處理器類。以下是其中一些類的介紹:
-
StreamHandler:
StreamHandler
類用于將日志消息輸出到流(例如,標準輸出或標準錯誤)。可以通過配置'class': 'logging.StreamHandler'
來使用它。
'handlers': {'console': {'class': 'logging.StreamHandler','level': 'DEBUG',}, },
-
RotatingFileHandler:
RotatingFileHandler
類用于將日志消息寫入到文件,并支持日志文件的滾動(回滾),即在達到一定大小時創建新的日志文件。可以通過配置'class': 'logging.handlers.RotatingFileHandler'
來使用它。
'handlers': {'rotating_file': {'class': 'logging.handlers.RotatingFileHandler','filename': 'logfile.log','maxBytes': 1024, # 指定單個日志文件的最大大小'backupCount': 3, # 指定保留的舊日志文件數量'level': 'DEBUG',}, },
對于類logging.handlers.RotatingFileHandler,假如日志文字的名字為 logfile.log 并超過指定大小后,會創建新的日志文件,之前的日志文件會被命名為什么呢?
答:RotatingFileHandler
類在創建新的日志文件時,會為舊的日志文件添加一個后綴,以標識其順序。這個后綴通常是一個數字,表示日志文件的旋轉順序。在默認情況下,后綴從 1 開始,每次創建新的日志文件,后綴遞增。例如,假設你配置了一個
RotatingFileHandler
如下:'handlers': {'rotating_file': {'class': 'logging.handlers.RotatingFileHandler','filename': 'logfile.log','maxBytes': 1024, # 指定單個日志文件的最大大小'backupCount': 3, # 指定保留的舊日志文件數量'level': 'DEBUG',}, },
如果
logfile.log
超過了1024
字節,RotatingFileHandler
會創建一個新的日志文件,原始的logfile.log
會被重命名為logfile.log.1
,而新的日志文件將繼續使用logfile.log
的文件名。如果再次超過最大大小,會創建另一個新的日志文件
logfile.log
,而原來的logfile.log.1
會被重命名為logfile.log.2
。以此類推,舊的日志文件會依次向后移動并重命名。這樣,
backupCount
參數指定了保留的舊日志文件數量。在上面的例子中,設置為3
表示會保留最新的 3 個舊日志文件,即logfile.log.1
、logfile.log.2
、logfile.log.3
。超過這個數量后,最舊的日志文件會被刪除。 -
TimedRotatingFileHandler:
TimedRotatingFileHandler
類也用于將日志消息寫入到文件,但支持基于時間的日志文件滾動。可以通過配置'class': 'logging.handlers.TimedRotatingFileHandler'
來使用它。
'handlers': {'timed_rotating_file': {'class': 'logging.handlers.TimedRotatingFileHandler','filename': 'logfile.log','when': 'midnight', # 指定滾動周期,可以是 'midnight', 'H', 'MIDNIGHT', 'D', 'S', 'W0' 等'interval': 1, # 滾動周期的時間間隔'backupCount': 3, # 指定保留的舊日志文件數量'level': 'DEBUG',}, },
問:對于類ogging.handlers.TimedRotatingFileHandler,假如日志文字的名字為 logfile.log 并超過指定大小后,會創建新的日志文件,之前的日志文件會被命名為什么呢?請舉一個具體的例子并假設周期為天。
答:TimedRotatingFileHandler
類與RotatingFileHandler
類不同,它的日志文件滾動是基于時間的。在配置TimedRotatingFileHandler
時,你需要指定when
參數來定義滾動周期。常見的值包括'midnight'
(每天的午夜滾動)以及其他時間單位,如'H'
(每小時滾動)等。假設你的配置如下:
'handlers': {'timed_rotating_file': {'class': 'logging.handlers.TimedRotatingFileHandler','filename': 'logfile.log','when': 'midnight', # 指定滾動周期為每天的午夜'interval': 1, # 滾動周期的時間間隔'backupCount': 3, # 指定保留的舊日志文件數量'level': 'DEBUG',}, },
在這個例子中,如果
logfile.log
超過指定的大小,TimedRotatingFileHandler
會創建一個新的日志文件,并在當前日志文件名上附加日期后綴,而不是簡單地增加數字序列。每天午夜時,會創建一個新的日志文件,之前的日志文件會被重命名為帶有日期后綴的形式。例如,如果當前日期是
2023-01-01
,并且logfile.log
超過了指定的大小,那么會創建一個新的日志文件,同時原來的logfile.log
會被重命名為logfile.log.2023-01-01
。下一天(
2023-01-02
),再次創建新的日志文件,同時原來的logfile.log
會被重命名為logfile.log.2023-01-02
。依此類推,每天都會創建一個新的日志文件,并按照日期后綴的形式重命名之前的日志文件。這樣,
backupCount
參數指定了保留的舊日志文件數量,而when
和interval
參數則定義了滾動的時間周期。 -
NullHandler:
NullHandler
類用于禁用日志記錄,即將所有接收到的日志消息忽略。可以通過配置'class': 'logging.NullHandler'
來使用它。
'handlers': {'null_handler': {'class': 'logging.NullHandler',}, },
這些處理器類提供了不同的日志記錄方式和選項,可以根據項目的需求選擇合適的處理器。在配置文件中,通過 'class'
鍵來指定使用的處理器類,然后可以根據處理器類的不同,配置相應的參數。
第04句代碼:'propagate': False
'propagate': False
'propagate'
是配置日志記錄器是否傳遞日志消息給其父記錄器的一個屬性。當 'propagate'
設置為 True
時,表示該記錄器的日志消息將會傳遞給其父記錄器,而父記錄器的處理程序也將處理這些消息。如果設置為 False
,則該記錄器的消息將不會傳遞給其父記錄器。
在 Django 中,默認情況下,'propagate'
屬性被設置為 True
。這意味著除非明確地指定為 False
,否則日志消息會被傳遞給更高級別的記錄器。這種設置允許你在項目的不同部分使用不同的日志記錄器,同時確保日志消息能夠在整個應用程序中傳遞。
'propagate'
設置為 True
時,如果在記錄器中記錄了一條日志消息,它將傳遞給其父記錄器(如果有的話),以便在整個日志體系中處理。
如果你希望某個特定記錄器的日志消息不傳遞給其父記錄器,可以將 'propagate'
設置為 False
。這通常在配置多個記錄器時用于避免重復記錄相同的日志消息。
06-如何按大小或時間分割日志
這個問題已經在本篇本文第05點的第03句代碼中說得很清楚了,這里不再贅述。
07-修改代碼給每一條日志添加時間戳
Python 的 logging
模塊可以自動在每一條日志消息的前面加上時間。這是通過在日志記錄器(logger)的格式化字符串中添加時間信息來實現的。
在配置日志處理器時,你可以指定一個格式化字符串,該字符串中可以包含各種信息,包括時間。常用的時間格式占位符包括:
%asctime
: 人類可讀的時間,其具體格式由formatter
參數指定。%created
: 創建日志記錄的時間戳。%msecs
: 毫秒部分。%relativeCreated
: 日志記錄創建時的時間戳,以毫秒為單位,相對于日志系統啟動時間。%thread
: 線程ID。%levelname
: 日志級別的文本表示。
接下來我們把本文中的例子加上時間戳。
其實就是把setting.py中對日志記錄器的配置改成下面這段代碼:
# 設置日志記錄器
LOGGING = {'version': 1,'disable_existing_loggers': False,'handlers': {'file01': {'level': 'DEBUG','class': 'logging.FileHandler','filename': 'log/logfile888.log', # 指定日志文件的路徑'formatter': 'verbose', # 將格式化器設置為 'verbose'},},'loggers': {'index_log': {'handlers': ['file01'],'level': 'DEBUG','propagate': False,},},'formatters': {'verbose': {'format': '%(asctime)s - %(message)s','datefmt': '%Y-%m-%d %H:%M:%S', # 人類可讀的時間格式},},
}
在上面這段代碼中:
我添加了 'formatters'
部分,定義了一個名為 'verbose'
的格式化器,其中包含人類可讀時間的占位符 %asctime
,并通過'datefmt'
指定了具體的人類可讀的時間格式。
然后,我在 'handlers'
部分的 'file01'
處將格式化器設置為 'verbose'
。
這樣,便可以實現在每條日志的前面加上時間戳了。
值得注意的是:如果不通過'datefmt'
指定具體的格式,那么默認情況下,%asctime
使用的是計算機友好的時間格式,即包含日期和時間的完整字符串。這種格式對于機器解析是方便的,但對人類來說不夠友好。
修改完成后,我們再訪問:
http://127.0.0.1:8010/index/
就產生了日志文件:logfile888.log
其內容如下:
可見,我們成功實現了在日志前面加上時間戳的需求。
08-一個既包含有時間輸出也包含無時間輸出的logging模塊配置
在下面的代碼中:
logger_time是有時間戳輸出的logger,對應綁定的handler為handler01;
logger_no_time是無時間輸出的looger,對應綁定的handler為handler02;
# 設置日志記錄器
LOGGING = {'version': 1,'disable_existing_loggers': False,'handlers': {'handler01': {'level': 'DEBUG','class': 'logging.FileHandler','filename': 'log/logfile-time-2013-11-23-05.log', # 指定日志文件的路徑,注意,在Centos服務器中要用絕對路徑'formatter': 'verbose1', # 將格式化器設置為 'verbose1'},'handler02': {'level': 'DEBUG','class': 'logging.FileHandler','filename': 'log/logfile-no-time-2023-11-23-05.log', # 指定日志文件的路徑,注意,在Centos服務器中要用絕對路徑},},'loggers': {'logger_time': {'handlers': ['handler01'],'level': 'DEBUG','propagate': False,},'logger_no_time': {'handlers': ['handler02'],'level': 'DEBUG','propagate': False,},},'formatters': {'verbose1': {'format': '%(asctime)s - %(message)s','datefmt': '%Y-%m-%d %H:%M:%S', # 人類可讀的時間格式},},
}
在上面的代碼中:
logger_time是有時間戳輸出的logger,對應綁定的handler為handler01;
logger_no_time是無時間輸出的looger,對應綁定的handler為handler02;