使用单个文件进行Python日志记录(函数名,文件名,行号)


109

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

这是我所知道的:

  1. 为了获得函数名,我可以使用,function_name.__name__但我不想使用function_name(这样我就可以Log.info("Message")在所有函数的主体中快速复制并粘贴泛型)。我知道这可以使用__func__宏在C语言中完成,但我不确定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. 我知道如何使用日志记录并向其添加处理程序以将其记录到文件中,但是我不确定是否可以使用单个文件来按项目中函数调用的正确顺序记录所有日志消息。

我将不胜感激任何帮助。

谢谢!


您可以使用进入python调试器import pdb; pdb.set_trace(),然后以交互方式逐步执行代码。这可以帮助您跟踪程序流。
马修·辛克尔

好想法!谢谢马特。获取问题中提到的日志仍然很有帮助,这样我就不必每次都进行调试。另外,您是否知道可以使用python的IDE来与Eclipse的Java一样好(使用ctrl + click即可进行函数定义),因此可以简化调试?
user1126425 2012年

Answers:


28

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

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

接下来:(2)。locals()提供当前范围的指示。因此,在没有其他参数的方法中,您将具有self作用域,该作用域包含对当前实例的引用。困扰您的技巧是使用字典作为%操作员的RHS的字符串格式。"%(foo)s" % bar将被替换为bar["foo"]is的任何值。

最后,您可以使用一些自省技巧,类似于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
    ))

这将记录传入的消息,以及(原始)函数名称,出现定义的文件名以及该文件中的行。看一下检查-检查活动对象以了解更多详细信息。

正如我在前面的评论中提到的那样,您还可以pdb通过插入行import pdb; pdb.set_trace()并重新运行程序来随时进入交互式调试提示符。这使您可以逐步检查代码,并根据需要检查数据。


谢谢马特!我将尝试此自动登录功能。我对使用dict作为%运算符的RHS有点困惑:'%(foo)s : %(bar)s'是否还会打印bar["foo"]的值?还是与您的示例有所不同?
user1126425 2012年

基本上,表单中的所有内容都将%(<foo>)s替换为dict中引用的对象的值<foo>docs.python.org/library/stdtypes.html#string-formatting
Matthew Schinckel

3
@synthesizerpatel的答案更有帮助。
1

504

正确的答案是使用已经提供的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>]]

61
这应该是答案!
user3885927

1
很好。添加一件事,我们可以动态地将日志文件命名为与代码文件相同吗?例如:我尝试了logging.basicConfig(filename =“%(filename)”,format = FORMAT)来动态获取文件名,但是它采用了静态值。有什么建议吗?
2014年

2
@Outlier不,推荐的方法是通过getLogger(__name__)
farthVader

2
我有一个问题:在Java的某个地方,我读到不建议打印行号,因为这会花费额外的时间来确定从哪个行开始记录器。在python中,这不是真的吗?
McSonk

2
无关紧要,但logging.getLogger('root')不是您所期望的,它不是root记录器,而是名称为“ root”的普通记录器。
0xc0de

5

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)

1
您的回答正是我解决问题所需要的。谢谢。
错误–句法Re悔,

因为Python 3.8的logging类支持堆栈级跳外的开箱:方法,如log()debug()等现在接受stacklevel的说法。请参阅文档
全速
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.