python - 如何在 python 应用程序的不同线程中为同一记录器使用不同的日志记录处理程序?
问题描述
我正在使用第三方库(在本例中为 pynetdicom)。从库中的模块生成日志,如下所示:
LOGGER = logging.getLogger('pynetdicom')
LOGGER.error("Some error message")
该库还为 pynetdicom 记录器设置了一个空处理程序,因此默认情况下不会生成任何记录:
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 模块的哪个调用。所以在原始线程上使用过滤器没有帮助。也不使用格式化程序在日志文件中包含线程名称或 ID。
解决方案
我建议您的每个线程向记录器添加一个FileHandlerpynetdicom
,然后向每个处理程序添加一个过滤器以仅接受从该线程发出的日志。
这是一个例子:
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
可能有更聪明或更有效的解决方案,但这个很容易理解。
推荐阅读
- amazon-web-services - 进出 AWS Api Gateway 的不同响应
- javascript - 如何向非营利网站添加捐赠功能?
- python - Django 不在数据库中存储密码
- go - 在不知道它们的类型的情况下比较来自两个接口的结构字段?
- azure - Azure 应用服务自定义域 - 网站功能不同
- r - 为什么我不断收到关于 Else 语句的错误?
- swift - 在 Xcode 中出现“无法打开权利”错误
- python-2.7 - Luigi:有没有办法像在 configparser 中那样使用扩展插值?
- python - 绘制和提取 fft 阶段
- javascript - 从 Prettier/ESlint 的显示中删除波浪线