在python中实现多个线程的自定义输出日志记录

时间:2016-12-01 11:41:07

标签: python multithreading logging

我在Python多线程方面的实践相当差。那么,现在,我正在研究如何从多个线程获取日志信息。我看到了很多不同的方法,但我想从简单的方法开始。因此,任务是创建多个线程并从每个线程中记录数据。为了识别日志源,我想在日志输出中添加一些自定义标记。我知道log lib有一个到达LogRecord属性(thread,threadName等),它运行良好。所以,我有一些例子(logging-from-multiple-threads)并做了一些修改。这是一个完整的代码:

import logging
import threading
import time

logger = logging.getLogger()
syslog = logging.StreamHandler()
formatter = logging.Formatter('%(project)s : %(thread)x '
                              '%(levelname)-8s '
                              '%(message)s')
syslog.setFormatter(formatter)
logger.setLevel(logging.DEBUG)
logger.addHandler(syslog)


class ContextFilter(logging.Filter):

    def __init__(self, project):
        super(ContextFilter, self).__init__()
        self.project = project

    def filter(self, record):
        record.project = self.project
        return True


def worker(args):
    while not args['stop']:
        logging.debug('Hi from {}'.format(args['project']))
        time.sleep(0.5)


def main():
    projects = ['project_1', 'project_2']
    info = {'stop': False}
    threads = []
    for project in projects:
        info['project'] = project
        logger.addFilter(ContextFilter(project))
        thread = threading.Thread(target=worker, args=(info,))
        thread.start()
        threads.append(thread)
    while True:
        try:
            logging.debug('Hello from main')
            time.sleep(1.75)
        except KeyboardInterrupt:
            info['stop'] = True
            break
    for t in threads:
        t.join()

if __name__ == '__main__':
    main()

以下是输出结果:

project_2 : 7fa627e77700 DEBUG    Hi from project_2
project_2 : 7fa6293d0700 DEBUG    Hello from main
project_2 : 7fa627676700 DEBUG    Hi from project_2
project_2 : 7fa627e77700 DEBUG    Hi from project_2
project_2 : 7fa627676700 DEBUG    Hi from project_2
project_2 : 7fa627e77700 DEBUG    Hi from project_2
project_2 : 7fa627676700 DEBUG    Hi from project_2
project_2 : 7fa627e77700 DEBUG    Hi from project_2
project_2 : 7fa627676700 DEBUG    Hi from project_2
project_2 : 7fa6293d0700 DEBUG    Hello from main
project_2 : 7fa627e77700 DEBUG    Hi from project_2

实际上,这不是我所期待的。你能告诉我一些我做错的事吗?

2 个答案:

答案 0 :(得分:2)

你到底想要什么?如果你想知道为什么"嗨来自"没有显示项目名称,请尝试:

logging.debug('Hi from {}'.format(args['project']))

编辑:回答你的评论,实际上你确实从两个线程中获取了日志。 但是您的info对象是共享的。当您传递args=(info,))时,实际上您传递了对info对象的引用,而不是副本。

因此,第一次for循环执行时,您获得info['project'] = "project_1",但是第二次info['project']被#34; project_2"覆盖。

您的工作线程只读取来自同一info字典...

的值

答案 1 :(得分:2)

您的部分问题来自您传递的对象变量。当您传递args=(info,)时,您正在传递reference to an object(您稍后修改并传递给下一个对象),而不是该对象的副本。将同一个对象传递给多个线程可能会变得很危险,因此可以race conditions

首先,我们可以删除ContextFilter。我们将它们添加到全局记录器中,而不是跟踪每个线程的任何内容。

import logging
import threading
import time

logger = logging.getLogger()
syslog = logging.StreamHandler()
formatter = logging.Formatter('%(project)s : %(thread)x '
                              '%(levelname)-8s '
                              '%(message)s')
syslog.setFormatter(formatter)
logger.setLevel(logging.DEBUG)
logger.addHandler(syslog)

我发现通常构建threading.Thread类对除了最简单的任务之外的所有类都更有用。

此类维护自己的running状态,并使用正确的extra数据构建自己的日志记录适配器。

class Worker(threading.Thread):
    def __init__(self, info):
        self.running=False
        self.info=info
        self.logger=logging.LoggerAdapter(logger, self.info)
        super(Worker, self).__init__()
    def start(self):
        self.running=True
        super(Worker, self).start()
    def stop(self):
        self.running=False
    def run(self):
        while self.running:
            self.logger.debug('Hi from {}'.format(self.info['project']))
            time.sleep(0.5)

现在我们需要改变一些事情。我们需要使用自己的Worker类。

我们不需要对记录器做任何事情,该类将管理自己的LoggerAdapter。

我们希望确保每次都创建一个新的info对象,这很简单,我们可以直接在函数调用({'project': project})中直接传递它而无需赋值变量。

我们需要确保在从主线程登录时传递project变量。使用另一个LoggerAdapter可能更好。

一旦我们打破循环,我们就可以让每个线程停止,然后等待每个线程(join()也许可以移动到stop类的Worker方法中< / p>

def main():
    projects = ['project_1', 'project_2']
    threads = []
    for project in projects:
        thread = Worker({'project': project})
        thread.start()
        threads.append(thread)
    while True:
        try:
            logging.debug('Hello from main', extra={'project':'main'})
            time.sleep(1.75)
        except KeyboardInterrupt:
            break
    for t in threads:
        t.stop()
    for t in threads:
        t.join()

if __name__ == '__main__':
    main()

此代码会产生

等结果
project_1 : 7f4b44180700 DEBUG    Hi from project_1
project_2 : 7f4b4397f700 DEBUG    Hi from project_2
main : 7f4b45c8d700 DEBUG    Hello from main
project_1 : 7f4b44180700 DEBUG    Hi from project_1
project_2 : 7f4b4397f700 DEBUG    Hi from project_2
project_1 : 7f4b44180700 DEBUG    Hi from project_1
project_2 : 7f4b4397f700 DEBUG    Hi from project_2
project_1 : 7f4b44180700 DEBUG    Hi from project_1
project_2 : 7f4b4397f700 DEBUG    Hi from project_2
main : 7f4b45c8d700 DEBUG    Hello from main
project_1 : 7f4b44180700 DEBUG    Hi from project_1

有很多方法可以整理代码并使其更具可读性,但这至少应该为您提供一些学习起点和开始实验的起点。当您了解有关线程的更多信息时,您还应该阅读有关thread synchronization机制的内容。我最近开始using Queues进行线程之间的通信,引导代码更容易调试。