Python 中更優雅的日志記錄方案

      網友投稿 1388 2025-04-03

      在 Python 中,一般情況下我們可能直接用自帶的 logging 模塊來記錄日志,包括我之前的時候也是一樣。在使用時我們需要配置一些 Handler、Formatter 來進行一些處理,比如把日志輸出到不同的位置,或者設置一個不同的輸出格式,或者設置日志分塊和備份。但其實個人感覺 logging 用起來其實并不是那么好用,其實主要還是配置較為繁瑣。

      常見使用

      首先看看 logging 常見的解決方案吧,我一般會配置輸出到文件、控制臺和 Elasticsearch。輸出到控制臺就僅僅是方便直接查看的;輸出到文件是方便直接存儲,保留所有歷史記錄的備份;輸出到 Elasticsearch,直接將 Elasticsearch 作為存儲和分析的中心,使用 Kibana 可以非常方便地分析和查看運行情況。

      所以在這里我基本會對 logging 做如下的封裝寫法:

      import loggingimport sysfrom os import makedirsfrom 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 HostELASTIC_SEARCH_PORT = 9200 ?# Elasticsearch PortELASTIC_SEARCH_INDEX = 'runtime' ?# Elasticsearch Index NameAPP_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?logginglogging.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 方法即可。

      Python 中更優雅的日志記錄方案

      在 loguru 里面有且僅有一個主要對象,那就是 logger,loguru 里面有且僅有一個 logger,而且它已經被提前配置了一些基礎信息,比如比較友好的格式化、文本顏色信息等等。

      上面的代碼運行結果如下:

      2019-10-13?22:46:12.367?|?DEBUG????|?__main__::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 方法,再次輸出一條日志,看看結果是怎樣的。

      控制臺輸出如下:

      2019-10-13?23:18:26.469?|?DEBUG????|?__main__::4?-?this?is?a?debug?message2019-10-13?23:18:26.469?|?DEBUG????|?__main__::6?-?this?is?another?debug?message

      日志文件 runtime.log 內容如下:

      2019-10-13?23:18:26.469?|?DEBUG????|?__main__::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.catchdef?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 ? ?my_function(0, 0, 0) └

      File "/private/var/py/logurutest/demo5.py", line 13, in my_function return 1 / (x + y + z) │ │ └ 0 │ └ 0 └ 0

      ZeroDivisireplaceString: division by zero

      因此,用 loguru 可以非常方便地實現日志追蹤,debug 效率可能要高上十倍了?

      另外 loguru 還有很多很多強大的功能,這里就不再一一展開講解了,更多的內容大家可以看看 loguru 的官方文檔詳細了解一下:https://loguru.readthedocs.io/en/stable/index.html。

      看完之后,是時候把自己的 logging 模塊替換成 loguru 啦!

      控制臺 Python

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:cad復制excel表格為什么尺寸變成黑框(excel表格復制到cad中字體顏色如何調黑白)
      下一篇:Word2013文檔中防止表格跨頁斷行有什么方法(word的表格跨頁斷行)
      相關文章
      亚洲αv在线精品糸列| 亚洲综合国产精品第一页| 精品亚洲综合久久中文字幕| 国产av无码专区亚洲国产精品| 亚洲AV无码国产精品永久一区| 亚洲国产精品成人午夜在线观看 | 亚洲无线观看国产精品| 国产亚洲精品a在线观看| AV在线亚洲男人的天堂| 亚洲毛片网址在线观看中文字幕 | 亚洲伊人久久大香线蕉影院| 亚洲福利一区二区三区| 亚洲成人免费网站| 亚洲人和日本人jizz| 亚洲国产人成在线观看| 亚洲综合色区中文字幕| 91在线亚洲综合在线| 亚洲精品动漫免费二区| 亚洲国产区男人本色| 亚洲高清无码专区视频| 国产成人亚洲综合无码| 国产国拍亚洲精品mv在线观看| 亚洲av中文无码乱人伦在线播放| 久久夜色精品国产嚕嚕亚洲av| 内射少妇36P亚洲区| 亚洲国产成人精品无码区在线网站| 亚洲免费黄色网址| 亚洲欧洲国产综合AV无码久久| 亚洲s码欧洲m码吹潮| 亚洲国产一区二区视频网站| 久久精品亚洲乱码伦伦中文| 亚洲综合国产一区二区三区| 亚洲国产精品VA在线观看麻豆| 亚洲天天在线日亚洲洲精| 亚洲国产成人在线视频| 亚洲色偷偷色噜噜狠狠99网| 国产亚洲精品欧洲在线观看| 老司机亚洲精品影视www| 久久久亚洲精品视频| 亚洲人成电影在线观看青青| 亚洲熟妇无码AV|