如何禁用标准错误流的日志记录?

Posted

技术标签:

【中文标题】如何禁用标准错误流的日志记录?【英文标题】:How to disable logging on the standard error stream? 【发布时间】:2011-01-17 00:31:42 【问题描述】:

如何在 Python 的标准错误流上禁用logging?这不起作用:

import logging

logger = logging.getLogger()
logger.removeHandler(sys.stderr)
logger.warning('foobar')  # emits 'foobar' on sys.stderr

【问题讨论】:

对于那些想知道为什么有人要禁用日志记录的人:您不想记录密码或 API 密钥等私人数据。 @StevenVascellaro。为什么那些首先被发送到记录器呢?这听起来不对…… @MadPhysicist 我有一个将 XML 请求发送到外部 API 的应用程序。默认情况下,这些请求被记录到一个文件中。但是,初始登录需要使用用户名和密码进行身份验证,我不想登录。 @StevenVascellaro。我知道了。谢谢你的解释。 您没有显示添加处理程序的方式/位置。如果将它们添加到根记录器,这将阻止记录添加默认 StreamHandler,如 docs.python.org/3/library/logging.html#logging.basicConfig 中所述。此外,根据链接描述,默认 StreamHandler 仅在第一次调用发出日志消息期间添加,因此当您打印 logger.handlers 它应该是空(因为它在logger.debug() 调用之前)。有问题的代码仅显示[](处理程序的空列表)。通过 Python 2.7.15 和 Python 3.6.6 验证。 【参考方案1】:

我不太了解日志记录模块,但我使用它的方式通常只想禁用调试(或信息)消息。您可以使用Handler.setLevel() 将日志记录级别设置为 CRITICAL 或更高。

此外,您可以将 sys.stderr 和 sys.stdout 替换为为写入而打开的文件。见http://docs.python.org/library/sys.html#sys.stdout。但我不建议这样做。

【讨论】:

如果 logger.handlers 包含某些东西,这可能会起作用,目前是[]【参考方案2】:

无需转移标准输出。这是更好的方法:

import logging
class MyLogHandler(logging.Handler):
    def emit(self, record):
        pass

logging.getLogger().addHandler(MyLogHandler())

更简单的方法是:

logging.getLogger().setLevel(100)

【讨论】:

在 Python 2.7+ 中可以使用 NullHandler() 在阅读logging.basicConfig() 函数的描述时可以看到这个工作的原因(禁用默认 StreamHandler)(强调我的): 通过创建一个带有默认格式化程序并将其添加到根记录器。 如果没有为根记录器定义处理程序,函数 debug()、info()、warning()、error() 和 critical() 将自动调用 basicConfig()。 – @ 987654322@【参考方案3】:

你可以使用:

logging.basicConfig(level=your_level)

your_level 是其中之一:

'debug': logging.DEBUG,
'info': logging.INFO,
'warning': logging.WARNING,
'error': logging.ERROR,
'critical': logging.CRITICAL

因此,如果您将 your_level 设置为 logging.CRITICAL,您将只会收到以下人员发送的关键消息:

logging.critical('This is a critical error message')

your_level 设置为 logging.DEBUG 将显示所有级别的日志记录。

更多详情请关注logging examples.

使用Handler.setLevel()函数以相同的方式更改每个Handler的级别。

import logging
import logging.handlers

LOG_FILENAME = '/tmp/logging_rotatingfile_example.out'

# Set up a specific logger with our desired output level
my_logger = logging.getLogger('MyLogger')
my_logger.setLevel(logging.DEBUG)

# Add the log message handler to the logger
handler = logging.handlers.RotatingFileHandler(
          LOG_FILENAME, maxBytes=20, backupCount=5)

handler.setLevel(logging.CRITICAL)

my_logger.addHandler(handler)

【讨论】:

这通常是有用的信息,但问题是如何禁用控制台日志记录,而不是如何添加额外的处理程序。如果您将上述代码应用于原始示例来检查 my_logger.handlers,您会看到两个处理程序——您的新文件处理程序和原始流处理程序。 CRITICAL 是我要找的词。谢谢。 我希望看到调试级别为 OFF。它是明确而简单的。 logging.CRITICAL+1【参考方案4】:

我找到了解决方案:

logger = logging.getLogger('my-logger')
logger.propagate = False
# now if you use logger it will not log to console.

这将阻止将日志记录发送到包含控制台日志记录的上层记录器。

【讨论】:

我认为这不是一个好的解决方案。不传播到更高的记录器可能会产生其他不良后果。 如果您只想过滤低于某个日志级别的消息(例如,所有INFO 消息),您可以将第二行更改为logger.setLevel(logging.WARNING) 之后如何重新启用日志? 不是答案,因为阻塞传播有效地禁用了根记录器的所有处理程序,并且问题清楚地说明了 (...) 但我可能有其他我想要的处理程序保留 这表明意图是禁用根记录器的默认 StreamHandler only 停止消息传播是不够的。 Since Python 3.2,logging.lastResort 处理程序仍将在没有其他处理程序的情况下将严重性为logging.WARNING 和更高级别的消息记录到sys.stderr。 See my answer.【参考方案5】:

我用:

logger = logging.getLogger()
logger.disabled = True
... whatever you want ...
logger.disabled = False

【讨论】:

这也适用于logging 模块级别以完全禁用日志记录,例如:import logging; logging.disable(logging.CRITICAL);:docs.python.org/2/library/logging.html#logging.disable 这比禁用传播要好得多。 Not an answer - 问题询问如何禁用默认 StreamHandler only disabled 属性不是公共 API 的一部分。见bugs.python.org/issue36318。 这个应该包含在 try / finally 中吗?如果代码出现一个异常会发生什么?记录器是否保持禁用状态?【参考方案6】:

(很久以前的问题,但对于未来的搜索者)

更接近原始海报的代码/意图,这在 python 2.6 下适用于我

#!/usr/bin/python
import logging

logger = logging.getLogger() # this gets the root logger

lhStdout = logger.handlers[0]  # stdout is the only handler initially

# ... here I add my own handlers 
f = open("/tmp/debug","w")          # example handler
lh = logging.StreamHandler(f)
logger.addHandler(lh)

logger.removeHandler(lhStdout)

logger.debug("bla bla")

我必须解决的问题是删除标准输出处理程序添加一个新的处理程序之后;如果没有处理程序,记录器代码似乎会自动重新添加标准输出。

IndexOutOfBound 修复:如果在实例化 lhStdout 时遇到 IndexOutOfBound 错误,请将实例化移至添加文件处理程序后,即

...
logger.addHandler(lh)

lhStdout = logger.handlers[0]
logger.removeHandler(lhStdout)

【讨论】:

序列 logger = logging.getLogger(); lhStdout = logger.handlers[0] 是错误的,因为根记录器最初没有处理程序 – python -c "import logging; assert not logging.getLogger().handlers"。通过 Python 2.7.15 和 Python 3.6.6 验证。【参考方案7】:

这里有一些非常好的答案,但显然最简单的答案没有考虑太多(仅来自 infinito)。

root_logger = logging.getLogger()
root_logger.disabled = True

这会禁用根记录器,从而禁用所有其他记录器。 我还没有真正测试过,但它应该也是最快的。

从 python 2.7 中的日志记录代码我看到了这个

def handle(self, record):
    """
    Call the handlers for the specified record.

    This method is used for unpickled records received from a socket, as
    well as those created locally. Logger-level filtering is applied.
    """
    if (not self.disabled) and self.filter(record):
        self.callHandlers(record)

这意味着当它被禁用时,不会调用任何处理程序,并且例如过滤到非常高的值或设置无操作处理程序应该更有效。

【讨论】:

除非我做错了什么,否则这只会禁用根记录器,而不是像log = logging.getLogger(__name__)那样创建的任何记录器 如果您要处理多个记录器或多个处理程序,这可能会出现问题。例如,如果您仍想记录到文件,但想在特定情况下禁用流处理程序。 这会禁用根记录器,从而禁用所有其他记录器 – 严格来说,禁用根记录器不会禁用任何其他记录器。除了问题询问关于禁用默认 StreamHandler only. disabled 属性不是公共 API 的一部分。见bugs.python.org/issue36318。【参考方案8】:

使用上下文管理器 - [最简单]

import logging 

class DisableLogger():
    def __enter__(self):
       logging.disable(logging.CRITICAL)
    def __exit__(self, exit_type, exit_value, exit_traceback):
       logging.disable(logging.NOTSET)

使用示例:

with DisableLogger():
    do_something()

如果您需要 [more COMPLEX] 细粒度的解决方案,您可以查看 AdvancedLogger

AdvancedLogger can be used for fine grained logging temporary modifications

How it works:
Modifications will be enabled when context_manager/decorator starts working and be reverted after

Usage:
AdvancedLogger can be used
- as decorator `@AdvancedLogger()`
- as context manager `with  AdvancedLogger():`

It has three main functions/features:
- disable loggers and it's handlers by using disable_logger= argument
- enable/change loggers and it's handlers by using enable_logger= argument
- disable specific handlers for all loggers, by using  disable_handler= argument

All features they can be used together

AdvancedLogger 用例

# Disable specific logger handler, for example for stripe logger disable console
AdvancedLogger(disable_logger="stripe": "console")
AdvancedLogger(disable_logger="stripe": ["console", "console2"])

# Enable/Set loggers
# Set level for "stripe" logger to 50
AdvancedLogger(enable_logger="stripe": 50)
AdvancedLogger(enable_logger="stripe": "level": 50, "propagate": True)

# Adjust already registered handlers
AdvancedLogger(enable_logger="stripe": "handlers": "console"

【讨论】:

我真的很喜欢这个成语,但我宁愿能够禁用特定的命名空间。例如,我只想暂时禁用根记录器。虽然使用了这个习语,但我们应该能够临时添加/删除处理程序等。 问题问如何only禁用默认StreamHandler。 你不需要滚动你自己的类,你可以使用 contextlib 中的@contextmanager 并编写一个屈服函数 如果您喜欢披萨上的异国水果。当然。 @PiotrDobrogost 我添加了一个指向 AdvancedLogger 的链接,它允许临时禁用到控制台的输出(StreamHandler)【参考方案9】:

你也可以:

handlers = app.logger.handlers
# detach console handler
app.logger.handlers = []
# attach
app.logger.handlers = handlers

【讨论】:

您为什么使用您甚至没有指定的app.logger,而不是问题(logging.getLogger())和大多数答案中明确提到的根记录器?你怎么知道你可以安全地修改handlers 属性而不是调用Logger.addHandler 方法?【参考方案10】:

完全禁用日志记录

logging.disable(sys.maxint) # Python 2

logging.disable(sys.maxsize) # Python 3

启用日志记录

logging.disable(logging.NOTSET)

其他答案提供了不能完全解决问题的变通办法,例如

logging.getLogger().disabled = True

而且,对于某些大于 50 的n

logging.disable(n)

第一个解决方案的问题是它只适用于根记录器。使用logging.getLogger(__name__) 创建的其他记录器不会被此方法禁用。

第二种解决方案确实会影响所有日志。但是它将输出限制在给定的级别之上,因此可以通过使用大于 50 的级别进行日志记录来覆盖它。

这可以通过

logging.disable(sys.maxint)

据我所知(在查看 source 之后)是完全禁用日志记录的唯一方法。

【讨论】:

当问题询问如何禁用标准 StreamHandler only 时投反对票【参考方案11】:
import logging

log_file = 'test.log'
info_format = '%(asctime)s - %(levelname)s - %(message)s'
logging.config.dictConfig(
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': 
        'info_format': 
            'format': info_format
        ,
    ,
    'handlers': 
        'console': 
            'level': 'INFO',
            'class': 'logging.StreamHandler',
            'formatter': 'info_format'
        ,
        'info_log_file': 
            'class': 'logging.handlers.RotatingFileHandler',
            'level': 'INFO',
            'filename': log_file,
            'formatter': 'info_format'
        
    ,
    'loggers': 
        '': 
            'handlers': [
                'console',
                'info_log_file'
            ],
            'level': 'INFO'
        
    
)


class A:

    def __init__(self):
        logging.info('object created of class A')

        self.logger = logging.getLogger()
        self.console_handler = None

    def say(self, word):
        logging.info('A object says: '.format(word))

    def disable_console_log(self):
        if self.console_handler is not None:
            # Console log has already been disabled
            return

        for handler in self.logger.handlers:
            if type(handler) is logging.StreamHandler:
                self.console_handler = handler
                self.logger.removeHandler(handler)

    def enable_console_log(self):
        if self.console_handler is None:
            # Console log has already been enabled
            return

        self.logger.addHandler(self.console_handler)
        self.console_handler = None


if __name__ == '__main__':
    a = A()
    a.say('111')
    a.disable_console_log()
    a.say('222')
    a.enable_console_log()
    a.say('333')

控制台输出:

2018-09-15 15:22:23,354 - INFO - object created of class A
2018-09-15 15:22:23,356 - INFO - A object says: 111
2018-09-15 15:22:23,358 - INFO - A object says: 333

test.log 文件内容:

2018-09-15 15:22:23,354 - INFO - object created of class A
2018-09-15 15:22:23,356 - INFO - A object says: 111
2018-09-15 15:22:23,357 - INFO - A object says: 222
2018-09-15 15:22:23,358 - INFO - A object says: 333

【讨论】:

添加一些关于代码的描述。它会帮助更好【参考方案12】:

子类化您希望能够暂时禁用的处理程序:

class ToggledHandler(logging.StreamHandler):
"""A handler one can turn on and off"""

def __init__(self, args, kwargs):
    super(ToggledHandler, self).__init__(*args, **kwargs)
    self.enabled = True  # enabled by default

def enable(self):
    """enables"""
    self.enabled = True

def disable(self):
    """disables"""
    self.enabled = False

def emit(self, record):
    """emits, if enabled"""
    if self.enabled:
        # this is taken from the super's emit, implement your own
        try:
            msg = self.format(record)
            stream = self.stream
            stream.write(msg)
            stream.write(self.terminator)
            self.flush()
        except Exception:
            self.handleError(record)

按名称查找处理程序非常简单:

_handler = [x for x in logging.getLogger('').handlers if x.name == your_handler_name]
if len(_handler) == 1:
    _handler = _handler[0]
else:
    raise Exception('Expected one handler but found '.format(len(_handler))

一旦找到:

_handler.disable()
doStuff()
_handler.enable()

【讨论】:

【参考方案13】:

通过更改“logging.config.dictConfig”中的一个级别,您将能够将整个日志记录级别提升到一个新级别。

logging.config.dictConfig(
'version': 1,
'disable_existing_loggers': False,
'formatters': 
    'console': 
        'format': '%(name)-12s %(levelname)-8s %(message)s'
    ,
    'file': 
        'format': '%(asctime)s %(name)-12s %(levelname)-8s %(message)s'
    
,
'handlers': 
    'console': 
        'class': 'logging.StreamHandler',
        'formatter': 'console'
    ,
#CHANGE below level from DEBUG to THE_LEVEL_YOU_WANT_TO_SWITCH_FOR
#if we jump from DEBUG to INFO
# we won't be able to see the DEBUG logs in our logging.log file
    'file': 
        'level': 'DEBUG',
        'class': 'logging.FileHandler',
        'formatter': 'file',
        'filename': 'logging.log'
    ,
,
'loggers': 
    '': 
        'level': 'DEBUG',
        'handlers': ['console', 'file'],
        'propagate': False,
    ,

)

【讨论】:

【参考方案14】:

使用 decorators 找到了一个优雅的解决方案,它解决了以下问题:如果您正在编写一个具有多个功能的模块,每个功能都有多个调试消息,并且您想要禁用所有登录功能,但您目前正在关注的功能?

你可以使用装饰器来做到这一点:

import logging, sys
logger = logging.getLogger()
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)


def disable_debug_messages(func):
    def wrapper(*args, **kwargs):
        prev_state = logger.disabled
        logger.disabled = True
        result = func(*args, **kwargs)
        logger.disabled = prev_state
        return result
    return wrapper

那么,你可以这样做:

@disable_debug_messages
def function_already_debugged():
    ...
    logger.debug("This message won't be showed because of the decorator")
    ...

def function_being_focused():
    ...
    logger.debug("This message will be showed")
    ...

即使您从function_being_focused 中调用function_already_debugged,也不会显示来自function_already_debugged 的调试消息。 这样可以确保您只看到来自您关注的函数的调试消息。

希望对你有帮助!

【讨论】:

【参考方案15】:

日志有the following structure:

记录器根据命名空间层次结构排列,并带有点分隔符; 每个记录器都有一个级别(根记录器默认为logging.WARNING,非根记录器默认为logging.NOTSET)和一个有效级别(非根记录器的父记录器的有效级别,级别为logging.NOTSET,否则为记录器的级别); 每个记录器都有一个过滤器列表; 每个记录器都有一个处理程序列表; 每个处理程序都有一个级别(默认为logging.NOTSET); 每个处理程序都有一个过滤器列表

Logging 有the following process(用流程图表示):

因此,要禁用特定记录器,您可以采用以下策略之一:

    将记录器的级别设置为logging.CRITICAL + 1

    使用主 API:

    import logging
    
    logger = logging.getLogger("foo")
    logger.setLevel(logging.CRITICAL + 1)
    

    使用配置 API:

    import logging.config
    
    logging.config.dictConfig(
        "version": 1,
        "loggers": 
            "foo": 
                "level": logging.CRITICAL + 1
            
        
    )
    

    向记录器添加过滤器lambda record: False

    使用主 API:

    import logging
    
    logger = logging.getLogger("foo")
    logger.addFilter(lambda record: False)
    

    使用配置 API:

    import logging.config
    
    logging.config.dictConfig(
        "version": 1,
        "filters": 
            "all": 
                "()": lambda: (lambda record: False)
            
        ,
        "loggers": 
            "foo": 
                "filters": ["all"]
            
        
    )
    

    删除记录器的现有处理程序add a handler logging.NullHandler() to the logger(以防止事件被处理程序logging.lastResort处理,这是一个logging.StreamHandler使用当前流sys.stderr和一个级别logging.WARNING)和@987654325 @(防止事件被记录器的祖先记录器的处理程序处理)。

    使用主 API:

    import logging
    
    logger = logging.getLogger("foo")
    for handler in logger.handlers.copy():
        try:
            logger.removeHandler(handler)
        except ValueError:  # in case another thread has already removed it
            pass
    logger.addHandler(logging.NullHandler())
    logger.propagate = False
    

    使用配置 API:

    import logging.config
    
    logging.config.dictConfig(
        "version": 1,
        "handlers": 
            "null": 
                "class": "logging.NullHandler"
            
        ,
        "loggers": 
            "foo": 
                "handlers": ["null"],
                "propagate": False
            
        
    )
    

警告。 - 与策略 1 和 2 相反,策略 3还可以防止记录器(例如logging.getLogger("foo.bar"))的后代记录器记录的事件被记录器及其祖先记录器的处理程序发出。

注意。 — 将记录器的属性 disabled 设置为 True 不是另一种策略,因为它不是公共 API 的一部分(参见 https://bugs.python.org/issue36318):

import logging

logger = logging.getLogger("foo")
logger.disabled = True  # DO NOT DO THIS

【讨论】:

很好,但问题还问如何重新启用它?例如,你会做一个 removeFilter 吗? @NealWalters 对于第一种解决方案,您将创建一个处理程序:handler = logging.NullHandler(),将其添加到记录器并禁用传播以禁用记录:logger.addHandler(handler); logger.propagate = False,然后将其从记录器中删除并重新启用传播以重新启用日志记录:logger.removeHandler(handler); logger.propagate = True。对于第二种解决方案,您将创建一个过滤器:def filter(record): return False,将其添加到记录器以禁用记录:logger.addFilter(filter),然后将其从记录器中删除以重新启用记录:logger.removeFilter(filter)【参考方案16】:

这将阻止来自第三个库的所有日志记录,如此处所述 https://docs.python.org/3/howto/logging.html#configuring-logging-for-a-library

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

【讨论】:

【参考方案17】:

您可以更改特定处理程序的调试模式级别,而不是完全禁用它。

因此,如果您有一个案例只想停止控制台的调试模式,但您仍然需要保留其他级别,例如错误。你可以这样做

# create logger
logger = logging.getLogger(__name__)

def enableConsoleDebug (debug = False):
    #Set level to logging.DEBUG to see CRITICAL, ERROR, WARNING, INFO and DEBUG statements
    #Set level to logging.ERROR to see the CRITICAL & ERROR statements only
    logger.setLevel(logging.DEBUG)

    debugLevel = logging.ERROR
    if debug:
        debugLevel = logging.DEBUG

    for handler in logger.handlers:
        if type(handler) is logging.StreamHandler:
            handler.setLevel (debugLevel)

【讨论】:

【参考方案18】:

考虑到您已经创建了自己的处理程序,然后在将它们添加到记录器之前,您可以这样做:

logger.removeHandler(logger.handlers[0])

这将删除默认的 StreamHandler。 这在 Python 3.8 上对我有用,因为在遇到不需要的日志发送到 stderr 后,它们应该只记录到文件中。

【讨论】:

以上是关于如何禁用标准错误流的日志记录?的主要内容,如果未能解决你的问题,请参考以下文章

排毒测试 - 如何禁用排毒信息记录到标准输出

如何禁用某个列的 ActiveRecord 日志记录?

如何使用 uber-go/zap 根据日志级别记录到标准输出或标准错误?

加载 Doctrine 固定装置时如何在控制台中禁用查询日志记录?

如何在代码中启用/禁用 spdlog 日志记录?

如何在根级别禁用导入的模块日志记录