天天看點

Python logging子產品切分和輪轉日志

Python logging子產品切分和輪轉日志

logging 子產品可以實作日志的輸出和寫入檔案,但實際工作中,對于日志的使用不僅限于輸出那麼簡單。

logging基本使用參考:

Python logging子產品的基本使用

logging 子產品中實作了很多日志處理的方法,可以幫我們實作日志的管理功能。

一、logging 中常用的日志處理方法和類

1. StreamHandler:logging.StreamHandler,日志輸出到流,可以是sys.stderr,sys.stdout或者檔案,這個方法通常用來将日志資訊輸出到控制台

2. FileHandler:logging.FileHandler,日志輸出到檔案,指定檔案,将日志資訊寫入到檔案中

3. BaseRotatingHandler:logging.handlers.BaseRotatingHandler,基本的日志輪轉方式,這個類是日志輪轉的基類,後面日志按時間輪轉,按大小輪轉的類都繼承于此。輪轉的意思就是保留一定數量的日志量,如設定保留7天日志,則會自動删除舊的日志,隻保留最近7天

4. RotatingHandler:logging.handlers.RotatingHandler,繼承BaseRotatingHandler,支援日志檔案按大小輪轉

5. TimeRotatingHandler:logging.handlers.TimeRotatingHandler,繼承BaseRotatingHandler,支援日志檔案按時間輪轉

6. SocketHandler:logging.handlers.SocketHandler,遠端輸出日志到TCP/IP sockets

7. DatagramHandler:logging.handlers.DatagramHandler,遠端輸出日志到UDP sockets

8. SMTPHandler:logging.handlers.SMTPHandler,遠端輸出日志到郵件位址

9. MemoryHandler:logging.handlers.MemoryHandler,日志輸出到記憶體中的指定buffer

10. HTTPHandler:logging.handlers.HTTPHandler,通過"GET"或者"POST"遠端輸出到HTTP伺服器

Python logging子產品切分和輪轉日志

二、logging 控制台輸出和檔案寫入

import logging
 
 
logger = logging.getLogger(__name__)
logger.setLevel(level=logging.INFO)
 
formatter = '%(asctime)s -<>- %(filename)s -<>- [line]:%(lineno)d -<>- %(levelname)s -<>- %(message)s'
file_handler = logging.FileHandler('log.txt')
file_handler.setLevel(level=logging.INFO)
log_formatter = logging.Formatter(formatter)
file_handler.setFormatter(log_formatter)
 
console_handler = logging.StreamHandler()
console_handler.setLevel(level=logging.INFO)
console_formatter = logging.Formatter(formatter)
console_handler.setFormatter(console_formatter)
 
logger.addHandler(file_handler)
logger.addHandler(console_handler)
 
logger.info('info')
logger.error('error')           

複制

運作結果:

2019-10-02 22:46:43,056 -<>- logging_demo.py -<>- [line]:64 -<>- INFO -<>- info
2019-10-02 22:46:43,056 -<>- logging_demo.py -<>- [line]:65 -<>- ERROR -<>- error           

複制

上面的代碼運作後,日志資訊寫入了 log.txt 檔案中,也列印到了控制台。如果日志檔案中沒有指定路徑,則生成的日志檔案與目前運作的py檔案處于同一目錄,指定了就會生成到指定的目錄下。

通過 FileHandler() 方法來定義日志寫入的檔案,日志格式,日志等級,通過 StreamHandler() 方法定義日志列印到控制台的格式和等級。

然後通過 addHandler() 方法将兩個日志處理對象添加到 logger 中,進而實作日志的列印和寫檔案。

Python logging子產品切分和輪轉日志

三、日志檔案按時間切分

import logging
from logging.handlers import TimedRotatingFileHandler
import time
 
 
logger = logging.getLogger(__name__)
logger.setLevel(level=logging.INFO)
 
formatter = '%(asctime)s -<>- %(filename)s -<>- [line]:%(lineno)d -<>- %(levelname)s -<>- %(message)s'
time_rotate_file = TimedRotatingFileHandler(filename='time_rotate', when='S', interval=2, backupCount=5)
time_rotate_file.setFormatter(logging.Formatter(formatter))
time_rotate_file.setLevel(logging.INFO)
 
console_handler = logging.StreamHandler()
console_handler.setLevel(level=logging.INFO)
console_handler.setFormatter(logging.Formatter(formatter))
 
logger.addHandler(time_rotate_file)
logger.addHandler(console_handler)
 
while True:
    logger.info('info')
    logger.error('error')
    time.sleep(1)           

複制

運作結果:

2019-10-02 22:57:00,523 -<>- logging_demo.py -<>- [line]:89 -<>- INFO -<>- info
2019-10-02 22:57:00,528 -<>- logging_demo.py -<>- [line]:90 -<>- ERROR -<>- error
2019-10-02 22:57:01,528 -<>- logging_demo.py -<>- [line]:89 -<>- INFO -<>- info
2019-10-02 22:57:01,528 -<>- logging_demo.py -<>- [line]:90 -<>- ERROR -<>- error
...           

複制

上面的代碼是無限循環,永遠也不會停止,為了示範,我将寫入檔案的日志資訊也列印到了控制台。運作代碼後,将日志寫到檔案中,每個檔案隻儲存兩秒鐘的資料,隻保留最新的5個日志檔案,檔案名是 time_rotate 加時間字元串。

使用 logging.handlers 中的 TimedRotatingFileHandler 類,可以幫助我們實作日志按時間來切分和輪轉。

在實際工作中,日志量是很大的,不可能将全部日志寫到同一個檔案中,這樣無法删除舊的日志,且這個檔案會越來越大,直到撐爆磁盤。日志按時間切分和輪轉的方式根據具體情況來定,如按月切分,保留3年,按天切分,保留30天,按小時切分,保留7天等等,這些 TimedRotatingFileHandler 都可以幫助我們實作。

TimedRotatingFileHandler 的主要參數:

1. filename:指定日志檔案的名字,會在指定的位置建立一個 filename 檔案,然後會按照輪轉數量建立對應數量的日志檔案,每個輪轉檔案的檔案名為 filename 拼接時間,預設YY-mm-DD_HH-MM-SS,可以自定義。

2. when:指定日志檔案輪轉的時間機關

S - Seconds

M - Minutes

H - Hours

D - Days

midnight - roll over at midnight

W{0-6} - roll over on a certain day; 0 - Monday

3. interval:指定日志檔案輪轉的周期,如 when='S', interval=10,表示每10秒輪轉一次,when='D', interval=7,表示每周輪轉一次。

4. backupCount:指定日志檔案保留的數量,指定一個整數,則日志檔案隻保留這麼多個,自動删除舊的檔案。

Python logging子產品切分和輪轉日志

四、日志檔案按大小切分

import logging
from logging.handlers import RotatingFileHandler
import time
 
 
logger = logging.getLogger(__name__)
logger.setLevel(level=logging.INFO)
 
formatter = '%(asctime)s -<>- %(filename)s -<>- [line]:%(lineno)d -<>- %(levelname)s -<>- %(message)s'
size_rotate_file = RotatingFileHandler(filename='size_rotate', maxBytes=1*1024, backupCount=5)
size_rotate_file.setFormatter(logging.Formatter(formatter))
size_rotate_file.setLevel(logging.INFO)
 
console_handler = logging.StreamHandler()
console_handler.setLevel(level=logging.INFO)
console_handler.setFormatter(logging.Formatter(formatter))
 
logger.addHandler(size_rotate_file)
logger.addHandler(console_handler)
 
while True:
    logger.info('info')
    logger.error('error')
    time.sleep(1)           

複制

運作結果:

2019-10-02 23:18:41,726 -<>- logging_demo.py -<>- [line]:115 -<>- INFO -<>- info
2019-10-02 23:18:41,726 -<>- logging_demo.py -<>- [line]:116 -<>- ERROR -<>- error
2019-10-02 23:18:42,727 -<>- logging_demo.py -<>- [line]:115 -<>- INFO -<>- info
2019-10-02 23:18:42,732 -<>- logging_demo.py -<>- [line]:116 -<>- ERROR -<>- error
...           

複制

運作代碼後,将日志寫到檔案中,每個檔案隻儲存 1kb 的資料,隻保留最新的5個日志檔案,檔案名是 size_rotate 加編号,編号從1開始,最新的日志永遠儲存在 size_rotate.1 中,編号越大,日志時間越靠前。

使用 logging.handlers 中的 RotatingFileHandler 類,可以幫助我們實作日志按檔案大小來切分和輪轉。

日志按時間切分和輪轉的方式根據具體情況來定,如一個檔案最多 1G,100M等,保留檔案數可以按需定義,這些 RotatingFileHandler 都可以幫助我們實作。

RotatingFileHandler 的主要參數:

1. filename:指定日志檔案的名字,會在指定的位置建立一個 filename 檔案,然後會按照輪轉數量建立對應數量的日志檔案,每個輪轉檔案的檔案名為 filename 拼接編号,編号從1開始。

2. maxBytes:設定日志檔案的大小,機關是位元組,如 1kb 是1024,1M 是 1024*1024 ,1G 是 1024*1024*1024 。

3. mode:設定檔案的寫入模式,預設 mode='a' ,即追加寫入。

4. backupCount:指定日志檔案保留的數量,指定一個整數,日志檔案隻保留這麼多個,自動删除舊的檔案。

Python logging子產品切分和輪轉日志

五、實作日志對象單例

在一個項目中,項目的代碼是分很多功能子產品的,在同一個項目中,最好保證使用的是同一個日志對象,所有日志都由同一個對象來輸出,才能保證所有日志寫到一個檔案之中,這就需要使用單例來實作。

import logging
from logging.handlers import RotatingFileHandler
from threading import Lock
 
 
class LoggerProject(object):
 
    def __init__(self):
        self.mutex = Lock()
        self.formatter = '%(asctime)s -<>- %(filename)s -<>- [line]:%(lineno)d -<>- %(levelname)s -<>- %(message)s'
 
    def _create_logger(self):
        _logger = logging.getLogger(__name__)
        _logger.setLevel(level=logging.INFO)
        return _logger
 
    def _file_logger(self):
        size_rotate_file = RotatingFileHandler(filename='size_rotate', maxBytes=1024*1024, backupCount=5)
        size_rotate_file.setFormatter(logging.Formatter(self.formatter))
        size_rotate_file.setLevel(logging.INFO)
        return size_rotate_file
 
    def _console_logger(self):
        console_handler = logging.StreamHandler()
        console_handler.setLevel(level=logging.INFO)
        console_handler.setFormatter(logging.Formatter(self.formatter))
        return console_handler
 
    def pub_logger(self):
        logger = self._create_logger()
        self.mutex.acquire()
        logger.addHandler(self._file_logger())
        logger.addHandler(self._console_logger())
        self.mutex.release()
        return logger
 
 
log_pro1 = LoggerProject()
log_pro2 = LoggerProject()
logger1 = log_pro1.pub_logger()
logger2 = log_pro2.pub_logger()
logger1.info('aaa')
logger2.info('aaa')
print('logger1: ', id(logger1))
print('logger2: ', id(logger2))
print('log_pro1: ', id(log_pro1))
print('log_pro2: ', id(log_pro2))           

複制

運作結果:

2019-10-03 00:22:18,711 -<>- logging_demo.py -<>- [line]:161 -<>- INFO -<>- aaa
2019-10-03 00:22:18,711 -<>- logging_demo.py -<>- [line]:161 -<>- INFO -<>- aaa
2019-10-03 00:22:18,711 -<>- logging_demo.py -<>- [line]:162 -<>- INFO -<>- aaa
2019-10-03 00:22:18,711 -<>- logging_demo.py -<>- [line]:162 -<>- INFO -<>- aaa
logger1:  2190132262336
logger2:  2190132262336
log_pro1:  2190132262224
log_pro2:  2190132262280           

複制

将建立 logger 對象的代碼封裝到一個類中,然後定義一個傳回 logger 對象的方法,執行個體化這個類的不同對象,id 不相同,但是通過它們調用類的方法傳回的 logger 對象,id 值是相等的,是同一個執行個體。隻是這個執行個體會在多個線程中運作,會造成線程安全問題,是以在代碼中加了鎖來避免線程安全問題。

這樣建立出來的 logger 對象已經實作單例了,如果想連類的對象也實作單例,寫一個單例裝飾器裝飾這個類就行了。

單例參考:

Python 實作單例模式

線程安全參考:

Python線程安全問題及解決方法