【问题标题】:Disable logging per method/function?禁用每个方法/功能的日志记录?
【发布时间】:2011-11-12 13:17:14
【问题描述】:

我是 Python 日志记录的新手,我很容易看出它比我想出的自制解决方案更可取。

一个我似乎找不到答案的问题:如何在每个方法/函数的基础上静默日志消息?

我假设的模块包含一个函数。随着我的发展,日志调用很有帮助:

logging.basicConfig(level=logging.DEBUG,
                format=('%(levelname)s: %(funcName)s(): %(message)s'))
log = logging.getLogger()

my_func1():
    stuff...
    log.debug("Here's an interesting value: %r" % some_value)
    log.info("Going great here!")
    more stuff...

当我结束对“my_func1”的工作并开始处理第二个函数“my_func2”时,来自“my_func1”的日志消息开始从“有用”变为“混乱”。

是否有单行魔术语句,例如“logging.disabled_in_this_func()”,我可以将其添加到“my_func1”的顶部以禁用“my_func1”中的所有日志记录调用,但仍保留所有其他日志调用功能/方法不变?

谢谢

Linux、Python 2.7.1

【问题讨论】:

标签: python logging


【解决方案1】:

诀窍是创建多个记录器。

这有几个方面。

首先。不要在模块的开头使用logging.basicConfig()在主导入开关中使用它

 if __name__ == "__main__":
     logging.basicConfig(...)
     main()
     logging.shutdown()

第二。永远不要使用“root”记录器,除非设置全局首选项。

第三。为可能启用或禁用的事物获取单独的命名记录器。

log = logging.getLogger(__name__)

func1_log = logging.getLogger( "{0}.{1}".format( __name__, "my_func1" )

现在您可以在每个命名记录器上设置日志记录级别。

log.setLevel( logging.INFO )
func1_log.setLevel( logging.ERROR )

【讨论】:

  • 感谢有关将日志记录配置放在主导入开关中的建议。很高兴知道。即使像我现在正在处理的单模块脚本也不推荐使用根记录器?你能澄清一下这样做的缺点吗?
  • "不推荐使用根记录器"。时期。不要使用它。它是“匿名的”。您只需要命名记录器,以便进行配置和过滤。
  • 详细说明@S.Lott 的明确信息:如果您将处理程序添加到根记录器,您将开始从您使用的所有 其他包中获取消息,这可以变得非常混乱(例如requests)。反之亦然,当另一个包使用您的包时,您的包中的消息也不能单独处理。糟糕的设计。
  • 将此答案与how to get function name as string 结合起来,以实现简单、通用的复制粘贴:logger = logging.getLogger("{}.{}".format(__name__, sys._getframe().f_code.co_name))
【解决方案2】:

你可以创建一个装饰器来临时暂停日志记录,ala:

from functools import wraps

def suspendlogging(func):
    @wraps(func)
    def inner(*args, **kwargs):
        previousloglevel = log.getEffectiveLevel()
        try:
            return func(*args, **kwargs)
        finally:
            log.setLevel(previousloglevel)
    return inner

@suspendlogging
def my_func1(): ...

警告:这也会暂停从 my_func1 调用的任何函数的日志记录,因此请小心使用它。

【讨论】:

  • 适用于许多应用程序的好方法。谢谢!
  • 我猜这应该是装饰工厂让记录器暂停。
  • 如果我错了,请纠正我,但在我看来,这只会禁用一个名为 log 的记录器。如果同时使用多个记录器,例如在 Django 应用程序中,如何禁用所有记录器?
【解决方案3】:

你可以使用装饰器:

import logging
import functools

def disable_logging(func):
    @functools.wraps(func)
    def wrapper(*args,**kwargs):
        logging.disable(logging.DEBUG)
        result = func(*args,**kwargs)
        logging.disable(logging.NOTSET)
        return result
    return wrapper

@disable_logging
def my_func1(...):

【讨论】:

  • 哈!你秒杀我。在wrapper 内部要小心,因为它不会重新启用func() 调用的日志记录会引发异常。这就是为什么我使用try/finally 短语来保证无论func() 是否成功都会重新启用日志记录。
  • @KirkStrauser 您应该编辑以添加您的解决方案。您的建议将是一个很好的补充
【解决方案4】:

我花了一些时间来学习如何按照 S.Lott 的建议实施子记录器。

考虑到在我刚开始时弄清楚如何设置日志记录是多么困难,我想现在是时候分享我从那时起学到的东西了。

请记住,这不是设置记录器/子记录器的唯一方法,也不是最好的方法。这只是我用来完成工作以满足我的需求的方式。我希望这对某人有帮助。请随时评论/分享/批评。


假设我们有一个我们喜欢使用的简单库。在主程序中,我们希望能够控制从库中获取的日志消息。当然,我们是体贴的库创建者,因此我们配置我们的库以简化此操作。

一、主程序:

# some_prog.py

import os
import sys

# Be sure to give Vinay Sajip thanks for his creation of the logging module
# and tireless efforts to answer our dumb questions about it.  Thanks Vinay!
import logging

# This module will make understanding how Python logging works so much easier.
# Also great for debugging why your logging setup isn't working.
# Be sure to give it's creator Brandon Rhodes some love.  Thanks Brandon!
import logging_tree

# Example library
import some_lib

# Directory, name of current module
current_path, modulename = os.path.split(os.path.abspath(__file__))
modulename = modulename.split('.')[0]   # Drop the '.py'


# Set up a module-local logger
# In this case, the logger will be named 'some_prog'
log = logging.getLogger(modulename)

# Add a Handler.  The Handler tells the logger *where* to send the logging
# messages.  We'll set up a simple handler that send the log messages
# to standard output (stdout)
stdout_handler = logging.StreamHandler(stream=sys.stdout)
log.addHandler(stdout_handler)


def some_local_func():
    log.info("Info: some_local_func()")
    log.debug("Debug: some_local_func()")


if __name__ == "__main__":

    # Our main program, here's where we tie together/enable the logging infra
    # we've added everywhere else.

    # Use logging_tree.printout() to see what the default log levels
    # are on our loggers.  Make logging_tree.printout() calls at any place in
    # the code to see how the loggers are configured at any time.
    #
    # logging_tree.printout()

    print("# Logging level set to default (i.e. 'WARNING').")
    some_local_func()
    some_lib.some_lib_func()
    some_lib.some_special_func()

    # We know a reference to our local logger, so we can set/change its logging
    # level directly.  Let's set it to INFO:
    log.setLevel(logging.INFO)
    print("# Local logging set to 'INFO'.")
    some_local_func()
    some_lib.some_lib_func()
    some_lib.some_special_func()


    # Next, set the local logging level to DEBUG:
    log.setLevel(logging.DEBUG)
    print("# Local logging set to 'DEBUG'.")
    some_local_func()
    some_lib.some_lib_func()
    some_lib.some_special_func()


    # Set the library's logging level to DEBUG.  We don't necessarily
    # have a reference to the library's logger, but we can use
    # logging_tree.printout() to see the name and then call logging.getLogger()
    # to create a local reference.  Alternately, we could dig through the
    # library code.
    lib_logger_ref = logging.getLogger("some_lib")
    lib_logger_ref.setLevel(logging.DEBUG)

    # The library logger's default handler, NullHandler() won't output anything.
    # We'll need to add a handler so we can see the output -- in this case we'll
    # also send it to stdout.
    lib_log_handler = logging.StreamHandler(stream=sys.stdout)
    lib_logger_ref.addHandler(lib_log_handler)
    lib_logger_ref.setLevel(logging.DEBUG)

    print("# Logging level set to DEBUG in both local program and library.")
    some_local_func()
    some_lib.some_lib_func()
    some_lib.some_special_func()


    print("# ACK! Setting the library's logging level to DEBUG output")
    print("# all debug messages from the library.  (Use logging_tree.printout()")
    print("# To see why.)")
    print("# Let's change the library's logging level to INFO and")
    print("# only some_special_func()'s level to DEBUG so we only see")
    print("# debug message from some_special_func()")

    # Raise the logging level of the libary and lower the logging level
    # of 'some_special_func()' so we see only some_special_func()'s
    # debugging-level messages.
    # Since it is a sub-logger of the library's main logger, we don't need
    # to create another handler, it will use the handler that belongs
    # to the library's main logger.
    lib_logger_ref.setLevel(logging.INFO)
    special_func_sub_logger_ref = logging.getLogger('some_lib.some_special_func')
    special_func_sub_logger_ref.setLevel(logging.DEBUG)

    print("# Logging level set to DEBUG in local program, INFO in library and")
    print("# DEBUG in some_lib.some_special_func()")
    some_local_func()
    some_lib.some_lib_func()
    some_lib.some_special_func()

接下来,我们的图书馆:

# some_lib.py

import os
import logging

# Directory, name of current module
current_path, modulename = os.path.split(os.path.abspath(__file__))
modulename = modulename.split('.')[0]   # Drop the '.py'

# Set up a module-local logger.  In this case the logger will be
# named 'some_lib'
log = logging.getLogger(modulename)

# In libraries, always default to NullHandler so you don't get
# "No handler for X" messages.
# Let your library callers set up handlers and set logging levels
# in their main program so the main program can decide what level
# of messages they want to see from your library.
log.addHandler(logging.NullHandler())

def some_lib_func():
    log.info("Info: some_lib.some_lib_func()")
    log.debug("Debug: some_lib.some_lib_func()")

def some_special_func():
    """
    This func is special (not really).  It just has a function/method-local
    logger in addition to the library/module-level logger.
    This allows us to create/control logging messages down to the
    function/method level.

    """
    # Our function/method-local logger
    func_log = logging.getLogger('%s.some_special_func' % modulename)

    # Using the module-level logger
    log.info("Info: some_special_func()")

    # Using the function/method-level logger, which can be controlled separately
    # from both the library-level logger and the main program's logger.
    func_log.debug("Debug: some_special_func(): This message can be controlled at the function/method level.")

现在让我们运行程序以及评论轨道:

# Logging level set to default (i.e. 'WARNING').

请注意,默认级别没有输出,因为我们没有生成任何警告级别的消息。

# Local logging set to 'INFO'.
Info: some_local_func()

库的处理程序默认为NullHandler(),因此我们只能看到主程序的输出。这很好。

# Local logging set to 'DEBUG'.
Info: some_local_func()
Debug: some_local_func()

主程序记录器设置为调试。我们仍然看不到库的输出。这很好。

# Logging level set to DEBUG in both local program and library.
Info: some_local_func()
Debug: some_local_func()
Info: some_lib.some_lib_func()
Debug: some_lib.some_lib_func()
Info: some_special_func()
Debug: some_special_func(): This message can be controlled at the function/method level.

哎呀。

# ACK! Setting the library's logging level to DEBUG output
# all debug messages from the library.  (Use logging_tree.printout()
# To see why.)
# Let's change the library's logging level to INFO and
# only some_special_func()'s level to DEBUG so we only see
# debug message from some_special_func()
# Logging level set to DEBUG in local program, INFO in library and
# DEBUG in some_lib.some_special_func()
Info: some_local_func()
Debug: some_local_func()
Info: some_lib.some_lib_func()
Info: some_special_func()
Debug: some_special_func(): This message can be controlled at the function/method level.

也可以从some_special_func() 获取only 调试消息only。使用logging_tree.printout() 确定需要调整哪些日志记录级别以实现这一目标!

【讨论】:

【解决方案5】:

如果您正在使用日志,则静音日志非常简单。只需使用NullHandler - https://logbook.readthedocs.io/en/stable/api/handlers.html

>>> logger.warn('TEST')
[12:28:17.298198]  WARNING: TEST

>>> from logbook import NullHandler
>>> with NullHandler():
...    logger.warn('TEST')

【讨论】:

    猜你喜欢
    • 2013-08-14
    • 1970-01-01
    • 2016-11-13
    • 2019-05-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多