python - 如何在 Python 中分离日志处理程序
问题描述
我有一种情况,我想在 Python 中创建两个单独的记录器对象,每个对象都有自己独立的处理程序。“分离”是指我希望能够独立地将日志语句传递给每个对象,而不会污染其他日志。
主文件
import logging
from my_other_logger import init_other_logger
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO, handlers=[logging.StreamHandler(sys.stdout)])
other_logger = init_other_logger(__name__)
logger.info('Hello World') # Don't want to see this in the other logger
other_logger.info('Goodbye World') # Don't want to see this in the first logger
my_other_logger.py
import logging
import os, sys
def init_other_logger(namespace):
logger = logging.getLogger(namespace)
logger.setLevel(logging.DEBUG)
fh = logging.FileHandler(LOG_FILE_PATH)
logger.addHandler(fh)
formatter = logging.Formatter('%(levelname)s:%(name)s:%(message)s')
fh.setFormatter(formatter)
#logger.propagate = False
return logger
我能够确定在这里有用的唯一配置是logger.propagate
属性。按原样运行上面的代码将所有日志语句通过管道传输到日志流和日志文件。当我没有logger.propagate = False
任何东西被传送到日志流时,两个日志对象再次将它们的输出传送到日志文件。
如何创建一个仅将日志发送到一个处理程序的日志对象,以及另一个将日志发送到另一个处理程序的日志对象?
解决方案
首先,让我们先看看发生了什么,然后再着手解决问题。
logger = logging.getLogger(__name__)
:当你这样做时,你正在获取或创建一个名为'main'
. 由于这是第一次调用,它将创建该记录器。
other_logger = init_other_logger(__name__)
: 当你这样做时,你再次获取或创建一个名为 的记录器'main'
。由于这是第二次调用,它将获取上面创建的记录器。所以你并没有真正实例化一个新的记录器,但是你得到了对上面创建的同一个记录器的引用。您可以在调用init_other_logger
表格后通过打印来检查这一点:) print(logger is other_logger
。
接下来发生的事情是将 aFileHandler
和 a添加Formatter
到'main'
记录器(在init_other_logger
函数内部),然后通过方法调用 2 个日志调用info()
。但是您正在使用相同的 logger进行操作。
所以这:
logger.info('Hello World')
other_logger.info('Goodbye World')
本质上与此相同:
logger.info('Hello World')
logger.info('Goodbye World')
现在,两个记录器都输出到文件和流中,这并不奇怪了。
解决方案
所以显而易见的事情就是init_other_logger
用另一个名字称呼你。
我会建议反对另一个答案提出的解决方案,因为当您需要一个独立的记录器时,这
不是应该做的事情。文档很好地说明了您永远不应该直接实例化记录器,而总是通过模块的功能getLogger
。logging
正如我们在上面发现的那样,当你调用它时,logging.getLogger(logger_name)
要么使用. 因此,当您也想要一个独特的记录器时,这非常有效。但是请记住,此函数是幂等的,这意味着它只会在您第一次调用它时创建一个具有给定名称的记录器,如果您使用相同的名称调用它,无论您调用多少次,它都会返回该记录器。logger_name
因此,例如:
表单的第一次调用
logging.getLogger('the_rock')
- 创建您独特的记录器表单的第二次调用
logging.getLogger('the_rock')
- 获取上述记录器
例如,如果您执行以下操作,您会发现这特别有用:
Formatters
在项目中的某个位置配置一个记录器Filters
,例如在project_root/main_package/__init__.py
.- 想要在位于
project_root/secondary_package/__init__.py
.
您可以对secondary_package/__init__.py
表单进行简单的调用:logger = logging.getLogger('main_package')
您将使用该记录器及其所有的花里胡哨。
注意力!
即使此时您将使用您的init_other_logger
函数创建一个唯一的记录器,它仍然会输出到文件和控制台。将此行替换other_logger = init_other_logger(__name__)
为other_logger = init_other_logger('the_rock')
以创建唯一的记录器并再次运行代码。您仍然会看到输出写入控制台和文件。
为什么 ?
因为它将同时使用FileHandler
和StreamHandler
。
为什么 ?
因为伐木机械的工作方式。您的记录器将通过其处理程序发出其消息,然后它将一直传播到根记录器,在那里它将使用StreamHandler
您通过basicConfig
调用附加的记录器。因此,propagate
您发现的属性实际上就是您想要的,因为您正在创建一个自定义记录器,您只想通过其手动附加的处理程序发出消息,而不是进一步发出任何消息。在创建唯一记录器后取消注释logger.propagate = False
,您会看到一切都按预期工作。