【问题标题】:How to use different logging handlers for the same logger in different threads of a python application?如何在 python 应用程序的不同线程中为同一个记录器使用不同的日志记录处理程序?
【发布时间】:2021-07-04 01:30:00
【问题描述】:

我正在使用第三方库(本例中为 pynetdicom)。从库中的模块生成日志记录如下:

LOGGER = logging.getLogger('pynetdicom')
LOGGER.error("Some error message")

该库还为 pynetdicom 记录器设置了一个 null 处理程序,因此默认情况下不会生成任何记录:

logging.getLogger('pynetdicom').addHandler(logging.NullHandler())

如果我希望库中的模块生成一些日志记录,我必须用其他一些方便的处理程序覆盖这个 null 处理程序。到目前为止,对我来说一切都很好,很清楚。

现在在我的应用程序中,有三个不同的线程,它们都使用 pynetdicom 库中的模块。我希望每个线程将 pynetdicom 事件记录到不同的文件(即 thread1 将 pynetdicom 事件记录到 thread1.log,thread2 将 pynetdicom 事件记录到 thread2.log,等等)。我怎样才能做到这一点?

我尝试在每个线程中将不同的处理程序附加到 pynetdicom 记录器,但当然它不起作用,因为它们都在修改同一个记录器实例,从而覆盖处理程序。

编辑

我还尝试将不同的处理程序附加到 pynetdicom 记录器,并为每个处理程序添加一个不同的过滤器,它只接受从所需线程发出的日志。这也不起作用,因为 pynetdicom 创建自己的内部线程作为从我的线程调用的函数和方法的一部分。

我还使用inspect.stack() 来捕获pynetdicom 内对LOGGER.error('...') 的每次调用的整个上下文,并尝试在我的应用程序中找到生成调用的原始函数/模块。这也不起作用,因为有时堆栈会在创建 pynetdicom 内部线程时完成,并且找不到我的应用程序模块的踪迹。

编辑

为了进一步说明情况,这里有一个例子:

import logging, threading

# Config pynetdicom to log to a file.
logger = logging.getLogger('pynetdicom')
logger.setLevel(logging.DEBUG)
logger.handlers = []
handler = RotatingFileHandler('dicom_events.log'))
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s: %(levelname).1s: %(threadName)s: %(message)s ')    
handler.setFormatter(formatter)
logger.addHandler(handler)

# Functions for each thread in my application
def ServiceClassUser():
    # Some modules from pynetdicom are used from here. They write log messages in 'dicom_events.log'
    
def ServiceClassProvider():
    # Some modules pynetdicom are used from here. They write log messages in 'dicom_events.log'

# Initialize threads
scu_thread = threading.Thread(target = ServiceClassUser, name = 'StoreSCU')
scu_thread.start()

scp_thread = threading.Thread(target = ServiceClassProvider, name = 'StoreSCP')
scp_thread.start()

dicom_events.log 如下所示:

2021-07-12 19:20:05,830: D: Thread-2: Priority                      : Medium 
2021-07-12 19:20:05,830: D: Thread-2: ============================ END DIMSE MESSAGE ============================= 
2021-07-12 19:20:05,830: D: AcceptorThread@20210712191907: pydicom.read_dataset() TransferSyntax="Little Endian Explicit" 
2021-07-12 19:20:05,888: D: Thread-2: pydicom.read_dataset() TransferSyntax="Little Endian Implicit" 
2021-07-12 19:20:07,266: I: Thread-2: Received Store Request 
2021-07-12 19:20:07,266: D: Thread-2: ========================== INCOMING DIMSE MESSAGE ========================== 
2021-07-12 19:20:07,266: D: Thread-2: Message Type                  : C-STORE RQ 
2021-07-12 19:20:07,266: D: Thread-2: Presentation Context ID       : 21 
2021-07-12 19:20:07,266: D: Thread-2: Message ID                    : 1019 
2021-07-12 19:20:07,266: D: Thread-2: Affected SOP Class UID        : 1.2.840.113619.4.30 
2021-07-12 19:20:07,266: D: Thread-2: Affected SOP Instance UID     : 1.2.840.113619.2.131.1460334732.1626124764.739758 
2021-07-12 19:20:07,266: D: Thread-2: Data Set                      : Present 
2021-07-12 19:20:07,267: D: Thread-2: Priority                      : Medium 
2021-07-12 19:20:07,267: D: Thread-2: ============================ END DIMSE MESSAGE ============================= 
2021-07-12 19:20:07,267: D: AcceptorThread@20210712191907: pydicom.read_dataset() TransferSyntax="Little Endian Explicit" 
2021-07-12 19:20:07,291: I: AcceptorThread@20210712191907: Association Released 
2021-07-12 19:20:09,372: I: StoreSCU: Requesting Association
2021-07-12 19:20:09,375: D: Thread-6: Request Parameters: 
2021-07-12 19:20:09,375: D: Thread-6: ======================= OUTGOING A-ASSOCIATE-RQ PDU ======================== 
2021-07-12 19:20:09,375: D: Thread-6: Our Implementation Class UID:      1.2.826.0.1.3680043.9.3811.1.5.7  

Thread-2、Thread-6 和 AcceptorThread 由 pynetdicom 创建。我无法控制它们,而且我无法知道最初创建它们的是对 pynetdicom 模块的哪个调用。所以在原始线程上使用Filter 并没有帮助。也不使用格式化程序在日志文件中包含线程名称或 ID。

【问题讨论】:

    标签: python logging


    【解决方案1】:

    我建议您的每个线程将FileHandler 添加到pynetdicom 记录器,然后将Filter 添加到每个处理程序以仅接受从该线程发出的日志。

    这是一个例子:

    import logging
    import threading
    
    logger = logging.getLogger("pynetdicom")
    logger.setLevel(logging.DEBUG)
    
    
    class FilterByThread(logging.Filter):
        def __init__(self, allowed_thread_id: int):
            super().__init__()
            self.allowed_thread_id = allowed_thread_id
    
        def filter(self, record: logging.LogRecord) -> bool:
            return record.thread == self.allowed_thread_id
    
    
    def setup_thread_logging():
        current_thread_name = threading.current_thread().name
        logfile_name = current_thread_name + ".log"
        handler = logging.FileHandler(logfile_name)
        current_thread_id = threading.current_thread().ident
        log_filter = FilterByThread(current_thread_id)
        handler.addFilter(log_filter)
        logger.addHandler(handler)
    
    
    def do_main_stuff():
        setup_thread_logging()
        logger.info("hello from Main Thread")
    
    
    def do_stuff2():
        setup_thread_logging()
        logger.info("hello from thread 2")
    
    
    def do_stuff3():
        setup_thread_logging()
        logger.info("hello from thread 3")
    
    
    main_thread = threading.current_thread()
    thread2 = threading.Thread(target=do_stuff2, name="thread2")
    thread3 = threading.Thread(target=do_stuff3, name="thread3")
    
    thread2.start()
    thread3.start()
    do_main_stuff()
    
    thread2.join()
    thread3.join()
    

    产生:

    • MainThread.log
      hello from Main Thread
      
    • thread2.log
      hello from thread 2
      
    • thread3.log
      hello from thread 3
      

    可能有更聪明或更有效的解决方案,但这个很容易理解。

    【讨论】:

    • 感谢@Lenormju 的帮助。我已经尝试过类似的方法,但没有奏效。我编辑了我的问题,提供了更多详细信息。
    • 解决问题的另一种方法是使用包含 threadId 的 Formatter,以便每个日志行指示哪个线程负责它。然后对文件进行后处理。
    • 不幸的是,这也不起作用,因为 pynetdicom 创建了我无法控制的额外线程。我找不到方法来知道对 pynetdicom 模块的哪个调用最初创建了这些额外的线程,因此知道每个日志条目的原始线程的名称或 ID 并不能解决问题。我在问题中发布了一个示例,也许它有助于澄清情况。我对此感到非常困惑,因为我知道必须有办法,但我到现在都找不到。
    猜你喜欢
    • 2020-03-27
    • 2014-09-30
    • 2011-03-08
    • 1970-01-01
    • 2014-02-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多