Python Logging (имя функции, имя файла, номер строки) с использованием одного файла

Я пытаюсь узнать, как работает приложение. И для этого я вставляю команды отладки в первую строку каждого тела функции с целью регистрации имени функции, а также номера строки (внутри кода), где я отправляю сообщение на выход журнала. Наконец, поскольку это приложение содержит множество файлов, я хочу создать один файл журнала, чтобы лучше понять поток управления приложением.

Вот что я знаю:

  • для получения имени функции, я могу использовать function_name.__name__, но я не хочу использовать имя_функции (чтобы я мог быстро скопировать и вставить общий Log.info("Message") в тело всех функций). Я знаю, что это можно сделать в C с помощью макроса __func__, но я не уверен в python.

  • для получения имени файла и номера строки, я видел, что (и я считаю, что) мое приложение использует функцию Python locals(), но в синтаксисе, о котором я не совсем понимаю, например: options = "LOG.debug('%(flag)s : %(flag_get)s' % locals()) и Я попробовал его, используя LOG.info("My message %s" % locals()), который создает что-то вроде {'self': <__main__.Class_name object at 0x22f8cd0>}. Любой вход по этому просьбе?

  • Я знаю, как использовать 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
    ))

Это приведет к регистрации переданного сообщения, а также имени функции (оригинала), имени файла, в котором отображается определение, и строки в этом файле. Посмотрите проверьте - проверьте живые объекты для более подробной информации.

Как я уже упоминал в своем комментарии ранее, вы также можете в любой момент добавить в приглашение pdb интерактивную отладку, вставив строку import pdb; pdb.set_trace() и повторно запустив свою программу. Это позволяет вам пройти через код, проверяя данные по вашему выбору.

Ответ 2

Правильный ответ для этого - использовать уже предоставленную переменную 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') 

Пример вывода из script Я сейчас работаю:

[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>]]

Ответ 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)