使用多处理疯狂记录

时间:2013-12-02 15:54:52

标签: python logging multiprocessing

我试图在多处理场景中使用python的默认日志记录模块。 我读过:

  1. Python MultiProcess, Logging, Various Classes
  2. Logging using multiprocessing
  3. 和其他关于多处理,日志记录,python类等的多篇文章。 在所有这些阅读之后,我来到这段代码,我无法正常运行使用python的logutils QueueHandler:

    import sys
    import logging
    from logging import INFO
    from multiprocessing import Process, Queue as mpQueue
    import threading
    import time
    
    from logutils.queue import QueueListener, QueueHandler
    
    
    class Worker(Process):
    
        def __init__(self, n, q):
            super(Worker, self).__init__()
            self.n = n
            self.queue = q
    
            self.qh = QueueHandler(self.queue)
            self.root = logging.getLogger()
            self.root.addHandler(self.qh)
            self.root.setLevel(logging.DEBUG)        
            self.logger = logging.getLogger("W%i"%self.n)
    
    
        def run(self):
            self.logger.info("Worker %i Starting"%self.n)
    
            for i in xrange(10):
                self.logger.log(INFO, "testing %i"%i)
    
            self.logger.log(INFO, "Completed %i"%self.n)
    
    
    def listener_process(queue):
        while True:
            try:
                record = queue.get()
                if record is None:
                    break
                logger = logging.getLogger(record.name)
                logger.handle(record)
            except (KeyboardInterrupt, SystemExit):
                raise
            except:
                import sys, traceback
                print >> sys.stderr, 'Whoops! Problem:'
                traceback.print_exc(file=sys.stderr)
    
    if __name__ == "__main__":
    
        mpq = mpQueue(-1)
    
        root = logging.getLogger()
        h = logging.StreamHandler()    
        f = logging.Formatter('%(asctime)s %(processName)-10s %(name)s %(levelname)-8s     %(message)s')
        h.setFormatter(f)
        root.addHandler(h)
    
        l = logging.getLogger("Test")
        l.setLevel(logging.DEBUG)
    
        listener = Process(target=listener_process,
                           args=(mpq,))
        listener.start()
        workers=[]
        for i in xrange(1):
            worker = Worker(i, mpq)
            worker.daemon = True
            worker.start()
            workers.append(worker)
    
        for worker in workers:
            worker.join()
    
        mpq.put_nowait(None)
        listener.join()
    
        for i in xrange(10):
            l.info("testing %i"%i)
    
        print "Finish"
    

    如果执行代码,输出会以某种方式重复以下行:

    2013-12-02 16:44:46,002 Worker-2   W0 INFO         Worker 0 Starting
    2013-12-02 16:44:46,003 Worker-2   W0 INFO         testing 0
    2013-12-02 16:44:46,003 Worker-2   W0 INFO         testing 1
    2013-12-02 16:44:46,003 Worker-2   W0 INFO         testing 2
    2013-12-02 16:44:46,002 Worker-2   W0 INFO         Worker 0 Starting
    2013-12-02 16:44:46,003 Worker-2   W0 INFO         testing 3
    2013-12-02 16:44:46,003 Worker-2   W0 INFO         testing 0
    2013-12-02 16:44:46,003 Worker-2   W0 INFO         testing 1
    2013-12-02 16:44:46,003 Worker-2   W0 INFO         testing 4
    2013-12-02 16:44:46,003 Worker-2   W0 INFO         testing 2
    2013-12-02 16:44:46,003 Worker-2   W0 INFO         testing 3
    2013-12-02 16:44:46,003 Worker-2   W0 INFO         testing 5
    2013-12-02 16:44:46,003 Worker-2   W0 INFO         testing 4
    2013-12-02 16:44:46,003 Worker-2   W0 INFO         testing 6
    2013-12-02 16:44:46,003 Worker-2   W0 INFO         testing 5
    2013-12-02 16:44:46,004 Worker-2   W0 INFO         testing 7
    2013-12-02 16:44:46,003 Worker-2   W0 INFO         testing 6
    2013-12-02 16:44:46,004 Worker-2   W0 INFO         testing 8
    2013-12-02 16:44:46,004 Worker-2   W0 INFO         testing 7
    2013-12-02 16:44:46,004 Worker-2   W0 INFO         testing 9
    2013-12-02 16:44:46,004 Worker-2   W0 INFO         testing 8
    2013-12-02 16:44:46,004 Worker-2   W0 INFO         Completed 0
    2013-12-02 16:44:46,004 Worker-2   W0 INFO         testing 9
    2013-12-02 16:44:46,004 Worker-2   W0 INFO         Completed 0
    2013-12-02 16:44:46,005 MainProcess Test INFO         testing 0
    2013-12-02 16:44:46,005 MainProcess Test INFO         testing 1
    2013-12-02 16:44:46,005 MainProcess Test INFO         testing 2
    2013-12-02 16:44:46,005 MainProcess Test INFO         testing 3
    2013-12-02 16:44:46,005 MainProcess Test INFO         testing 4
    2013-12-02 16:44:46,005 MainProcess Test INFO         testing 5
    2013-12-02 16:44:46,006 MainProcess Test INFO         testing 6
    2013-12-02 16:44:46,006 MainProcess Test INFO         testing 7
    2013-12-02 16:44:46,006 MainProcess Test INFO         testing 8
    2013-12-02 16:44:46,006 MainProcess Test INFO         testing 9
    Finish
    

    在其他问题中,建议处理程序多次添加,但是,正如您所看到的,我只在 main 方法中添加了一次streamhanlder。 我已经测试过将 main 方法嵌入到具有相同结果的类中。

    编辑: 正如@max建议的那样(或者我相信他说的话)我已将工人类的代码修改为:

    class Worker(Process):
    
        root = logging.getLogger()
        qh = None
    
        def __init__(self, n, q):
            super(Worker, self).__init__()
            self.n = n
            self.queue = q
    
            if not self.qh:
                Worker.qh = QueueHandler(self.queue)            
                Worker.root.addHandler(self.qh)
                Worker.root.setLevel(logging.DEBUG)
    
            self.logger = logging.getLogger("W%i"%self.n)
    
            print self.root.handlers
    
        def run(self):
            self.logger.info("Worker %i Starting"%self.n)
    
            for i in xrange(10):
                self.logger.log(INFO, "testing %i"%i)
    
            self.logger.log(INFO, "Completed %i"%self.n)
    

    使用相同的结果,现在不会一次又一次地添加队列处理程序,但仍然存在重复的日志条目,即使只有一个工作程序也是如此。

    EDIT2: 我已经改变了一点代码。我改变了监听程序进程,现在使用了QueueListener(这就是我在开始时的意图),将主代码移动到了一个类。

    import sys
    
    import logging
    from logging import INFO
    from multiprocessing import Process, Queue as mpQueue
    import threading
    import time
    
    from logutils.queue import QueueListener, QueueHandler
    
    root = logging.getLogger()
    added_qh = False
    
    class Worker(Process):
    
        def __init__(self, logconf, n, qh):
            super(Worker, self).__init__()
            self.n = n
            self.logconf = logconf
    
    #        global root
            global added_qh
    
            if not added_qh:
                added_qh = True
                root.addHandler(qh)
                root.setLevel(logging.DEBUG)            
    
            self.logger = logging.getLogger("W%i"%self.n)
    
            #print root.handlers
    
        def run(self):
            self.logger.info("Worker %i Starting"%self.n)
    
            for i in xrange(10):
                self.logger.log(INFO, "testing %i"%i)
    
            self.logger.log(INFO, "Completed %i"%self.n)
    
    
    class Main(object):
    
        def __init__(self):
            pass
    
        def start(self):
    
            mpq = mpQueue(-1)
            qh = QueueHandler(mpq)
    
            h = logging.StreamHandler()
    
            ql = QueueListener(mpq, h)
    
            #h.setFormatter(f)
            root.addHandler(qh)
    
            l = logging.getLogger("Test")
            l.setLevel(logging.DEBUG)
    
            workers=[]
    
            for i in xrange(15):
                worker = Worker(logconf, i, qh)
                worker.daemon = True
                worker.start()
                workers.append(worker)
    
            for worker in workers:
                print "joining worker: {}".format(worker)
                worker.join()
    
            mpq.put_nowait(None)
    
            ql.start()
    
            # listener.join()
    
            for i in xrange(10):
                l.info("testing %i"%i)
    
    if __name__ == "__main__":
    
    
        x = Main()
        x.start()
    
        time.sleep(10)
    
        print "Finish"
    

    现在 一直工作,直到我达到一定数量的工人(~15),因为某些原因,主要课程在de join中被阻止而其他工作人员什么都不做。

3 个答案:

答案 0 :(得分:3)

我迟到了,所以你可能不再需要答案了。问题来自于您已经在主进程中设置了处理程序,并且在您的工作程序中添加了另一个处理程序。这意味着在您的工作进程中,两个处理程序实际上是在管理您的数据,一个是将日志推送到队列,另一个是写入流。

您可以通过在代码中添加额外的行self.root.handlers = []来解决此问题。从原始代码中,worker的__init__方法如下所示:

def __init__(self, n, q):
    super(Worker, self).__init__()
    self.n = n
    self.queue = q

    self.qh = QueueHandler(self.queue)
    self.root = logging.getLogger()
    self.root.handlers = []
    self.root.addHandler(self.qh)
    self.root.setLevel(logging.DEBUG)
    self.logger = logging.getLogger("W%i"%self.n)

输出现在看起来像这样:

python workers.py 
2016-05-12 10:07:02,971 Worker-2   W0 INFO         Worker 0 Starting
2016-05-12 10:07:02,972 Worker-2   W0 INFO         testing 0
2016-05-12 10:07:02,973 Worker-2   W0 INFO         testing 1
2016-05-12 10:07:02,973 Worker-2   W0 INFO         testing 2
2016-05-12 10:07:02,973 Worker-2   W0 INFO         testing 3
2016-05-12 10:07:02,973 Worker-2   W0 INFO         testing 4
2016-05-12 10:07:02,973 Worker-2   W0 INFO         testing 5
2016-05-12 10:07:02,973 Worker-2   W0 INFO         testing 6
2016-05-12 10:07:02,973 Worker-2   W0 INFO         testing 7
2016-05-12 10:07:02,973 Worker-2   W0 INFO         testing 8
2016-05-12 10:07:02,973 Worker-2   W0 INFO         testing 9
2016-05-12 10:07:02,973 Worker-2   W0 INFO         Completed 0
Finish

答案 1 :(得分:3)

我想出了一个使用monkeypatching的非常简单的解决方法。它可能不健壮,我不是日志模块的专家,但它似乎是我的情况的最佳解决方案。在尝试了一些代码更改后(为了能够传入现有的记录器,来自multiprocess.get_logger()),我不知道代码的变化有多快,并想出了一个快速的(好吧,如果有的话,我可以首先完成此任务)易于阅读黑客/解决方法:

(工作示例,完成多处理池)

import logging
import multiprocessing

class FakeLogger(object):
    def __init__(self, q):
        self.q = q
    def info(self, item):
        self.q.put('INFO - {}'.format(item))
    def debug(self, item):
        self.q.put('DEBUG - {}'.format(item))
    def critical(self, item):
        self.q.put('CRITICAL - {}'.format(item))
    def warning(self, item):
        self.q.put('WARNING - {}'.format(item))

def some_other_func_that_gets_logger_and_logs(num):
    # notice the name get's discarded
    # of course you can easily add this to your FakeLogger class
    local_logger = logging.getLogger('local')
    local_logger.info('Hey I am logging this: {} and working on it to make this {}!'.format(num, num*2))
    local_logger.debug('hmm, something may need debugging here')
    return num*2

def func_to_parallelize(data_chunk):
    # unpack our args
    the_num, logger_q = data_chunk
    # since we're now in a new process, let's monkeypatch the logging module
    logging.getLogger = lambda name=None: FakeLogger(logger_q)
    # now do the actual work that happens to log stuff too
    new_num = some_other_func_that_gets_logger_and_logs(the_num)
    return (the_num, new_num)

if __name__ == '__main__':
    multiprocessing.freeze_support()
    m = multiprocessing.Manager()
    logger_q = m.Queue()
    # we have to pass our data to be parallel-processed
    # we also need to pass the Queue object so we can retrieve the logs
    parallelable_data = [(1, logger_q), (2, logger_q)]
    # set up a pool of processes so we can take advantage of multiple CPU cores
    pool_size = multiprocessing.cpu_count() * 2
    pool = multiprocessing.Pool(processes=pool_size, maxtasksperchild=4)
    worker_output = pool.map(func_to_parallelize, parallelable_data)
    pool.close() # no more tasks
    pool.join()  # wrap up current tasks
    # get the contents of our FakeLogger object
    while not logger_q.empty():
        print logger_q.get()
    print 'worker output contained: {}'.format(worker_output)

当然,这可能不会涵盖logging使用的全部范围,但我认为这个概念很简单,可以快速,轻松地完成工作。它应该很容易修改(例如lambda func丢弃可以传递给getLogger的可能前缀)。

答案 2 :(得分:0)

所有Worker共享相同的根记录器对象(在Worker.__init__中获得 - getLogger调用始终返回相同的记录器)。但是,每次创建Worker时,都会向该记录器添加处理程序(QueueHandler)。

因此,如果您创建10个Worker,您的根记录器上将有10个(相同的)处理程序,这意味着输出重复10次。

相反,您应该使记录器成为模块属性而不是实例属性,并在模块级别配置一次 - 而不是在类级别。

(实际上,记录器应在程序级别配置一次)