在上一篇中我們介紹了在 IPython 中使用 mpi4py,下面我們將介紹一個使用 mpi4py 實現的並行日誌工具 —— python-mpi-logger。
在介紹 python-mpi-logger 之前,我們先簡要地介紹一下日誌的概念和其作用以及 Python 標準庫提供的日誌紀錄 logging 模塊,因爲 python-mpi-logger 也是處於 logging 模塊框架之下的。
日誌(log)
日誌(log)是一種可以追蹤某些軟件運行時所發生事件的方法。軟件開發人員可以向他們的代碼中調用日誌記錄相關的方法來表明發生了某些事情。一個事件可以用一個包含可選變量數據的消息來描述。此外,事件也有重要性的概念,這個重要性也可以被稱爲嚴重性級別(level)。
通過日誌的分析,可以方便用戶瞭解系統或軟件、應用的運行情況;如果你的應用日誌足夠豐富,也可以分析以往用戶的操作行爲、類型喜好、地域分佈或其他更多信息;如果一個應用的日誌同時也分了多個級別,那麼可以很輕易地分析得到該應用的健康狀況,及時發現問題並快速定位、解決問題,補救損失。簡單來講就是,我們通過記錄和分析日誌可以瞭解一個系統或軟件程序運行情況是否正常,也可以在應用程序出現故障時快速定位問題。
日誌的作用可以簡單總結爲以下3點:
- 程序調試;
- 瞭解軟件程序運行情況,是否正常;
- 軟件程序運行故障分析與問題定位。
Python logging 模塊
Python 標準庫中的 logging 模塊定義的函數和類爲應用程序和庫的開發實現了一個靈活的事件日誌系統。logging 模塊是 Python 的一個標準庫模塊,由標準庫模塊提供日誌記錄 API 的好處是所有 Python 模塊都可以使用這個日誌記錄功能。所以,你的應用日誌可以將你自己的日誌信息與來自第三方模塊的信息整合起來。
logging 模塊包含的一些基本類有:
- Logger 對象:提供應用直接使用的日誌接口;
- Handler 對象:發送日誌紀錄(由 Logger 對象產生)到合適的目的地;
- Filter 對象:精細地控制哪些日誌紀錄會最終被輸出;
- Formatter 對象:控制最終輸出的日誌紀錄的格式。
下面給出 logging 模塊中主要類和模塊級別函數的使用接口,更詳細的介紹請參考其文檔。
Logger 類
注意:一般並不直接初始化一個 Logger 類對象,而是使用模塊級別函數 logging.getLogger(name) 來產生一個新的或者返回一個已經存在的 Logger 對象。
下面是其主要方法接口:
Logger.setLevel(level)
設定日誌等級爲 level
。那些等級比 level
低的日誌消息將會被忽略掉。下面是預定義的日誌等級及其對應的數值:
等級 | 數值 |
---|---|
CRITICAL | 50 |
ERROR | 40 |
WARNING | 30 |
INFO | 20 |
DEBUG | 10 |
NOTSET | 0 |
Logger.debug(msg, *args, **kwargs)
以 DEBUG 等級紀錄一條日誌。msg
是日誌消息的格式化字符串,args
是將會被合併到 msg
中的相關參數。一個簡單的例程如下:
FORMAT = '%(asctime)-15s %(clientip)s %(user)-8s %(message)s'
logging.basicConfig(format=FORMAT)
d = {'clientip': '192.168.0.1', 'user': 'fbloggs'}
logger = logging.getLogger('tcpserver')
logger.debug('Protocol problem: %s', 'connection reset', extra=d)
產生的輸出如下:
2006-02-08 22:20:02,165 192.168.0.1 fbloggs Protocol problem: connection reset
Logger.info(msg, *args, **kwargs)
以 INFO 等級紀錄一條日誌。參數同 debug()。
Logger.warning(msg, *args, **kwargs)
以 WARNING 等級紀錄一條日誌。參數同 debug()。
Logger.error(msg, *args, **kwargs)
以 ERROR 等級紀錄一條日誌。參數同 debug()。
Logger.critical(msg, *args, **kwargs)
以 CRITICAL 等級紀錄一條日誌。參數同 debug()。
Logger.log(lvl, msg, *args, **kwargs)
以整數值 lvl
作爲等級紀錄一條日誌。其它參數同 debug()。
Logger.addFilter(filter)
向該 Logger 添加一個 Filter 對象 filter
。
Logger.removeFilter(filter)
從該 Logger 中刪除一個 Filter 對象 filter
。
Logger.addHandler(hdlr)
向該 Logger 添加一個 Handler 對象 hdlr
。
Logger.removeHandler(hdlr)
從該 Logger 中刪除一個 Handler 對象 hdlr
。
Handler 類
注意:一般不會直接初始化一個 Handler 類對象。Handler 類的目的是爲了作爲更加有用的子類的基類,在其子類的 __init__() 方法中需要調用 Handler.__init__()。
下面是其主要方法接口:
Handler.__init__(level=NOTSET)
以等級 level
初始化一個 Handler 對象。
Handler.setLevel(level)
將該 Handler 對象的日誌等級設定爲 level
。那些等級比 level
低的日誌消息將會被忽略掉。
Handler.setFormatter(fmt)
將該 Handler 對象的 Formatter 設置爲 fmt
。
Handler.addFilter(filter)
向該 Handler 對象添加一個 Filter 對象 filter
。
Handler.removeFilter(filter)
從該 Handler 對象中刪除一個 Filter 對象 filter
。
Handler.emit(record)
在該方法中完成實際紀錄日誌的工作。子類應該具體實現該方法。
Python 標準庫中包含了若干 handler 可供直接使用,這些 handler 的列表見 logging.handlers。
模塊級別函數
下面是部分主要的模塊級別函數接口:
logging.getLogger([name])
如果 name
給定,返回一個名字爲 name
的 logger,否則返回所在 logger 等級中的根 logger。用同樣的 name
調用該函數會返回同一個 logger 對象。
logging.debug(msg[, *args[, **kwargs]])
以 DEBUG 等級紀錄一條日誌。msg
是日誌消息的格式化字符串,args
是將會被合併到 msg
中的相關參數。
logging.info(msg[, *args[, **kwargs]])
以 INFO 等級紀錄一條日誌。參數同 debug()。
logging.warning(msg[, *args[, **kwargs]])
以 WARNING 等級紀錄一條日誌。參數同 debug()。
logging.error(msg[, *args[, **kwargs]])
以 ERROR 等級紀錄一條日誌。參數同 debug()。
logging.critical(msg[, *args[, **kwargs]])
以 CRITICAL 等級紀錄一條日誌。參數同 debug()。
logging.log(level, msg[, *args[, **kwargs]])
以整數值 lvl
作爲等級紀錄一條日誌。其它參數同 debug()。
logging.basicConfig([**kwargs])
該函數會創建一個 Streamhandler 和一個默認的 Formatter 並將其加入到根 logger 上,並對此 logger 做一些基本的設置工作。如果根 logger 沒有指定一個 handler,則函數 debug(), info(), warning(), error() 和 critical() 會自動調用 basicConfig()。
python-mpi-logger
python-mpi-logger 是一個使用 mpi4py 實現的並行日誌工具,它提供了一個 MPILogHandler 類,該類是繼承自標準庫中的 logging.Handler 類的,可以在 logging 模塊中使用該 handler 安全且高效地紀錄 MPI 並行程序的日誌。MPILogHandler 類的基本工作原理是執行程序的 MPI 進程會生成一個子 MPI 進程(使用 MPI.Intracomm.Spawn 方法),該子進程會收集所有父 MPI 進程的 logging 模塊所產生的消息,並將收集到的消息寫入指定的日誌文件。
可以在這裏下載然後安裝 python-mpi-logger。下載和安裝的命令如下:
$ git clone https://github.com/jrs65/python-mpi-logger.git
$ cd python-mpi-logger
$ python setup.py install [--user]
下面是 mpilogger.MPILogHandler 類的定義及方法接口:
class MPILogHandler(logging.Handler)
MPILogHandler 類,繼承自標準庫中的 logging.Handler。
def __init__(self, logfile, comm=None, *args, **kwargs)
初始化方法,logfile
爲日誌文件,comm
爲一個 mpi4py.MPI.Intracomm 通信子,默認值爲 None,表示使用 MPI.COMM_WORLD。在此方法中會調用 comm.Spawn 方法生成一個新的子進程。
def emit(self, record):
重載 logging.Handler 類中的同名方法,在該方法中完成實際紀錄日誌的工作。具體來說,所有父 MPI 進程都將自己的日誌發送給新生成的子進程,子進程接收到後會將這些日誌寫入到 logfile
中。record
爲 LogRecord 對象,該方法將初始化該類時所用的通信子的 rank 和 size 賦值給了 record
的同名屬性,因此可以在 formatter 中使用這兩個屬性。
例程
使用 python-mpi-logger 進行並行日誌紀錄和使用標準庫中的 logging 模塊進行日誌紀錄的方法基本一樣,因爲 python-mpi-logger 只是提供了一個額外的 handler 而已,我們只需初始化一個 MPILogHandler 對象,然後將其加入到 logger 對象中就行了。下面是一個簡單的使用例程:
# mpi_logger.py
"""
Demonstrates the usage of python-mpi-logger package.
Run this with 2 processes like:
$ mpiexec -n 2 python mpi_logger.py
"""
import logging
from mpi4py import MPI
import mpilogger
# the communicator
comm = MPI.COMM_WORLD
# get the logger
logger = logging.getLogger('mpitestlogger')
# initialize a MPILogHandler object
mh = mpilogger.MPILogHandler('testlog.log', comm=comm)
# construct an MPI formatter which prints out the rank and size
mpifmt = logging.Formatter(fmt='[rank %(rank)s/%(size)s] %(asctime)s : %(message)s')
mh.setFormatter(mpifmt)
# add the handler to the logger
logger.addHandler(mh)
# iterate through and log some messages
for i in range(2):
logger.warning("Hello (times %i) from rank %i (of %i)" % (i+1, comm.rank, comm.size))
產生的日誌文件 testlog.log 中的內容如下:
[rank 0/2] 2018-11-01 21:21:02,138 : Hello (times 1) from rank 0 (of 2)
[rank 1/2] 2018-11-01 21:21:02,138 : Hello (times 1) from rank 1 (of 2)
[rank 0/2] 2018-11-01 21:21:02,187 : Hello (times 2) from rank 0 (of 2)
[rank 1/2] 2018-11-01 21:21:02,187 : Hello (times 2) from rank 1 (of 2)