Реєстрація Python (назва функції, ім'я файлу, номер рядка) за допомогою одного файлу


109

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

Ось що я знаю:

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

  2. для отримання імені файлу та номера рядка я бачив, що (і я вважаю, що) моя програма використовує locals()функцію Python, але в синтаксисі, про який я не знаю, наприклад: 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(), а потім інтерактивно переглядати код. Це може допомогти вам простежити потік програми.
Метью Шинкель

Чудова ідея! Дякую Метт. Було б корисно отримати журнал, як згадується у запитанні, щоб мені не довелося кожен раз налагоджувати. Крім того, ви знаєте про IDE для python, який так само хороший, як Eclipse для Java (ctrl + клацніть, щоб визначити функцію), яким я можу скористатися, щоб полегшити налагодження?
user1126425

Відповіді:


28

У вас тут є кілька незначних питань.

Почну з найпростішого: (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()та повторно запустивши програму. Це дає змогу переглядати код, перевіряючи дані за вибором.


Дякую Метт! Я спробую цю функцію autolog. У мене невелика плутанина щодо використання dict як RHS% operator: Чи '%(foo)s : %(bar)s'також надрукується значення bar["foo"]'? Або це дещо інше, ніж ваш приклад?
користувач1126425

В основному все форми %(<foo>)sзамінюється значенням об'єкта, на який посилається в диктаті <foo>. Є більше прикладів / деталей на docs.python.org/library/stdtypes.html#string-formatting
Matthew Schinckel

3
Відповідь @synthesizerpatel набагато корисніша.
Jan

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
Це повинна була відповісти!
користувач3885927

1
Чудово .. Одне додати, чи можемо ми назвати файл журналу таким же, як кодовий файл динамічно? наприклад: Я спробував logging.basicConfig (filename = "% (filename)", format = FORMAT), щоб прийняти ім'я файлу динамічно, але воно прийняло статичне значення. будь-яка пропозиція?
Outlier

2
@Outlier Ні, рекомендований спосіб досягти цього - черезgetLogger(__name__)
farthVader

2
У мене є одне запитання: десь у Java я прочитав, що друк номера рядка не рекомендується, оскільки потрібен додатковий час, щоб зрозуміти, з якого рядка викликається реєстратор. У python це неправда?
McSonk

2
Неважливо, але, logging.getLogger('root')напевно, ви не очікуєте, що це не rootреєстратор, а звичайний реєстратор з назвою "root".
0xc0de

5

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)

1
Ваша відповідь була саме тим, що мені потрібно було вирішити свою проблему. Дякую.
Помилка - синтаксичне

Оскільки Python 3.8, loggingклас підтримує стек рівня пропуску поза коробки: методи , такі як log(), debug()і т.д. Тепер прийняти stacklevelаргумент. Дивіться документи .
amain
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.