【问题标题】:Python Logging (function name, file name, line number) using a single file使用单个文件的 Python 日志记录(函数名、文件名、行号)
【发布时间】:2012-06-13 23:02:34
【问题描述】:

我正在尝试了解应用程序的工作原理。为此,我将调试命令作为每个函数主体的第一行插入,目的是记录函数的名称以及向日志输出发送消息的行号(在代码中)。最后,由于这个应用程序包含许多文件,因此我想创建一个日志文件,以便更好地了解应用程序的控制流程。

这是我所知道的:

  1. 为了获取函数名,我可以使用function_name.__name__,但我不想使用function_name(这样我可以快速复制并粘贴通用Log.info("Message") 到所有函数的主体中)。我知道这可以在 C 中使用 __func__ 宏来完成,但我不确定 python。

  2. 为了获取文件名和行号,我已经看到(并且我相信)我的应用程序正在使用 Python locals() 函数,但使用的语法我并不完全了解,例如:options = "LOG.debug('%(flag)s : %(flag_get)s' % locals()) 和我尝试使用类似LOG.info("My message %s" % locals()) 产生类似{'self': <__main__.Class_name object at 0x22f8cd0>} 的东西。请对此有任何意见?

  3. 我知道如何使用日志记录并向其添加处理程序以记录到文件,但我不确定是否可以使用单个文件以项目中函数调用的正确顺序记录所有日志消息。

如果有任何帮助,我将不胜感激。

谢谢!

【问题讨论】:

  • 您可以使用import pdb; pdb.set_trace() 进入python 调试器,然后以交互方式逐步执行代码。这可能有助于您跟踪程序流程。
  • 好主意!谢谢马特。获取问题中提到的日志仍然会有所帮助,这样我就不必每次都进行调试。另外,你知道我可以利用它来简化调试吗?

标签: python logging


【解决方案1】:

这里有几个无关紧要的问题。

我将从最简单的开始:(3)。使用logging,您可以将所有调用聚合到单个日志文件或其他输出目标:它们将按照它们在进程中发生的顺序排列。

接下来:(2)。 locals() 提供当前范围的字典。因此,在没有其他参数的方法中,self 在范围内,其中包含对当前实例的引用。正在使用的让您感到困惑的技巧是使用 dict 作为 % 运算符的 RHS 的字符串格式。 "%(foo)s" % bar 将被替换为 bar["foo"] 的任何值。

最后,您可以使用一些自省技巧,类似于pdb 使用的可以记录更多信息的技巧:

def autolog(message):
    "Automatically log the current function details."
    import inspect, logging
    # Get the previous frame in the stack, otherwise it would
    # be this function!!!
    func = inspect.currentframe().f_back.f_code
    # Dump the message + the name of this function to the log.
    logging.debug("%s: %s in %s:%i" % (
        message, 
        func.co_name, 
        func.co_filename, 
        func.co_firstlineno
    ))

这将记录传入的消息,加上(原始)函数名、定义出现的文件名以及该文件中的行。请查看inspect - Inspect live objects 了解更多详情。

正如我在前面的评论中提到的,您也可以随时通过插入import pdb; pdb.set_trace() 行进入pdb 交互式调试提示,然后重新运行您的程序。这使您可以单步执行代码,根据您的选择检查数据。

【讨论】:

  • 谢谢马特!我会试试这个自动记录功能。我对使用 dict 作为 % 运算符的 RHS 有点困惑:'%(foo)s : %(bar)s' 也会打印bar["foo"] 的值吗?还是与您的示例有所不同?
  • 基本上,%(<foo>)s 形式的所有内容都替换为<foo> 字典中引用的对象的值。 docs.python.org/library/stdtypes.html#string-formatting 有更多示例/详细信息
  • @synthesizerpatel 的回答更有帮助。
【解决方案2】:

对此的正确答案是使用已经提供的funcName变量

import logging
logger = logging.getLogger(__name__)
FORMAT = "[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s"
logging.basicConfig(format=FORMAT)
logger.setLevel(logging.DEBUG)

然后在任何你想要的地方,只需添加:

logger.debug('your message') 

我正在处理的脚本的示例输出:

[invRegex.py:150 -          handleRange() ] ['[A-Z]']
[invRegex.py:155 -     handleRepetition() ] [[<__main__.CharacterRangeEmitter object at 0x10ba03050>, '{', '1', '}']]
[invRegex.py:197 -          handleMacro() ] ['\\d']
[invRegex.py:155 -     handleRepetition() ] [[<__main__.CharacterRangeEmitter object at 0x10ba03950>, '{', '1', '}']]
[invRegex.py:210 -       handleSequence() ] [[<__main__.GroupEmitter object at 0x10b9fedd0>, <__main__.GroupEmitter object at 0x10ba03ad0>]]

【讨论】:

  • 这应该是答案!
  • 太棒了..要补充一点,我们可以动态地将日志文件命名为与代码文件相同吗?例如:我尝试 logging.basicConfig(filename="%(filename)", format=FORMAT) 动态获取文件名,但它采用静态值。有什么建议吗?
  • @Outlier 不,推荐的实现方式是通过getLogger(__name__)
  • 我有一个问题:在 Java 的某个地方,我读到不鼓励打印行号,因为需要额外的时间来确定从哪一行调用记录器。在 python 中这不是真的?
  • 无关紧要,但logging.getLogger('root') 可能不是您所期望的,它不是root 记录器,而是一个名为“root”的普通记录器。
【解决方案3】:

funcname、linename 和 lineno 提供有关执行日志记录的最后一个函数的信息。

如果您有记录器的包装器(例如单例记录器),那么@synthesizerpatel 的答案可能不适合您。

要找出调用堆栈中的其他调用者,您可以这样做:

import logging
import inspect

class Singleton(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class MyLogger(metaclass=Singleton):
    logger = None

    def __init__(self):
        logging.basicConfig(
            level=logging.INFO,
            format="%(asctime)s - %(threadName)s - %(message)s",
            handlers=[
                logging.StreamHandler()
            ])

        self.logger = logging.getLogger(__name__ + '.logger')

    @staticmethod
    def __get_call_info():
        stack = inspect.stack()

        # stack[1] gives previous function ('info' in our case)
        # stack[2] gives before previous function and so on

        fn = stack[2][1]
        ln = stack[2][2]
        func = stack[2][3]

        return fn, func, ln

    def info(self, message, *args):
        message = "{} - {} at line {}: {}".format(*self.__get_call_info(), message)
        self.logger.info(message, *args)

【讨论】:

  • 您的回答正是我解决问题所需要的。谢谢。
  • 从 Python 3.8 开始,logging 类支持开箱即用的堆栈级别跳过:log()、debug() 等方法现在接受 stacklevel 参数。请参阅docs。
【解决方案4】:

我喜欢@synthesizerpatel 给出的答案,但我更喜欢这种格式以包含关卡名称

FORMAT = "[%(filename)s->%(funcName)s():%(lineno)s]%(levelname)s: %(message)s"
logging.basicConfig(format=FORMAT, level=logging.INFO)

结果如下:

[main.py->loop():62]INFO: looping

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-11-18
    • 2014-10-21
    • 1970-01-01
    • 1970-01-01
    • 2018-04-16
    • 1970-01-01
    • 1970-01-01
    • 2020-08-20
    相关资源
    最近更新 更多