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

use*_*425 96 python logging

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

这就是我所知道的:

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

我非常感谢任何帮助.

谢谢!

syn*_*tel 431

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

import logging
logger = logging.getLogger('root')
FORMAT = "[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s"
logging.basicConfig(format=FORMAT)
logger.setLevel(logging.DEBUG)
Run Code Online (Sandbox Code Playgroud)

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

logger.debug('your message') 
Run Code Online (Sandbox Code Playgroud)

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

[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>]]
Run Code Online (Sandbox Code Playgroud)

  • 这应该是答案! (45认同)
  • @hipoglucido 我认为它格式化了 20 个空格,右对齐。 (3认同)
  • @Outlier不,推荐的方法是通过`getLogger(__ name __)`来实现 (2认同)
  • 我有一个问题:在 Java 的某个地方,我读到不鼓励打印行号,因为需要额外的时间来确定从哪一行调用记录器。在python中,这不是真的吗? (2认同)
  • 无关紧要,但 `logging.getLogger('root')` 可能不是你所期望的,它不是 `root` 记录器,而是一个名为 'root' 的普通记录器。 (2认同)

Mat*_*kel 25

你在这里有一些相关的问题.

我将从最简单的开始:(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
    ))
Run Code Online (Sandbox Code Playgroud)

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

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

  • @synthesizerpatel的回答更有帮助. (3认同)

Kat*_*atu 15

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

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

结果如下:

[2022-04-25 11:00:50,885 main.py->loop():21]INFO: looping
Run Code Online (Sandbox Code Playgroud)

filename对于第 3 点,您可以在基本配置中使用以下命令记录到文件:

logging.basicConfig(format=FORMAT, level=logging.INFO, filename='main.log')
Run Code Online (Sandbox Code Playgroud)

但我更喜欢设置一个旋转日志文件,这样它就不会因 RotatingFileHandler 而不受控制地增长。在写入日志文件的同时它也会显示在控制台中

完整示例main.py

import logging                                                                                         
from logging.handlers import RotatingFileHandler                                                       
import time                                                                                            
                                                                                                       
#Setup logger                                                                                          
logger = logging.getLogger(__name__)                                                                   
FORMAT = "[%(asctime)s %(filename)s->%(funcName)s():%(lineno)s]%(levelname)s: %(message)s"             
logging.basicConfig(format=FORMAT, level=logging.INFO)                                                 
#Log to file                                                                                           
logging_filename = 'main.log'                                                                          
handler = RotatingFileHandler(logging_filename, maxBytes=1000000, backupCount=10) #10 files of 1MB each
handler.setFormatter(logging.Formatter(FORMAT))                                                        
logger.addHandler(handler)                                                                             
                                                                                                       
def main():                                                                                            
  while True:                                                                                        
    loop()                                                                                         
    time.sleep(1)                                                                                  
                                                                                                       
def loop():                                                                                            
  logger.info('looping')                                                                             
                                                                                                       
if __name__== "__main__":                                                                              
  main()                                                                                               
                                                                                                       
Run Code Online (Sandbox Code Playgroud)


Dev*_*cie 6

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)
Run Code Online (Sandbox Code Playgroud)

  • 从 Python 3.8 开始,“logging”类支持开箱即用的堆栈级别跳过:“log()”、“debug()”等方法现在接受“stacklevel”参数。请参阅[文档](https://docs.python.org/3/library/logging.html#logging.Logger.debug)。 (4认同)
  • 您的回答正是我解决问题所需要的。谢谢。 (2认同)