【问题标题】:Per-file/module logger in PythonPython中的每个文件/模块记录器
【发布时间】:2013-07-16 14:45:28
【问题描述】:

我有一些 Python 代码需要添加日志记录。

我一直更喜欢漂亮的大 C 宏外观语句,如“DEBUG()”、“ERROR()”等来进行日志记录。我觉得当跟踪点与实际代码在视觉上可以区分时,它使代码更易于阅读(没有对象)。

我还希望能够在每个模块级别设置日志记录级别。

我怎样才能制作一个名为“log”的模块来执行此操作(同时使用 Python 标准库日志记录模块)?

例如:

文件:main.py

# This imports LOG_MODULE_NAME, DEBUG, WARN, etc
from log import *
import my_module

LOG_MODULE_NAME("main")

log.set_level("main", log.LVL_DEBUG)
log.set_level("my_module", log.LVL_WARN)

if __name__ == "__main__":
    foo = my_module.myFunc(2)

DEBUG("Exiting main.py")

文件:my_module.py

from log import *

LOG_MODULE_NAME("my_module")


def myFunc(x):
    DEBUG("Entering function")
    if x != 1:
         WARN("I thought it would be 1")
    DEBUG("Exiting function")
    return x+1

我希望输出看起来像:

[WARN:my_module - my_module.py:9] I thought it would be 1
[DEBUG:main - main.py:11] Exiting main.py

【问题讨论】:

  • @dnozay 不是。 OP 正在尝试创建环绕 Logger 的函数。
  • 如果您阅读了我链接的问题,我们不需要关于在涉及不同模块时如何设置日志记录的第 n 个问题。
  • 请编辑您的问题。就目前而言,您当前的问题是如何编写整个模块。你试过写这个模块吗?你卡在哪些部分?使用 Python 日志记录模块?获取从中调用函数的模块的名称?按照您指定的方式设置格式化程序?
  • 我一直坚持让函数(DEBUG、WARN 等)的行为取决于调用它的文件/模块。重新考虑这一点,我想我可以用更直接的方式提出这个问题(这与日志记录无关)。我不能只在 log.py 中引用“_name_”,因为那将引用日志模块。另外,我希望能够将多个文件“注册”为一个“模块”。无论如何,在阅读 2rs2ts 的答案后,我认为仅使用全局名称是正确的方法。不过,我不确定“from x import *”的影响。

标签: python logging


【解决方案1】:

这些答案似乎跳过了一个非常简单的想法,即函数是一流的对象,它允许:

def a_function(n):
    pass

MY_HAPPY_NAME = a_function
MY_HAPPY_NAME(15) # which is equivalent to a_function(15)

除了我建议您不要这样做。 PEP8 coding conventions 被广泛使用,因为生命太短,我不得不以你喜欢的 COBOLy 方式读取标识符。

其他一些答案也使用global 语句,这在 Python 中几乎总是不需要。如果您正在制作模块级记录器实例,那么

import logging

log = logging.getLogger(__name__)

def some_method():
    ...
    log.debug(...)

是使用模块级变量log 的一种完美可行的简洁方式。你甚至可以做

log = logging.getLogger(__name__)
DEBUG = log.debug

def some_method():
    ...
    DEBUG(...)

但我保留称其为丑陋的权利。

【讨论】:

  • 我完全忘记了如何利用函数在 Python 中的工作方式。非常非常好的电话。我同意做 OP 正在做的事情是一个糟糕的主意,但是,嘿,他问。
  • 也许这是个坏主意...但是当各种“log.debug”、“log.warn”等行交织在一起时,我发现代码看起来更加混乱。从视觉上看,很难区分检测/跟踪与实际有用的代码。 OTOH、“DEBUG”、“WARN”等有时实际上使代码更易于阅读(仪表开始看起来像注释)。
  • @BobIsNotMyName 不过,您的方法消除了拥有多个具有不同配置的记录器的可能性。除非您将记录器作为参数添加到“宏”函数中,否则在这种情况下,它比方法调用更清楚您在做什么。
  • @msw,公平地说,您的答案与“标准”方法之间的唯一区别是DEBUG = log.debug
【解决方案2】:

要扩展@2rs2ts 概念,您也可以按模块登录,如Python logging HOWTO 所示。

命名记录器时使用的一个好的约定是在每个使用记录的模块中使用模块级记录器,命名如下:

logger = logging.getLogger(__name__) 

这意味着记录器名称跟踪包/模块层次结构,并且直观地从记录器名称记录事件的位置很明显

将所有文件的前 2 行设为:

import logging
logger = logging.getLogger(__name__) 

然后您就有了可轻松自定义的每个模块的日志记录。

您可以通过以下方式登录到不同的级别:

logger.debug("I just saw this %s" , var )
logger.info("I just saw this %s" , var )
logger.warn("I just saw this %s" , var )

请注意,我将字符串模板变量作为*arg 传递,而不是% 以立即模板化字符串。如果您的记录器设置为高于您登录的级别,您可以节省 CPU 周期。 logging 文档中有一些类似的技巧。

您可以设置日志记录级别以及在何处登录您运行的任何应用程序/脚本;日志模块将处理其余部分。

【讨论】:

    【解决方案3】:

    如果您想让记录器的名称指示使用它的模块,您可以使用logger.getLogger([name]) 模块函数,并将__name__ 作为其(可选)参数传递,如here 所述。

    如果您想使用DEBUG() 之类的名称,请在每个 文件中执行类似操作...

    LOG_MODULE_NAME = logging.getLogger(__name__)
    
    def DEBUG(msg):
        global LOG_MODULE_NAME
        LOG_MODULE_NAME.debug(msg)
    

    我不清楚全局命名空间在 Python 中的实际工作方式...this answer

    每个模块都有自己的“全局”命名空间。

    所以我猜你会这样,因为LOG_MODULE_NAME 不会在模块之间发生冲突。

    我相信这种方法会给你一个日志文件,其中的行看起来像这样:

    DEBUG:my_module:Entering function
    WARN:my_module:I thought it would be 1
    DEBUG:my_module:Exiting function
    DEBUG:root:Exiting main.py
    

    不够内省?您需要inpsect 模块,它会在程序运行时为您提供很多关于程序的information。例如,这将为您获取当前行号。

    您关于“在每个模块级别设置日志级别”的注释让我认为您想要getEffectiveLevel() 之类的东西。你可以试着像这样把它弄脏:

    LOG_MODULE_NAME = logging.getLogger(__name__)
    MODULE_LOG_LEVEL = log.LVL_WARN
    
    def DEBUG(msg):
        if MODULE_LOG_LEVEL = log.LVL_DEBUG:
            global LOG_MODULE_NAME
            LOG_MODULE_NAME.debug(msg)
    

    不过,我对 logging 模块的经验还不够,无法告诉您如何动态地设置每个模块的日志更改级别。

    【讨论】:

    • 我要补充一点,日志记录模块使您能够自定义日志输出以匹配您上面的请求。
    • 最初我发布了一个指向logging 教程的链接,但后来我意识到 OP 实际上已经提到了该模块,所以我删除了它。
    • 谢谢。是的,在发布此之前,我阅读了很多有关日志记录模块的信息。我只是想知道如何让“DEBUG()”具有基于调用它的文件的行为(或者更具体地说,基于在所述文件中传递给 LOG_MODULE_NAME() 的字符串 - 如我的示例代码中所示)。无论如何,在 Python 中处理全局变量的方式对我来说仍然不是 100% 清楚,但我认为这是要走的路。
    • @2rs2ts:您使用global 来实现模块范围绑定已经够糟糕的了,但是条件中的import 简直太可怕了。请参阅我的答案以了解更多 Pythonic 方法。
    • 我知道;这正是这个答案不能解决问题的原因; @msw 的答案在这方面更好,因为行号是发出 DEBUG() 调用的位置。
    【解决方案4】:

    更清洁的解决方案可以be found on this other question。更简洁的configure logging for a library 方法是在每个模块中使用这几行代码。

    import logging
    logging.getLogger(__name__).addHandler(logging.NullHandler())
    

    你的主脚本(例如我的script.py)负责:


    举个例子:

    def myFunc(x):
        DEBUG("Entering function")
        if x != 1:
             WARN("I thought it would be 1")
        DEBUG("Exiting function")
        return x+1
    

    以及您的预期输出:

    [WARN:my_module - my_module.py:9] I thought it would be 1
    [DEBUG:main - main.py:11] Exiting main.py
    

    我们如何确定DEBUG("Existing main.py") 发生在main.py:11 上?显而易见的答案是您不能,除非您检查框架并检查调用者是谁。如果您想在log.py 模块中定义DEBUG() 函数,那么您将无法获得正确的记录。函数与宏非常不同,您必须定义一个函数并且没有内联替换,因此您实际上不再处于同一框架中。

    只是为了好玩,我用introspection with the inspect module写了一个解决方案 (注意:不要将其作为生产代码运行),但现实情况是,为了回答挑战“这个函数在哪里调用,在哪一行?”需要做一些工作。

    • bar.py

      # file: bar.py
      # this is just a module to show DEBUG works when used in a different module
      from log import DEBUG
      
      def baz():
          DEBUG('oh hai from baz')
      
    • log.py

      # file: log.py
      import inspect
      import logging
      
      def DEBUG(msg, *args):
          curframe = inspect.currentframe()
          try:
              # http://docs.python.org/2/library/inspect.html#inspect.getouterframes
              _, filename, _, code, _, _ = inspect.getouterframes(curframe, 2)[1]
              logging.debug('[%s:%s] '+msg, filename, code, *args)
          finally:
              del curframe
      
    • 主脚本script.py

      # file: script.py
      # responsible for the logging setup.
      import logging
      from log import DEBUG
      from bar import baz
      
      def foo():
          DEBUG('oh hai')
      
      if __name__ == '__main__':
          logging.basicConfig()
          logging.root.setLevel(logging.DEBUG)
          foo()
          baz()
      

    我的解决方案的明显问题是我们只使用root 记录器:

    $ python script.py 
    DEBUG:root:[script.py:foo] oh hai
    DEBUG:root:[/Users/dnozay/Desktop/bar.py:baz] oh hai from baz
    

    鉴于所有这些元素,我不鼓励重新实现 logging 中已有的功能。

    【讨论】:

    • @msw,以提问的方式回答这个问题将助长不良做法。甚至 @2rs2ts 的答案也发生了变化,以促进良好的做法; logging.getLogger(__name__) 最佳实践。这个问题的明显答案是“不要那样做!请按照教程”。
    猜你喜欢
    • 2020-06-19
    • 1970-01-01
    • 2019-11-24
    • 1970-01-01
    • 1970-01-01
    • 2022-08-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多