并行日志 python-mpi-logger

本文从本人简书博客同步过来

上一篇中我们介绍了在 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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章