在 Python 中,通常狀況下咱們可能直接用自帶的 logging 模塊來記錄日誌,包括我以前的時候也是同樣。在使用時咱們須要配置一些 Handler、Formatter 來進行一些處理,好比把日誌輸出到不一樣的位置,或者設置一個不一樣的輸出格式,或者設置日誌分塊和備份。但其實我的感受 logging 用起來其實並非那麼好用,其實主要仍是配置較爲繁瑣.html
首先看看 logging 常見的解決方案吧,我通常會配置輸出到文件、控制檯和 Elasticsearch。輸出到控制檯就僅僅是方便直接查看的;輸出到文件是方便直接存儲,保留全部歷史記錄的備份;輸出到 Elasticsearch,直接將 Elasticsearch 做爲存儲和分析的中心,使用 Kibana 能夠很是方便地分析和查看運行狀況。
因此在這裏我基本會對 logging 作以下的封裝寫法:python
mport logging import sys from os import makedirs from os.path import dirname, exists from cmreslogging.handlers import CMRESHandler loggers = {} LOG_ENABLED = True # 是否開啓日誌 LOG_TO_CONSOLE = True # 是否輸出到控制檯 LOG_TO_FILE = True # 是否輸出到文件 LOG_TO_ES = True # 是否輸出到 Elasticsearch LOG_PATH = './runtime.log' # 日誌文件路徑 LOG_LEVEL = 'DEBUG' # 日誌級別 LOG_FORMAT = '%(levelname)s - %(asctime)s - process: %(process)d - %(filename)s - %(name)s - %(lineno)d - %(module)s - %(message)s' # 每條日誌輸出格式 ELASTIC_SEARCH_HOST = 'eshost' # Elasticsearch Host ELASTIC_SEARCH_PORT = 9200 # Elasticsearch Port ELASTIC_SEARCH_INDEX = 'runtime' # Elasticsearch Index Name APP_ENVIRONMENT = 'dev' # 運行環境,如測試環境仍是生產環境 def get_logger(name=None): """ get logger by name :param name: name of logger :return: logger """ global loggers if not name: name = __name__ if loggers.get(name): return loggers.get(name) logger = logging.getLogger(name) logger.setLevel(LOG_LEVEL) # 輸出到控制檯 if LOG_ENABLED and LOG_TO_CONSOLE: stream_handler = logging.StreamHandler(sys.stdout) stream_handler.setLevel(level=LOG_LEVEL) formatter = logging.Formatter(LOG_FORMAT) stream_handler.setFormatter(formatter) logger.addHandler(stream_handler) # 輸出到文件 if LOG_ENABLED and LOG_TO_FILE: # 若是路徑不存在,建立日誌文件文件夾 log_dir = dirname(log_path) if not exists(log_dir): makedirs(log_dir) # 添加 FileHandler file_handler = logging.FileHandler(log_path, encoding='utf-8') file_handler.setLevel(level=LOG_LEVEL) formatter = logging.Formatter(LOG_FORMAT) file_handler.setFormatter(formatter) logger.addHandler(file_handler) # 輸出到 Elasticsearch if LOG_ENABLED and LOG_TO_ES: # 添加 CMRESHandler es_handler = CMRESHandler(hosts=[{'host': ELASTIC_SEARCH_HOST, 'port': ELASTIC_SEARCH_PORT}], # 能夠配置對應的認證權限 auth_type=CMRESHandler.AuthType.NO_AUTH, es_index_name=ELASTIC_SEARCH_INDEX, # 一個月分一個 Index index_name_frequency=CMRESHandler.IndexNameFrequency.MONTHLY, # 額外增長環境標識 es_additional_fields={'environment': APP_ENVIRONMENT} ) es_handler.setLevel(level=LOG_LEVEL) formatter = logging.Formatter(LOG_FORMAT) es_handler.setFormatter(formatter) logger.addHandler(es_handler) # 保存到全局 loggers loggers[name] = logger return logger
定義完了怎麼使用呢?只須要使用定義的方法獲取一個 logger,而後 log 對應的內容便可:git
logger = get_logger() logger.debug('this is a message')
運行結果以下:github
DEBUG - 2019-10-11 22:27:35,923 - process: 99490 - logger.py - __main__ - 81 - logger - this is a message
咱們看看這個定義的基本實現吧。首先這裏一些常量是用來定義 logging
模塊的一些基本屬性的,好比 LOG_ENABLED 表明是否開啓日誌功能,LOG_TO_E
S 表明是否將日誌輸出到 Elasticsearch,另外還有不少其餘的日誌基本配置,如 LOG_FORMAT
配置了日誌每一個條目輸出的基本格式,另外還有一些鏈接的必要信息。這些變量能夠和運行時的命令行或環境變量對接起來,能夠方便地實現一些開關和配置的更換。api
而後定義了這麼一個 get_logger
方法,接收一個參數 name。首先該方法拿到 name 以後,會到全局的 loggers 變量裏面查找,loggers 變量是一個全局字典,若是有已經聲明過的 logger,直接將其獲取返回便可,不用再將其二次初始化。若是 loggers 裏面沒有找到 name 對應的 logger,那就進行建立便可。建立 logger 以後,能夠爲其添加各類對應的 Handler,如輸出到控制檯就用 StreamHandler,輸出到文件就用 FileHandler 或 RotatingFileHandler,輸出到 Elasticsearch 就用 CMRESHandler,分別配置好對應的信息便可。數據結構
最後呢,將新建的 logger 保存到全局的 loggers 裏面並返回便可,這樣若是有同名的 logger 即可以直接查找 loggers 直接返回了。
在這裏依賴了額外的輸出到 Elasticsearch 的包,叫作 CMRESHandler,它能夠支持將日誌輸出到 Elasticsearch 裏面,若是要使用的話能夠安裝一下:elasticsearch
pip install CMRESHandler
其 GitHub 地址是:https://github.com/cmanaha/python-elasticsearch-logger
,具體的使用方式能夠看看它的官方說明,如配置認證信息,配置 Index 分隔信息等等。
好,上面就是我以前經常使用的 logging 配置,經過如上的配置,我就能夠實現將 logging 輸出到三個位置,並能夠實現對應的效果。好比輸出到 Elasticsearch 以後,我就能夠很是方便地使用 Kibana 來查看當前運行狀況,ERROR Log 的比例等等.
也能夠在它的基礎上作更進一步的統計分析.ide
上面的實現方式已是一個較爲可行的配置方案了。然而,我仍是會感受到有些 Handler 配起來麻煩,尤爲是新建一個項目的不少時候懶得去寫一些配置。即便是不用上文的配置,用最基本的幾行 logging 配置,像以下的通用配置:測試
import logging logging.basicConfig(level = logging.INFO,format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__)
我也懶得去寫,感受並非一個優雅的實現方式。
有需求就有動力啊,這不,就有人實現了這麼一個庫,叫作 loguru,能夠將 log 的配置和使用更加簡單和方便。
下面咱們來看看它究竟是怎麼用的吧.this
首先,這個庫的安裝方式很簡單,就用基本的 pip 安裝便可,Python 3 版本的安裝以下:
pip install loguru
安裝完畢以後,咱們就能夠在項目裏使用這個 loguru 庫了.
那麼這個庫怎麼來用呢?咱們先用一個實例感覺下:
from loguru import logger logger.debug('this is a debug message')
看到了吧,不須要配置什麼東西,直接引入一個 logger,而後調用其 debug 方法便可。
在 loguru 裏面有且僅有一個主要對象,那就是 logger,loguru 裏面有且僅有一個 logger,並且它已經被提早配置了一些基礎信息,好比比較友好的格式化、文本顏色信息等等。
上面的代碼運行結果以下:
2019-10-13 22:46:12.367 | DEBUG | __main__:<module>:4 - this is a debug message
能夠看到其默認的輸出格式是上面的內容,有時間、級別、模塊名、行號以及日誌信息,不須要手動建立 logger,直接使用便可,另外其輸出仍是彩色的,看起來會更加友好。
以上的日誌信息是直接輸出到控制檯的,並無輸出到其餘的地方,若是想要輸出到其餘的位置,好比存爲文件,咱們只須要使用一行代碼聲明便可。
例如將結果同時輸出到一個 runtime.log 文件裏面,能夠這麼寫:
from loguru import logger logger.add('runtime.log') logger.debug('this is a debug')
很簡單吧,咱們也不須要再聲明一個 FileHandler 了,就一行 add 語句搞定,運行以後會發現目錄下 runtime.log 裏面一樣出現了剛剛控制檯輸出的 DEBUG 信息。
上面就是一些基本的使用,但這還遠遠不夠,下面咱們來詳細瞭解下它的一些功能模塊.
既然是日誌,那麼最多見的就是輸出到文件了。loguru 對輸出到文件的配置有很是強大的支持,好比支持輸出到多個文件,分級別分別輸出,過大建立新文件,太久自動刪除等等。
下面咱們分別看看這些怎樣來實現,這裏基本上就是 add 方法的使用介紹。由於這個 add 方法就至關於給 logger 添加了一個 Handler,它給咱們暴露了許多參數來實現 Handler 的配置,下面咱們來詳細介紹下。
首先看看它的方法定義吧:
def add( self, sink, *, level=_defaults.LOGURU_LEVEL, format=_defaults.LOGURU_FORMAT, filter=_defaults.LOGURU_FILTER, colorize=_defaults.LOGURU_COLORIZE, serialize=_defaults.LOGURU_SERIALIZE, backtrace=_defaults.LOGURU_BACKTRACE, diagnose=_defaults.LOGURU_DIAGNOSE, enqueue=_defaults.LOGURU_ENQUEUE, catch=_defaults.LOGURU_CATCH, **kwargs ): pass
看看它的源代碼,它支持這麼多的參數,如 level、format、filter、color 等等。
sink
另外咱們還注意到它有個很是重要的參數 sink,咱們看看官方文檔:https://loguru.readthedocs.io/en/stable/api/logger.html#sink
,能夠了解到經過 sink 咱們能夠傳入多種不一樣的數據結構,彙總以下:
- sink 能夠傳入一個 file 對象,例如 sys.stderr 或者 open('file.log', 'w') 均可以。
- sink 能夠直接傳入一個 str 字符串或者 pathlib.Path 對象,其實就是表明文件路徑的,若是識別到是這種類型,它會自動建立對應路徑的日誌文件並將日誌輸出進去。
- sink 能夠是一個方法,能夠自行定義輸出實現。
- sink 能夠是一個 logging 模塊的 Handler,好比 FileHandler、StreamHandler 等等,或者上文中咱們提到的 CMRESHandler 照樣也是能夠的,這樣就能夠實現自定義 Handler 的配置。
- sink 還能夠是一個自定義的類,具體的實現規範能夠參見官方文檔.
因此說,剛纔咱們所演示的輸出到文件,僅僅給它傳了一個 str 字符串路徑,他就給咱們建立了一個日誌文件,就是這個原理。format, filter, level
下面咱們再瞭解下它的其餘參數,例如 format、filter、level 等等。
其實它們的概念和格式和 logging 模塊都是基本同樣的了,例如這裏使用 format、filter、level 來規定輸出的格式:logger.add('runtime.log', format="{time} {level} {message}", filter="my_module", level="INFO")刪除sink
另外添加 sink 以後咱們也能夠對其進行刪除,至關於從新刷新並寫入新的內容。
刪除的時候根據剛剛 add 方法返回的 id 進行刪除便可,看下面的例子:from loguru import logger
trace = logger.add('runtime.log')
logger.debug('this is a debug message')
logger.remove(trace)
logger.debug('this is another debug message')
看這裏,咱們首先 add 了一個 sink,而後獲取它的返回值,賦值爲 trace。隨後輸出了一條日誌,而後將 trace 變量傳給 remove 方法,再次輸出一條日誌,看看結果是怎樣的。 控制檯輸出以下:
019-10-13 23:18:26.469 | DEBUG | main:<module>:4 - this is a debug message
2019-10-13 23:18:26.469 | DEBUG | main:<module>:6 - this is another debug message
日誌文件 runtime.log 內容以下:
2019-10-13 23:18:26.469 | DEBUG | main:<module>:4 - this is a debug message
能夠發現,在調用 remove 方法以後,確實將歷史 log 刪除了。 這樣咱們就能夠實現日誌的刷新從新寫入操做。 > rotation 配置 用了 loguru 咱們還能夠很是方便地使用 rotation 配置,好比咱們想一天輸出一個日誌文件,或者文件太大了自動分隔日誌文件,咱們能夠直接使用 add 方法的 rotation 參數進行配置。 咱們看看下面的例子:
logger.add('runtime_{time}.log', rotation="500 MB")
經過這樣的配置咱們就能夠實現每 500MB 存儲一個文件,每一個 log 文件過大就會新建立一個 log 文件。咱們在配置 log 名字時加上了一個 time 佔位符,這樣在生成時能夠自動將時間替換進去,生成一個文件名包含時間的 log 文件。 另外咱們也可使用 rotation 參數實現定時建立 log 文件,例如:
logger.add('runtime_{time}.log', rotation='00:00')
這樣就能夠實現天天 0 點新建立一個 log 文件輸出了。 另外咱們也能夠配置 log 文件的循環時間,好比每隔一週建立一個 log 文件,寫法以下:
logger.add('runtime_{time}.log', rotation='1 week')
這樣咱們就能夠實現一週建立一個 log 文件了。 > retention 配置 不少狀況下,一些很是久遠的 log 對咱們來講並無什麼用處了,它白白佔據了一些存儲空間,不清除掉就會很是浪費。retention 這個參數能夠配置日誌的最長保留時間。 好比咱們想要設置日誌文件最長保留 10 天,能夠這麼來配置:
logger.add('runtime.log', retention='10 days')
這樣 log 文件裏面就會保留最新 10 天的 log,媽媽不再用擔憂 log 沉積的問題啦。 > compression 配置 loguru 還能夠配置文件的壓縮格式,好比使用 zip 文件格式保存,示例以下:
logger.add('runtime.log', compression='zip')
這樣能夠更加節省存儲空間。 > 字符串格式化 loguru 在輸出 log 的時候還提供了很是友好的字符串格式化功能,像這樣:
logger.info('If you are using Python {}, prefer {feature} of course!', 3.6, feature='f-strings')
這樣在添加參數就很是方便了。 > Traceback 記錄 在不少狀況下,若是遇到運行錯誤,而咱們在打印輸出 log 的時候萬一不當心沒有配置好 Traceback 的輸出,頗有可能咱們就無法追蹤錯誤所在了。 但用了 loguru 以後,咱們用它提供的裝飾器就能夠直接進行 Traceback 的記錄,相似這樣的配置便可:
@logger.catch
def my_function(x, y, z):
return 1 / (x + y + z)
咱們作個測試,咱們在調用時三個參數都傳入 0,直接引起除以 0 的錯誤,看看會出現什麼狀況:
my_function(0, 0, 0)
運行完畢以後,能夠發現 log 裏面就出現了 Traceback 信息,並且給咱們輸出了當時的變量值,真的是不能再讚了!結果以下:
File "run.py", line 15, in <module>
my_function(0, 0, 0)
└ <function my_function at 0x1171dd510>
File "/private/var/py/logurutest/demo5.py", line 13, in my_function
return 1 / (x + y + z)
│ │ └ 0
│ └ 0
└ 0
ZeroDivisionError: division by zero
所以,用 loguru 能夠很是方便地實現日誌追蹤,debug 效率可能要高上十倍了? 另外 loguru 還有不少不少強大的功能,這裏就再也不一一展開講解了,更多的內容你們能夠看看 loguru 的官方文檔詳細瞭解一下:`https://loguru.readthedocs.io/en/stable/index.html` 看完以後,是時候把本身的 logging 模塊替換成 loguru 啦!