最近因為一個小需求,需要保存日志到文件中。因為平時調試都只是用print,當不需要的時候又得把print刪掉,這樣很不方便,而且這樣也只能把報錯信息輸出到控制台。於是上網查了一下,python有一個內置模塊logging,用來輸出日志信息,可以進行各種配置,看了之後有種相見恨晚的感覺。下面進行一些個人的總結,主要是對自己學習進行的歸納,也希望能對你有所幫助。
首先是最簡單的使用:
# -*- coding: utf-8 -*-
import logging
logging.debug('debug級別,一般用來打印一些調試信息,級別最低')
logging.info('info級別,一般用來打印一些正常的操作信息')
logging.warning('waring級別,一般用來打印警告信息')
logging.error('error級別,一般用來打印一些錯誤信息')
logging.critical('critical級別,一般用來打印一些致命的錯誤信息,等級最高')
這樣直接就可以在控制台輸出日志信息了:
WARNING:root:waring級別,一般用來打印警告信息
ERROR:root:error級別,一般用來打印一些錯誤信息
CRITICAL:root:critical級別,一般用來打印一些致命的錯誤信息,等級最高
會發現只輸出下面三條信息,這是因為logging是分級別的,上面5個級別的信息從上到下依次遞增,可以通過設置logging的level,使其只打印某個級別以上的信息。因為默認等級是 WARNING,所以只有 WARNING 以上級別的日志被打印出來。 如果我們想把debug和info也打印出來,可以使用 basicConfig 對其進行配置:
logging.basicConfig(level=logging.DEBUG)
這樣控制台的輸出就會包含上面5條所有信息。
日志級別不是只有python才有,基本上日志都是分級別的,這樣可以讓我們在不同的時期關注不同的重點,比如我們把一些調試的信息以debug的級別輸出,並且把 logging 的 level 設為 DEBUG,這樣我們以後不需要顯示這些日志的時候,只需要把level設置為info或者更高,不用像 print 一樣要去把那條語句注釋掉或者刪掉。
我們發現上面的日志輸出信息很簡略,暫時還不能滿足我們的需求,比如我們可能需要輸出該條信息的時間,所在位置等等,這同樣可以通過basicConfig進行配置。
logging.basicConfig(format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s',
level=logging.DEBUG)
然後輸出就會是這樣的格式:
2019-07-19 15:54:26,625 - log_test.py[line:11] - DEBUG: debug級別,一般用來打印一些調試信息,級別最低
format 可以指定輸出的內容和格式,其內置的參數如下:
%(name)s:Logger的名字
%(levelno)s:打印日志級別的數值
%(levelname)s:打印日志級別的名稱
%(pathname)s:打印當前執行程序的路徑,其實就是sys.argv[0]
%(filename)s:打印當前執行程序名
%(funcName)s:打印日志的當前函數
%(lineno)d:打印日志的當前行號
%(asctime)s:打印日志的時間
%(thread)d:打印線程ID
%(threadName)s:打印線程名稱
%(process)d:打印進程ID
%(message)s:打印日志信息
此外,basicConfig 還可以進行許多其他配置,後文繼續介紹。
以上我們只是把日志輸出到控制台,但很多時候我們可能會需要把日志存到文件,這樣程序出現問題時,可以方便我們根據日志信息進行定位。 最簡單的方式是使用 basicConfig:
logging.basicConfig(format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s',
level=logging.DEBUG,
filename='test.log',
filemode='a')
只是在上面配置的基礎上加上filename
和 filemode
參數,這樣就可以把日志輸出到 test.log 文件中了,如果沒有這個文件的話會自動創建。 其中參數 filemode
表示文件打開模式,不設的話默認為’a’,即追加模式,可以不設;也可以設為’w’,每次寫日志會覆蓋之前的日志。 但是進行這樣的操作之後,我們會發現控制台不輸出了,怎麼做到既輸出到控制台又寫入到文件呢? 這需要更進一步的學習。
以上我們只是使用logging進行非常簡單的操作,但這樣作用有限,其實 logging 庫采取了模塊化的設計,提供了許多組件:記錄器、處理器、過濾器和格式化器。
簡單地說,其中 Logger 是負責記錄日志消息的,然後我們要把這些日志消息放到哪裡,交給 Handler 處理,Filter 則幫我們過濾信息(不限於通過級別過濾),Formatter 就是跟上面的 format 一個意思,用來設置日志內容和格式。
這樣,我們試一下使用模塊的方式,重新記錄日志:
logger = logging.getLogger('test')
logger.debug('debug級別,一般用來打印一些調試信息,級別最低')
logger.info('info級別,一般用來打印一些正常的操作信息')
logger.warning('waring級別,一般用來打印警告信息')
logger.error('error級別,一般用來打印一些錯誤信息')
logger.critical('critical級別,一般用來打印一些致命的錯誤信息,等級最高')
首先第一行 getLogger 獲取了一個記錄器,其中命名標識了這個 Logger。然後下面的輸出方式跟我們一開始 logging 的用法是很相似的,看起來是不是很簡單。但這樣是不行,運行後會報錯:
No handlers could be found for logger "test"
是說我們沒有為這個logger指定handler,它不知道要怎麼處理日志,要輸出到哪裡去。那我們就給他加一個Handler吧,Handler的種類有很多,常用的有4種:
現在我們先使用最簡單的StreamHandler把日志輸出到控制台:
logger = logging.getLogger('test')
stream_handler = logging.StreamHandler()
logger.addHandler(stream_handler)
...
這樣就可以在控制台看到:
waring級別,一般用來打印警告信息
error級別,一般用來打印一些錯誤信息
critical級別,一般用來打印一些致命的錯誤信息,等級最高
還是少了幾條日志,因為我們沒有設置日志級別,我們同樣設置一下級別,並且也使用Formatter模塊設置一下輸出格式。
logger = logging.getLogger('test')
logger.setLevel(level=logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')
stream_handler = logging.StreamHandler()
stream_handler.setLevel(logging.DEBUG)
stream_handler.setFormatter(formatter)
logger.addHandler(stream_handler)
...
我們發現Formatter是給handler設置的,這很好理解,因為handler是負責把日志輸出到哪裡,所以是給它設置格式,而不是給logger;那為什麼level需要設置兩次呢?給logger設置是告訴它要記錄哪些級別的日志,給handler設是告訴它要輸出哪些級別的日志,相當於進行了兩次過濾。這樣的好處在於,當我們有多個日志去向時,比如既保存到文件,又輸出到控制台,就可以分別給他們設置不同的級別;logger 的級別是先過濾的,所以被 logger 過濾的日志 handler 也是無法記錄的,這樣就可以只改 logger 的級別而影響所有輸出。兩者結合可以更方便地管理日志記錄的級別。
有了handler,我們就可以很方便地同時將日志輸出到控制台和文件:
logger = logging.getLogger('test')
logger.setLevel(level=logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')
file_handler = logging.FileHandler('test2.log')
file_handler.setLevel(level=logging.INFO)
file_handler.setFormatter(formatter)
stream_handler = logging.StreamHandler()
stream_handler.setLevel(logging.DEBUG)
stream_handler.setFormatter(formatter)
logger.addHandler(file_handler)
logger.addHandler(stream_handler)
只需要多加一個FileHandler即可。
有時候我們需要對日志文件進行分割,以方便我們的管理。python 提供了兩個處理器,方便我們分割文件:
使用方法跟上面的 Handler 類似,只是需要添加一些參數配置,比如when='D'
表示以天為周期切分文件,其他參數的意思可以參考:Python + logging 輸出到屏幕,將log日志寫入文件
from logging import handlers
time_rotating_file_handler = handlers.TimedRotatingFileHandler(filename='rotating_test.log', when='D')
time_rotating_file_handler.setLevel(logging.DEBUG)
time_rotating_file_handler.setFormatter(formatter)
logger.addHandler(time_rotating_file_handler)
若改為when='S'
,則以秒為周期進行切割,運行幾次後會生成文件:
其中沒有後綴的為最新日志文件。
參考文章: Python + logging 輸出到屏幕,將log日志寫入文件Python標准模塊–logging