網站首頁 編程語言 正文
在 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 的比例等等,
也可以在它的基礎上做更進一步的統計分析。
loguru
上面的實現方式已經是一個較為可行的配置方案了。然而,我還是會感覺到有些 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 效率可能要高上十倍了?
原文鏈接:https://segmentfault.com/a/1190000042052916
相關推薦
- 2023-03-28 react-redux及redux狀態管理工具使用詳解_React
- 2022-01-07 event的srcelement和target
- 2022-08-12 python利用winreg生成桌面路徑及實現掃描二維碼圖片返回相關信息_python
- 2022-05-06 Python學習之函數的定義與使用詳解_python
- 2022-11-15 Rust使用kind進行異常處理(錯誤的分類與傳遞)_相關技巧
- 2021-12-06 centos7.6批量增加修改刪除虛擬網卡操作介紹_Linux
- 2023-02-01 C語言結構體嵌套與對齊超詳細講解_C 語言
- 2022-12-26 Python中pywifi模塊的基本用法講解_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支