Python Logging 模組進階篇 - logger與自動備份

在上一篇文章 Python Logging 模組入門篇,模組介紹與基本用法 中,提到了Python logging的基本用法,而本篇會介紹logging的進階用法,怎麼使用logger,以及Log的儲存與備份,最後則是如何在multi-thread或multi-process的情境下使用logging

1. Logging Logger說明與用法

1.1 Logger 是什麼?

Logger是Logging提供的模組化操作,可以讓你新增刪除Handler,每個Handler可以有各自的格式、用途,例如A Handler可以發送Email,而B Handler可以將Log存至檔案,又或是讓Logger有階層式關係,例如A Logger是A1及A2 Logger的父Logger,則A1及A2的Log都會匯集至A Logger。

Logger是logging模組中的一個class,主要提供了四個方法

1.2 Logger 範例

以下會示範,要如何建立一個logger,其中一個handler可以將log顯示在consoole上,顯示debug等級以上的log;另一個handler將log儲存至檔案,但只儲存等級info以上的log。

建立一個advanced.py,在其開頭載入模組,以及建立一個叫做advanced_logging的logger

import logging
import logging.handlers

# create logger
logger = logging.getLogger('advanced_logging')
logger.setLevel(logging.DEBUG)

建立兩個Handler,StreamHandler會將DEBUG等級以上的log輸出至console,FileHandler會將INFO等級以上的log輸出至advanced.log的檔案中,並設定兩個Handler的log格式都為formatter。

除了這兩種之外,官網還有提供許多功能的Handler

# create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

 

# create file handler and set level to info
fh = logging.FileHandler(filename='advanced.log', mode='a')
fh.setLevel(logging.INFO)

 

# create formatter
formatter = logging.Formatter(
    '%(asctime)s - %(name)-36s - %(levelname)-8s - %(message)s')

 

# add formatter to console handler and file handler
ch.setFormatter(formatter)
fh.setFormatter(formatter)

 

# add console handler and file handler to logger
logger.addHandler(ch)
logger.addHandler(fh)

接著就可以開始蒐集應用程式的log

# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')

執行後,advanced.log內容為,console則多了一條DEBUG的log

2022-06-21 07:48:39,265 - advanced_logging                     - INFO     - info message
2022-06-21 07:48:39,265 - advanced_logging                     - WARNING  - warn message
2022-06-21 07:48:39,266 - advanced_logging                     - ERROR    - error message
2022-06-21 07:48:39,266 - advanced_logging                     - CRITICAL - critical message

完整程式碼

import logging
import logging.handlers
import advanced_lib


# create logger
logger = logging.getLogger('advanced_logging')
logger.setLevel(logging.DEBUG)

# create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

# create file handler and set level to info
fh = logging.FileHandler(filename='advanced.log', mode='a')
fh.setLevel(logging.INFO)

# create formatter
formatter = logging.Formatter(
    '%(asctime)s - %(name)-36s - %(levelname)-8s - %(message)s')

# add formatter to console handler and file handler
ch.setFormatter(formatter)
fh.setFormatter(formatter)

# add console handler and file handler to logger
logger.addHandler(ch)
logger.addHandler(fh)

# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')

1.3 logger的階層

logging有支援階層式的logger,若今天建立了test的logger,test.run及test.go及test.go.gogo都是test的子孫

在logging中,子logger的log都會匯集到父logger,所以不需要再為子logger設定handler與format,只需要設定父logger的即可。以下是實際範例

銜接1.2的範例,在最下面新增

# using logging in multiple modules
import advanced_lib
 
a = advanced_lib.Auxiliary()
logger.info('created an instance of advanced_lib.Auxiliary')
logger.info('calling advanced_lib.Auxiliary.do_something')
a.do_something()
logger.info('finished advanced_lib.Auxiliary.do_something')
logger.info('calling advanced_lib.some_function()')
advanced_lib.some_function()
logger.info('done with advanced_lib.some_function()')

advanced_lib.py為

import logging

# create logger
module_logger = logging.getLogger('advanced_logging.auxiliary')


class Auxiliary:
    def __init__(self):
        self.logger = logging.getLogger('advanced_logging.auxiliary.Auxiliary')
        self.logger.info('creating an instance of Auxiliary')

    def do_something(self):
        self.logger.info('doing something')
        a = 1 + 1
        self.logger.info('done doing something')


def some_function():
    module_logger.info('received a call to "some_function"')

再次執行advanced.py後,會發現advanced.log多了以下的log

2022-06-24 07:38:24,569 - advanced_logging.auxiliary.Auxiliary - INFO     - creating an instance of Auxiliary
2022-06-24 07:38:24,570 - advanced_logging                     - INFO     - created an instance of advanced_lib.Auxiliary
2022-06-24 07:38:24,570 - advanced_logging                     - INFO     - calling advanced_lib.Auxiliary.do_something
2022-06-24 07:38:24,571 - advanced_logging.auxiliary.Auxiliary - INFO     - doing something
2022-06-24 07:38:24,572 - advanced_logging.auxiliary.Auxiliary - INFO     - done doing something
2022-06-24 07:38:24,572 - advanced_logging                     - INFO     - finished advanced_lib.Auxiliary.do_something
2022-06-24 07:38:24,572 - advanced_logging                     - INFO     - calling advanced_lib.some_function()
2022-06-24 07:38:24,573 - advanced_logging.auxiliary           - INFO     - received a call to "some_function"
2022-06-24 07:38:24,573 - advanced_logging                     - INFO     - done with advanced_lib.some_function()

2. log 自動備份

開始使用log後,如果一直寫入Log而沒有清除的話,這個檔案遲早會占滿硬碟,因此限制檔案大小,以及自動備份,若超過指定數量則會自動刪除最舊的log。

若過往的Log需要保存,則可以自行撰寫Handler,保存前先壓縮是比較好的做法,因為Log都是文字,壓縮比相當高

自動備份Log只需要使用RotatingFileHandler即可,其中以下的maxBytes即是每個log檔案的大小上限,backupCoung則是會備份Log的數量。

# Add the log message handler to the logger
import logging
import logging.handlers

 

rh = logging.handlers.RotatingFileHandler(
    'rotatingfile', maxBytes=20, backupCount=5)

 

formatter = logging.Formatter(
    '%(asctime)s - %(name)-36s - %(levelname)-8s - %(message)s')
rh.setFormatter(formatter)
logger.addHandler(rh)

 

# Log some messages
for i in range(20):
    logger.debug('i = %d' % i)

執行後會產生總共六個檔案,最新到最舊分別是: rotatingfile及rotatingfile.1 ~ rotatingfile.5

每個檔案都會有一行log,而i = 13以前的log都不存在了

2022-06-24 07:38:24,651 - advanced_logging                     - DEBUG    - i = 19

 

3. 在thread使用logging

在trhead使用logging與在模組中使用logging神似,可以善用format中的thread或threadName,更清楚的知道log是從哪個thread來的

import logging
import threading
import time

def worker(arg):
    while not arg['stop']:
        logging.debug('Hi from myfunc')
        time.sleep(0.5)

def main():
    logging.basicConfig(level=logging.DEBUG, format='%(relativeCreated)6d %(threadName)s %(message)s')
    info = {'stop': False}
    thread = threading.Thread(target=worker, args=(info,))
    thread.start()
    while True:
        try:
            logging.debug('Hello from main')
            time.sleep(0.75)
        except KeyboardInterrupt:
            info['stop'] = True
            break
    thread.join()

if __name__ == '__main__':
    main()

4. 結論

謝謝各位讀者看到這裡,以上介紹了python logging的進階用法,希望對各位在做程式日誌上有幫助,如果想更清楚了解logging,可以到logging-cookbook ,內有許多我沒有介紹的進階範例,若之後有使用到也會在寫文章分享給大家

目前介紹的功能中,個人覺得自動備份的功能相當有幫助,因為我在公司建的Server就是Log越來越大,有bug或要查些資訊,都會等比較久XD

若你有任何覺得好用的套件或是任何建議,也歡迎您在下方留言告訴我以及分享給其他人唷

若你覺得文章對你很有幫助,歡迎在網頁右方填入你的email訂閱本站唷

Leave a Comment

發佈留言必須填寫的電子郵件地址不會公開。