Python使用单个文件记录(功能名称,文件名,行号)

时间:2012-06-11 00:07:12

标签: python logging

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

以下是我所知道的:

  1. 获取函数名称,我可以使用function_name.__name__但我不想使用function_name(这样我就可以快速复制并粘贴一个通用的Log.info("Message")功能)。我知道这可以使用__func__宏在C中完成,但我不确定python。

  2. 获取文件名和行号,我已经看到(我相信)我的应用程序正在使用Python locals()函数,但语法中我并不完全清楚,例如:{{ 1}}我尝试使用类似options = "LOG.debug('%(flag)s : %(flag_get)s' % locals())的{​​{1}}来生成像LOG.info("My message %s" % locals())这样的东西。对此有任何意见吗?

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

  4. 我非常感谢任何帮助。

    谢谢!

3 个答案:

答案 0 :(得分:401)

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

import logging
logger = logging.getLogger('root')
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>]]

答案 1 :(得分:22)

您在这里有一些相关的问题。

我将从最简单的开始:(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

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

答案 2 :(得分:2)

funcnamelinenamelineno提供有关执行记录的最后一个功能的信息。

如果您有记录器的包装器(例如单例记录器),那么@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)