<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在 Python 中,一般情況下我們可能直接用自帶的 logging 模組來記錄紀錄檔,包括我之前的時候也是一樣。在使用時我們需要設定一些 Handler、Formatter 來進行一些處理,比如把紀錄檔輸出到不同的位置,或者設定一個不同的輸出格式,或者設定紀錄檔分塊和備份。但其實個人感覺 logging 用起來其實並不是那麼好用,其實主要還是設定較為繁瑣。
首先看看 logging 常見的解決方案吧,我一般會設定輸出到檔案、控制檯和 Elasticsearch。輸出到控制檯就僅僅是方便直接檢視的;輸出到檔案是方便直接儲存,保留所有歷史記錄的備份;輸出到 Elasticsearch,直接將 Elasticsearch 作為儲存和分析的中心,使用 Kibana 可以非常方便地分析和檢視執行情況。
所以在這裡我基本會對 logging 做如下的封裝寫法:
import 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 對應的內容即可:
logger = get_logger() logger.debug('this is a message')
執行結果如下:
DEBUG - 2019-10-11 22:27:35,923 - process: 99490 - logger.py - __main__ - 81 - logger - this is a message
我們看看這個定義的基本實現吧。首先這裡一些常數是用來定義 logging 模組的一些基本屬性的,比如 LOG_ENABLED 代表是否開啟紀錄檔功能,LOG_TO_ES 代表是否將紀錄檔輸出到 Elasticsearch,另外還有很多其他的紀錄檔基本設定,如 LOG_FORMAT 設定了紀錄檔每個條目輸出的基本格式,另外還有一些連線的必要資訊。這些變數可以和執行時的命令列或環境變數對接起來,可以方便地實現一些開關和設定的更換。
然後定義了這麼一個 get_logger 方法,接收一個引數 name。首先該方法拿到 name 之後,會到全域性的 loggers 變數裡面查詢,loggers 變數是一個全域性字典,如果有已經宣告過的 logger,直接將其獲取返回即可,不用再將其二次初始化。如果 loggers 裡面沒有找到 name 對應的 logger,那就進行建立即可。建立 logger 之後,可以為其新增各種對應的 Handler,如輸出到控制檯就用 StreamHandler,輸出到檔案就用 FileHandler 或 RotatingFileHandler,輸出到 Elasticsearch 就用 CMRESHandler,分別設定好對應的資訊即可。
最後呢,將新建的 logger 儲存到全域性的 loggers 裡面並返回即可,這樣如果有同名的 logger 便可以直接查詢 loggers 直接返回了。
在這裡依賴了額外的輸出到 Elasticsearch 的包,叫做 CMRESHandler,它可以支援將紀錄檔輸出到 Elasticsearch 裡面,如果要使用的話可以安裝一下:
pip install CMRESHandler
其 GitHub 地址是:https://github.com/cmanaha/python-elasticsearch-logger,具體的使用方式可以看看它的官方說明,如設定認證資訊,設定 Index 分隔資訊等等。
好,上面就是我之前常用的 logging 設定,通過如上的設定,我就可以實現將 logging 輸出到三個位置,並可以實現對應的效果。比如輸出到 Elasticsearch 之後,我就可以非常方便地使用 Kibana 來檢視當前執行情況,ERROR Log 的比例等等,
也可以在它的基礎上做更進一步的統計分析。
上面的實現方式已經是一個較為可行的設定方案了。然而,我還是會感覺到有些 Handler 配起來麻煩,尤其是新建一個專案的很多時候懶得去寫一些設定。即使是不用上文的設定,用最基本的幾行 logging 設定,像如下的通用設定:
import logging logging.basicConfig(level = logging.INFO,format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__)
我也懶得去寫,感覺並不是一個優雅的實現方式。
有需求就有動力啊,這不,就有人實現了這麼一個庫,叫做 loguru,可以將 log 的設定和使用更加簡單和方便。
下面我們來看看它到底是怎麼用的吧。
首先,這個庫的安裝方式很簡單,就用基本的 pip 安裝即可,Python 3 版本的安裝如下:
pip3 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,我們看看官方檔案,可以瞭解到通過 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 方法,再次輸出一條紀錄檔,看看結果是怎樣的。
控制檯輸出如下:
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 內容如下:
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): # An error? It's caught anyway! 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 效率可能要高上十倍了?
以上就是Python中更優雅的紀錄檔記錄方案詳解的詳細內容,更多關於Python紀錄檔記錄的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45