记录以将文件与在不同进程中运行的类实例分开

Posted

技术标签:

【中文标题】记录以将文件与在不同进程中运行的类实例分开【英文标题】:Log to separate files from class instances that run in different processes 【发布时间】:2017-10-01 01:50:04 【问题描述】:

问题

在一个主进程中,我实例化了多个并行运行方法的类实例,并且应该记录到它们自己的日志文件中。在他们完成工作之前和之后,应该将来自主进程的一些事件记录到另一个文件中。

由于在程序执行期间的任何时候都没有对同一文件的并行访问,因此我不使用队列来序列化日志事件。我只使用一个基本记录器,并为每个模块使用一个从基本记录器继承的单独记录器。

我现在的问题是并行执行其方法的类实例使用来自 utils 模块的函数。这个 utils 模块中的记录器应该记录到它所使用的类实例的文件中,据我所知,它只有在它知道记录器的正确名称的情况下才能做到这一点。


示例代码

我将实际代码简化为一个最小的工作示例,以帮助更好地理解我的问题。在主模块中,我实例化了一个名为“Main”的基本记录器,它只有一个StreamHandler,应用程序中的所有其他记录器都从该记录器继承

# Content of main.py

import logging
import multiprocessing
import time

from worker import Worker
from container import Container

logger = logging.getLogger('Main')

def setup_base_logger():
    formatter = logging.Formatter('%(asctime)s - %(name)-14s - %(levelname)8s - %(message)s')
    console_handler = logging.StreamHandler()
    console_handler.setFormatter(formatter)
    logger.addHandler(console_handler)

if __name__ == '__main__':
    multiprocessing.freeze_support()
    setup_base_logger()
    logger.warning('Starting the main program')
    container = Container([Worker(name='Worker_Nr.%d' % i) for i in range(4)])
    container.run()

Container 类在 container.py 中定义,并且只包含一个 Worker 实例列表:

# Content of container.py

import logging
import multiprocessing

logger = logging.getLogger('Main.container')

def run_worker(worker):
    worker.run()

class Container:
    def __init__(self, workers):
        self.workers = workers

    def run(self):
        logger.warning('The workers begin to run ...')
        pool = multiprocessing.Pool(processes=4, maxtasksperchild=1)
        pool.map(run_worker, self.workers)
        logger.warning('Workers finished running.')

它的任务是并行执行worker的run()方法。我使用multiprocessing.Pool,因为我需要限制使用的处理器数量。 Worker 类在模块 worker.py 中定义:

# Content of worker.py

import logging
import os
import time

import util

def configure_logger(name, logfile):
    logger = logging.getLogger(name)
    formatter = logging.Formatter('%(asctime)s - %(name)-14s - %(levelname)-8s - %(message)s')
    file_handler = logging.FileHandler(logfile, mode='w')
    file_handler.setFormatter(formatter)
    logger.addHandler(file_handler)

class Worker:
    def __init__(self, name):
        self.name = name
        self.run_time = 2
        logger_name = 'Main.worker.' + name
        configure_logger(name=logger_name, logfile=self.name + '.log')
        self.logger = logging.getLogger(logger_name)

    def __getstate__(self):
        d = self.__dict__.copy()
        if 'logger' in d:
            d['logger'] = d['logger'].name
        return d

    def __setstate__(self, d):
        if 'logger' in d:
            d['logger'] = logging.getLogger(d['logger'])
        self.__dict__.update(d)

    def run(self):
        self.logger.warning('0 is running for 1 seconds with process id 2'.format(self.name, self.run_time, os.getpid()))
        time.sleep(self.run_time)
        util.print_something(os.getpid())
        self.logger.warning(' woke up!'.format(self.name))

如果Worker 的每个实例都应该有一个日志文件,我认为Worker 需要一个记录器实例作为属性。 utils 模块如下所示:

# Content of util.py

import logging

logger = logging.getLogger('Main.util')

def print_something(s):
    print(s)
    logger.warning('%s was just printed', s)

执行 main.py 得到以下输出:

2017-05-03 11:08:05,738 - Main           -  WARNING - Starting the main program
2017-05-03 11:08:05,740 - Main.container -  WARNING - The workers begin to run ...
Worker_Nr.0 is running for 2 seconds with process id 5532
Worker_Nr.1 is running for 2 seconds with process id 17908
Worker_Nr.2 is running for 2 seconds with process id 19796
Worker_Nr.3 is running for 2 seconds with process id 10804
5532
5532 was just printed
Worker_Nr.0 woke up!
17908
19796
17908 was just printed
19796 was just printed
Worker_Nr.1 woke up!
Worker_Nr.2 woke up!
10804
10804 was just printed
Worker_Nr.3 woke up!
2017-05-03 11:08:07,941 - Main.container -  WARNING - Workers finished running.

如您所见,Worker 实例创建的日志记录缺少格式。此外,创建的日志文件没有任何内容。如果在Worker.__init__ 中添加带有configure_logger() 的格式化处理程序,这怎么可能?


我的尝试

将记录器名称传递给 utils 模块中的每个函数。这可行,但似乎过于复杂,因为 util.py 中有很多功能,并且以这种方式使用的模块更多 与多处理应用程序中的日志记录有关的类似问题通常希望从不同进程登录到同一个文件,我希望每个进程都有一个单独的日志文件

问题

    如何将在 utils 模块(可能还有其他模块)中创建的日志记录转到正确的日志文件? 从Worker 实例记录的所有内容都会以无格式的形式发送到标准输出,并且不会将任何内容写入日志文件(但它们已被创建)。为什么?

我在 Windows 7 64 位上使用 Python 3.5.1。

如果您认为在主进程中使用Queue 和日志记录线程要容易得多,那完全可以接受。我唯一关心的是日志的顺序。不过,我想我可以按照其他一些帖子中的建议对它们进行排序。

我不知所措,非常感谢任何帮助或正确方向的提示!

【问题讨论】:

【参考方案1】:

你必须重复

configure_logger(name=logger_name, logfile=self.name + '.log')

每个进程

def run(self):
    configure_logger(name=logger_name, logfile=self.name + '.log')
    ...

【讨论】:

【参考方案2】:

通过这个最小的示例,我能够重现提示您修改 Worker 类以便可以腌制的原始错误:

import logging
import multiprocessing
import time

def configure_logger(name, logfile):
    logger = logging.getLogger(name)
    formatter = logging.Formatter('%(asctime)s - %(name)-14s - %(levelname)-8s - %(message)s')
    file_handler = logging.FileHandler(logfile, mode='w')
    file_handler.setFormatter(formatter)
    logger.addHandler(file_handler)
    logger.setLevel(logging.DEBUG)

class Worker:
    def __init__(self, number):
        self.name = "worker%d" % number
        self.log_file = "%s.log" % self.name
        configure_logger(self.name, self.log_file)
        self.logger = logging.getLogger(self.name)

    def run(self):
        self.logger.info("%s is running...", self.name)
        time.sleep(1.0)
        self.logger.info("%s is exiting...", self.name)

def run_worker(worker):
    worker.run()

N = 4
workers = [Worker(n) for n in range(N)]
pool = multiprocessing.Pool(processes=N, maxtasksperchild=1)
pool.map(run_worker, workers)

这是运行此程序的异常回溯:

Traceback (most recent call last):
  File "custom.py", line 31, in <module>
    pool.map(run_worker, workers)
  File "/usr/local/Cellar/python/2.7.12_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/multiprocessing/pool.py", line 251, in map
    return self.map_async(func, iterable, chunksize).get()
  File "/usr/local/Cellar/python/2.7.12_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/multiprocessing/pool.py", line 567, in get
    raise self._value
TypeError: can't pickle thread.lock objects

解决方法不是改变Worker类的腌制方式,而是在run方法中调用logging.getLogger

class Worker:
    def __init__(self, number):
        self.name = "worker%d" % number
        self.log_file = "%s.log" % self.name
        configure_logger(self.name, self.log_file)

    def run(self):
        self.logger = logging.getLogger(self.name)
        self.logger.info("%s is running...", self.name)
        time.sleep(1.0)
        self.logger.info("%s is exiting...", self.name)

通过此更改,程序运行,并生成预期的日志文件。

【讨论】:

感谢您提出解决方案!虽然我看不出您的解决方案与我的我尝试过的内容 的第一点之间有什么区别。从get_file_logger 检索记录器的每个函数都需要记录器名称和文件作为参数,还是我弄错了什么? 我知道您为什么要更改 Worker 类的酸洗行为及其产生的错误。我用实际问题和解决方案的解释更新了解决方案。 非常感谢您进一步调查我的问题,很抱歉这么晚才回复!与此同时,我决定从每个进程登录到同一个文件。如果不将该信息传递给函数,我无法找到一种方法让 utils 模块函数登录到正确的文件。现在我仍然可以在格式化程序中使用 %(processName)s 对文件进行剪切和排序。再次感谢您帮助我!

以上是关于记录以将文件与在不同进程中运行的类实例分开的主要内容,如果未能解决你的问题,请参考以下文章

不同端口上的 Route 53 记录集

为啥发布运行的代码与在 Visual Studio 中运行不同

在与排队的用户不同的进程中运行后台作业

如何配置 nginx 以将 websocket 转发到不同的进程

swig 在 C++ 中运行的结果与在 python 中的不同

UITableViewCell 在运行时看起来与在 InterfaceBuilder 中不同(在 iPad 上)