Python中更優(yōu)雅的日志記錄方案詳解
在 Python 中,一般情況下我們可能直接用自帶的 logging 模塊來(lái)記錄日志,包括我之前的時(shí)候也是一樣。在使用時(shí)我們需要配置一些 Handler、Formatter 來(lái)進(jìn)行一些處理,比如把日志輸出到不同的位置,或者設(shè)置一個(gè)不同的輸出格式,或者設(shè)置日志分塊和備份。但其實(shí)個(gè)人感覺(jué) logging 用起來(lái)其實(shí)并不是那么好用,其實(shí)主要還是配置較為繁瑣。
常見(jiàn)使用
首先看看 logging 常見(jiàn)的解決方案吧,我一般會(huì)配置輸出到文件、控制臺(tái)和 Elasticsearch。輸出到控制臺(tái)就僅僅是方便直接查看的;輸出到文件是方便直接存儲(chǔ),保留所有歷史記錄的備份;輸出到 Elasticsearch,直接將 Elasticsearch 作為存儲(chǔ)和分析的中心,使用 Kibana 可以非常方便地分析和查看運(yùn)行情況。
所以在這里我基本會(huì)對(duì) logging 做如下的封裝寫法:
import logging
import sys
from os import makedirs
from os.path import dirname, exists
from cmreslogging.handlers import CMRESHandler
loggers = {}
LOG_ENABLED = True # 是否開(kāi)啟日志
LOG_TO_CONSOLE = True # 是否輸出到控制臺(tái)
LOG_TO_FILE = True # 是否輸出到文件
LOG_TO_ES = True # 是否輸出到 Elasticsearch
LOG_PATH = './runtime.log' # 日志文件路徑
LOG_LEVEL = 'DEBUG' # 日志級(jí)別
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' # 運(yùn)行環(huán)境,如測(cè)試環(huán)境還是生產(chǎn)環(huán)境
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)
# 輸出到控制臺(tái)
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:
# 如果路徑不存在,創(chuàng)建日志文件文件夾
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}],
# 可以配置對(duì)應(yīng)的認(rèn)證權(quán)限
auth_type=CMRESHandler.AuthType.NO_AUTH,
es_index_name=ELASTIC_SEARCH_INDEX,
# 一個(gè)月分一個(gè) Index
index_name_frequency=CMRESHandler.IndexNameFrequency.MONTHLY,
# 額外增加環(huán)境標(biāo)識(shí)
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定義完了怎么使用呢?只需要使用定義的方法獲取一個(gè) logger,然后 log 對(duì)應(yīng)的內(nèi)容即可:
logger = get_logger()
logger.debug('this is a message')運(yùn)行結(jié)果如下:
DEBUG - 2019-10-11 22:27:35,923 - process: 99490 - logger.py - __main__ - 81 - logger - this is a message
我們看看這個(gè)定義的基本實(shí)現(xiàn)吧。首先這里一些常量是用來(lái)定義 logging 模塊的一些基本屬性的,比如 LOG_ENABLED 代表是否開(kāi)啟日志功能,LOG_TO_ES 代表是否將日志輸出到 Elasticsearch,另外還有很多其他的日志基本配置,如 LOG_FORMAT 配置了日志每個(gè)條目輸出的基本格式,另外還有一些連接的必要信息。這些變量可以和運(yùn)行時(shí)的命令行或環(huán)境變量對(duì)接起來(lái),可以方便地實(shí)現(xiàn)一些開(kāi)關(guān)和配置的更換。
然后定義了這么一個(gè) get_logger 方法,接收一個(gè)參數(shù) name。首先該方法拿到 name 之后,會(huì)到全局的 loggers 變量里面查找,loggers 變量是一個(gè)全局字典,如果有已經(jīng)聲明過(guò)的 logger,直接將其獲取返回即可,不用再將其二次初始化。如果 loggers 里面沒(méi)有找到 name 對(duì)應(yīng)的 logger,那就進(jìn)行創(chuàng)建即可。創(chuàng)建 logger 之后,可以為其添加各種對(duì)應(yīng)的 Handler,如輸出到控制臺(tái)就用 StreamHandler,輸出到文件就用 FileHandler 或 RotatingFileHandler,輸出到 Elasticsearch 就用 CMRESHandler,分別配置好對(duì)應(yīng)的信息即可。
最后呢,將新建的 logger 保存到全局的 loggers 里面并返回即可,這樣如果有同名的 logger 便可以直接查找 loggers 直接返回了。
在這里依賴了額外的輸出到 Elasticsearch 的包,叫做 CMRESHandler,它可以支持將日志輸出到 Elasticsearch 里面,如果要使用的話可以安裝一下:
pip install CMRESHandler其 GitHub 地址是:https://github.com/cmanaha/python-elasticsearch-logger,具體的使用方式可以看看它的官方說(shuō)明,如配置認(rèn)證信息,配置 Index 分隔信息等等。
好,上面就是我之前常用的 logging 配置,通過(guò)如上的配置,我就可以實(shí)現(xiàn)將 logging 輸出到三個(gè)位置,并可以實(shí)現(xiàn)對(duì)應(yīng)的效果。比如輸出到 Elasticsearch 之后,我就可以非常方便地使用 Kibana 來(lái)查看當(dāng)前運(yùn)行情況,ERROR Log 的比例等等,
也可以在它的基礎(chǔ)上做更進(jìn)一步的統(tǒng)計(jì)分析。
loguru
上面的實(shí)現(xiàn)方式已經(jīng)是一個(gè)較為可行的配置方案了。然而,我還是會(huì)感覺(jué)到有些 Handler 配起來(lái)麻煩,尤其是新建一個(gè)項(xiàng)目的很多時(shí)候懶得去寫一些配置。即使是不用上文的配置,用最基本的幾行 logging 配置,像如下的通用配置:
import logging logging.basicConfig(level = logging.INFO,format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__)
我也懶得去寫,感覺(jué)并不是一個(gè)優(yōu)雅的實(shí)現(xiàn)方式。
有需求就有動(dòng)力啊,這不,就有人實(shí)現(xiàn)了這么一個(gè)庫(kù),叫做 loguru,可以將 log 的配置和使用更加簡(jiǎn)單和方便。
下面我們來(lái)看看它到底是怎么用的吧。
安裝
首先,這個(gè)庫(kù)的安裝方式很簡(jiǎn)單,就用基本的 pip 安裝即可,Python 3 版本的安裝如下:
pip3 install loguru
安裝完畢之后,我們就可以在項(xiàng)目里使用這個(gè) loguru 庫(kù)了。
基本使用
那么這個(gè)庫(kù)怎么來(lái)用呢?我們先用一個(gè)實(shí)例感受下:
from loguru import logger
logger.debug('this is a debug message')看到了吧,不需要配置什么東西,直接引入一個(gè) logger,然后調(diào)用其 debug 方法即可。
在 loguru 里面有且僅有一個(gè)主要對(duì)象,那就是 logger,loguru 里面有且僅有一個(gè) logger,而且它已經(jīng)被提前配置了一些基礎(chǔ)信息,比如比較友好的格式化、文本顏色信息等等。
上面的代碼運(yùn)行結(jié)果如下:
2019-10-13 22:46:12.367 | DEBUG | __main__:<module>:4 - this is a debug message
可以看到其默認(rèn)的輸出格式是上面的內(nèi)容,有時(shí)間、級(jí)別、模塊名、行號(hào)以及日志信息,不需要手動(dòng)創(chuàng)建 logger,直接使用即可,另外其輸出還是彩色的,看起來(lái)會(huì)更加友好。
以上的日志信息是直接輸出到控制臺(tái)的,并沒(méi)有輸出到其他的地方,如果想要輸出到其他的位置,比如存為文件,我們只需要使用一行代碼聲明即可。
例如將結(jié)果同時(shí)輸出到一個(gè) runtime.log 文件里面,可以這么寫:
from loguru import logger
logger.add('runtime.log')
logger.debug('this is a debug')很簡(jiǎn)單吧,我們也不需要再聲明一個(gè) FileHandler 了,就一行 add 語(yǔ)句搞定,運(yùn)行之后會(huì)發(fā)現(xiàn)目錄下 runtime.log 里面同樣出現(xiàn)了剛剛控制臺(tái)輸出的 DEBUG 信息。
上面就是一些基本的使用,但這還遠(yuǎn)遠(yuǎn)不夠,下面我們來(lái)詳細(xì)了解下它的一些功能模塊。
詳細(xì)使用
既然是日志,那么最常見(jiàn)的就是輸出到文件了。loguru 對(duì)輸出到文件的配置有非常強(qiáng)大的支持,比如支持輸出到多個(gè)文件,分級(jí)別分別輸出,過(guò)大創(chuàng)建新文件,過(guò)久自動(dòng)刪除等等。
下面我們分別看看這些怎樣來(lái)實(shí)現(xiàn),這里基本上就是 add 方法的使用介紹。因?yàn)檫@個(gè) add 方法就相當(dāng)于給 logger 添加了一個(gè) Handler,它給我們暴露了許多參數(shù)來(lái)實(shí)現(xiàn) Handler 的配置,下面我們來(lái)詳細(xì)介紹下。
首先看看它的方法定義吧:
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看看它的源代碼,它支持這么多的參數(shù),如 level、format、filter、color 等等。
sink
另外我們還注意到它有個(gè)非常重要的參數(shù) sink,我們看看官方文檔,可以了解到通過(guò) sink 我們可以傳入多種不同的數(shù)據(jù)結(jié)構(gòu),匯總?cè)缦拢?/p>
•sink 可以傳入一個(gè) file 對(duì)象,例如 sys.stderr 或者 open('file.log', 'w') 都可以。
•sink 可以直接傳入一個(gè) str 字符串或者 pathlib.Path 對(duì)象,其實(shí)就是代表文件路徑的,如果識(shí)別到是這種類型,它會(huì)自動(dòng)創(chuàng)建對(duì)應(yīng)路徑的日志文件并將日志輸出進(jìn)去。
•sink 可以是一個(gè)方法,可以自行定義輸出實(shí)現(xiàn)。
•sink 可以是一個(gè) logging 模塊的 Handler,比如 FileHandler、StreamHandler 等等,或者上文中我們提到的 CMRESHandler 照樣也是可以的,這樣就可以實(shí)現(xiàn)自定義 Handler 的配置。
•sink 還可以是一個(gè)自定義的類,具體的實(shí)現(xiàn)規(guī)范可以參見(jiàn)官方文檔。
所以說(shuō),剛才我們所演示的輸出到文件,僅僅給它傳了一個(gè) str 字符串路徑,他就給我們創(chuàng)建了一個(gè)日志文件,就是這個(gè)原理。
format、filter、level
下面我們?cè)倭私庀滤钠渌麉?shù),例如 format、filter、level 等等。
其實(shí)它們的概念和格式和 logging 模塊都是基本一樣的了,例如這里使用 format、filter、level 來(lái)規(guī)定輸出的格式:
logger.add('runtime.log', format="{time} {level} {message}", filter="my_module", level="INFO")刪除 sink
另外添加 sink 之后我們也可以對(duì)其進(jìn)行刪除,相當(dāng)于重新刷新并寫入新的內(nèi)容。
刪除的時(shí)候根據(jù)剛剛 add 方法返回的 id 進(jìn)行刪除即可,看下面的例子:
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 了一個(gè) sink,然后獲取它的返回值,賦值為 trace。隨后輸出了一條日志,然后將 trace 變量傳給 remove 方法,再次輸出一條日志,看看結(jié)果是怎樣的。
控制臺(tái)輸出如下:
2019-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 內(nèi)容如下:
2019-10-13 23:18:26.469 | DEBUG | __main__:<module>:4 - this is a debug message
可以發(fā)現(xiàn),在調(diào)用 remove 方法之后,確實(shí)將歷史 log 刪除了。
這樣我們就可以實(shí)現(xiàn)日志的刷新重新寫入操作。
rotation 配置
用了 loguru 我們還可以非常方便地使用 rotation 配置,比如我們想一天輸出一個(gè)日志文件,或者文件太大了自動(dòng)分隔日志文件,我們可以直接使用 add 方法的 rotation 參數(shù)進(jìn)行配置。
我們看看下面的例子:
logger.add('runtime_{time}.log', rotation="500 MB")通過(guò)這樣的配置我們就可以實(shí)現(xiàn)每 500MB 存儲(chǔ)一個(gè)文件,每個(gè) log 文件過(guò)大就會(huì)新創(chuàng)建一個(gè) log 文件。我們?cè)谂渲?log 名字時(shí)加上了一個(gè) time 占位符,這樣在生成時(shí)可以自動(dòng)將時(shí)間替換進(jìn)去,生成一個(gè)文件名包含時(shí)間的 log 文件。
另外我們也可以使用 rotation 參數(shù)實(shí)現(xiàn)定時(shí)創(chuàng)建 log 文件,例如:
logger.add('runtime_{time}.log', rotation='00:00')這樣就可以實(shí)現(xiàn)每天 0 點(diǎn)新創(chuàng)建一個(gè) log 文件輸出了。
另外我們也可以配置 log 文件的循環(huán)時(shí)間,比如每隔一周創(chuàng)建一個(gè) log 文件,寫法如下:
logger.add('runtime_{time}.log', rotation='1 week')這樣我們就可以實(shí)現(xiàn)一周創(chuàng)建一個(gè) log 文件了。
retention 配置
很多情況下,一些非常久遠(yuǎn)的 log 對(duì)我們來(lái)說(shuō)并沒(méi)有什么用處了,它白白占據(jù)了一些存儲(chǔ)空間,不清除掉就會(huì)非常浪費(fèi)。retention 這個(gè)參數(shù)可以配置日志的最長(zhǎng)保留時(shí)間。
比如我們想要設(shè)置日志文件最長(zhǎng)保留 10 天,可以這么來(lái)配置:
logger.add('runtime.log', retention='10 days')這樣 log 文件里面就會(huì)保留最新 10 天的 log,媽媽再也不用擔(dān)心 log 沉積的問(wèn)題啦。
compression 配置
loguru 還可以配置文件的壓縮格式,比如使用 zip 文件格式保存,示例如下:
logger.add('runtime.log', compression='zip')這樣可以更加節(jié)省存儲(chǔ)空間。
字符串格式化
loguru 在輸出 log 的時(shí)候還提供了非常友好的字符串格式化功能,像這樣:
logger.info('If you are using Python {}, prefer {feature} of course!', 3.6, feature='f-strings')這樣在添加參數(shù)就非常方便了。
Traceback 記錄
在很多情況下,如果遇到運(yùn)行錯(cuò)誤,而我們?cè)诖蛴≥敵?log 的時(shí)候萬(wàn)一不小心沒(méi)有配置好 Traceback 的輸出,很有可能我們就沒(méi)法追蹤錯(cuò)誤所在了。
但用了 loguru 之后,我們用它提供的裝飾器就可以直接進(jìn)行 Traceback 的記錄,類似這樣的配置即可:
@logger.catch
def my_function(x, y, z):
# An error? It's caught anyway!
return 1 / (x + y + z)我們做個(gè)測(cè)試,我們?cè)谡{(diào)用時(shí)三個(gè)參數(shù)都傳入 0,直接引發(fā)除以 0 的錯(cuò)誤,看看會(huì)出現(xiàn)什么情況:
my_function(0, 0, 0)運(yùn)行完畢之后,可以發(fā)現(xiàn) log 里面就出現(xiàn)了 Traceback 信息,而且給我們輸出了當(dāng)時(shí)的變量值,真的是不能再贊了!結(jié)果如下:
> 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 可以非常方便地實(shí)現(xiàn)日志追蹤,debug 效率可能要高上十倍了?
以上就是Python中更優(yōu)雅的日志記錄方案詳解的詳細(xì)內(nèi)容,更多關(guān)于Python日志記錄的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
torch.utils.data.DataLoader與迭代器轉(zhuǎn)換操作
這篇文章主要介紹了torch.utils.data.DataLoader與迭代器轉(zhuǎn)換操作,文章內(nèi)容接受非常詳細(xì),對(duì)正在學(xué)習(xí)或工作的你有一定的幫助,需要的朋友可以參考一下2022-02-02
提升Python項(xiàng)目整潔度使用import?linter實(shí)例探究
在復(fù)雜的Python項(xiàng)目中,良好的代碼組織結(jié)構(gòu)是維護(hù)性和可讀性的關(guān)鍵,本文將深入研究?import-linter?工具,它是一個(gè)強(qiáng)大的靜態(tài)分析工具,旨在優(yōu)化項(xiàng)目的模塊導(dǎo)入,提高代碼質(zhì)量和可維護(hù)性2024-01-01
Python3自動(dòng)安裝第三方庫(kù),跟pip說(shuō)再見(jiàn)
很多朋友私信小編Python安裝第三方庫(kù)安裝技巧,在這就不一一回復(fù)大家了,今天小編給大家分享一篇教程關(guān)于Python自動(dòng)安裝第三方庫(kù)的小技巧,本文以安裝plotly為例給大家詳細(xì)講解,感興趣的朋友跟隨小編一起看看吧2021-10-10
關(guān)于Python中zipfile壓縮包模塊的使用
這篇文章主要介紹了關(guān)于Python中zipfile壓縮包模塊的使用,zipfile?模塊提供了創(chuàng)建、讀取、寫入、添加及列出?ZIP?文件的工具,本文做一個(gè)簡(jiǎn)單的總結(jié),需要的朋友可以參考下2023-04-04
詳解利用Pandas求解兩個(gè)DataFrame的差集,交集,并集
這篇文章主要和大家講解一下如何利用Pandas函數(shù)求解兩個(gè)DataFrame的差集、交集、并集,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2022-07-07
詳解Python網(wǎng)絡(luò)爬蟲功能的基本寫法
這篇文章主要介紹了Python網(wǎng)絡(luò)爬蟲功能的基本寫法,網(wǎng)絡(luò)爬蟲,即Web Spider,是一個(gè)很形象的名字。把互聯(lián)網(wǎng)比喻成一個(gè)蜘蛛網(wǎng),那么Spider就是在網(wǎng)上爬來(lái)爬去的蜘蛛,對(duì)網(wǎng)絡(luò)爬蟲感興趣的朋友可以參考本文2016-01-01

