Python — Избегать передачи ссылки на регистратор между функциями?

#python #logging

#python #ведение журнала #python-ведение журнала

Вопрос:

У меня есть простой скрипт на Python, который использует встроенный logging .

Я настраиваю ведение журнала внутри функции. Базовая структура была бы примерно такой:

 #!/usr/bin/env python
import logging
import ...

def configure_logging():
    logger = logging.getLogger("my logger")
    logger.setLevel(logging.DEBUG)
    # Format for our loglines
    formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
    # Setup console logging
    ch = logging.StreamHandler()
    ch.setLevel(logging.DEBUG)
    ch.setFormatter(formatter)
    logger.addHandler(ch)
    # Setup file logging as well
    fh = logging.FileHandler(LOG_FILENAME)
    fh.setLevel(logging.DEBUG)
    fh.setFormatter(formatter)
    logger.addHandler(fh)
    return logger

def count_parrots():
    ...
    logger.debug??

if __name__ == '__main__':
    logger = configure_logging()
    logger.debug("I'm a log file")
    parrots = count_parrots()
  

Я могу нормально вызвать logger изнутри __main__ . Однако, как мне вызвать logger изнутри функции count_parrots()? Какой самый питонический способ обработки настройки такого регистратора?

Ответ №1:

Вы можете либо использовать корневой регистратор (по умолчанию) и, следовательно, функции уровня модуля logging.debug , … либо использовать свой регистратор в функции, используя его. Действительно, getLogger функция является заводской функцией с реестром (типа singleton), то есть она всегда возвращает один и тот же экземпляр для данного имени регистратора. Таким образом, вы можете получить свой регистратор в count_parrots, просто используя

 logger = logging.getLogger("my logger") 
  

в начале. Однако соглашение заключается в использовании иерархического имени с точками для вашего регистратора. Смотрите http://docs.python.org/library/logging.html#logging.getLogger

Редактировать:

Вы можете использовать декоратор, чтобы добавить поведение ведения журнала к вашим отдельным функциям, например:

 def debug(loggername):
    logger = logging.getLogger(loggername) 
    def log_(enter_message, exit_message=None):
        def wrapper(f):
            def wrapped(*args, **kargs):
                logger.debug(enter_message)
                r = f(*args, **kargs)
                if exit_message:
                    logger.debug(exit_message)
                return r
            return wrapped
        return wrapper
    return log_

my_debug = debug('my.logger')

@my_debug('enter foo', 'exit foo')
def foo(a, b):
    return a b
  

вы можете «жестко закодировать» имя регистратора и удалить закрытие верхнего уровня и my_debug.

Комментарии:

1. Хорошо, итак, я могу просто вызвать logging.getLogger в начале каждой функции, которая должна регистрироваться. Кажется немного расточительным и повторяющимся, не так ли? Достаточно справедливо. Или мне было бы лучше перейти на объектно-ориентированный подход и попытаться встроить все это в класс? (Это очень общий вопрос, который я знаю, я просто ищу, что сделано в мире Python).

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

3. Этот ответ показывает практически все, что не так с модулем Python logging

4. Мне любопытно, какие альтернативные варианты, по мнению людей, желательны для такого рода сквозного модуля? Я не могу понять, в чем проблема с logging.getLogger с точки зрения общего дизайна. Кажется довольно шаблонным материалом.

Ответ №2:

Вы можете просто сделать :

 logger = logging.getLogger("my logger") 
  

в вашем count_parrots() методе. Когда вы передаете имя, которое использовалось ранее (т. Е. «мой регистратор»), модуль ведения журнала вернет тот же экземпляр, который был создан, соответствующий этому имени.

Обновление: Из руководства по ведению журнала (подчеркнуто мной)

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

Ответ №3:

Типичный способ обработки ведения журнала — хранить регистратор для каждого модуля в глобальной переменной. Любые функции и методы в этом модуле затем просто ссылаются на тот же экземпляр регистратора.

Это кратко обсуждается во введении к руководству по предварительному ведению журнала в документации: http://docs.python.org/howto/logging.html#advanced-logging-tutorial

Вы можете передавать экземпляры регистратора в качестве параметров, но обычно это делается редко.

Комментарии:

1. Я думал, что стандартной практикой является использование logger=logging.getLogger(«logger.name «)

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

3. Ах. Я думал, вы имели в виду фактическое использование global ключевого слова.

4. @MatthewCornell Ты имеешь в виду logger = logging.getLogger(__name__) .

Ответ №4:

Меня смутило, как глобальные переменные работают в Python. Внутри функции вам нужно только объявить, global logger если вы делали что-то подобное logger = logging.getLogger("my logger") и надеялись изменить глобальную logger .

Итак, чтобы изменить ваш пример, вы можете создать глобальный объект logger в начале файла. Если ваш модуль может быть импортирован другим, вам следует добавить NullHandler , чтобы, если импортер библиотеки не хочет, чтобы ведение журнала было включено, у него не возникло никаких проблем с вашей библиотекой (ref).

 #!/usr/bin/env python
import logging
import ...

logger = logging.getLogger("my logger").addHandler(logging.NullHandler())

def configure_logging():
    logger.setLevel(logging.DEBUG)
    # Format for our loglines
    formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
    # Setup console logging
    ch = logging.StreamHandler()
    ch.setLevel(logging.DEBUG)
    ch.setFormatter(formatter)
    logger.addHandler(ch)
    # Setup file logging as well
    fh = logging.FileHandler(LOG_FILENAME)
    fh.setLevel(logging.DEBUG)
    fh.setFormatter(formatter)
    logger.addHandler(fh)

def count_parrots():
    ...
    logger.debug('counting parrots')
    ...
    return parrots

if __name__ == '__main__':
    configure_logging()
    logger.debug("I'm a log file")
    parrots = count_parrots()
  

Ответ №5:

Если вам не нужны сообщения журнала на вашей консоли, вы можете использовать минималистичный способ.

В качестве альтернативы вы можете использовать tail -f myapp.log для просмотра сообщений на консоли.

 import logging

logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', 
    filename='myapp.log', 
    level=logging.INFO)

def do_something():
    logging.info('Doing something')

def main():
    logging.info('Started')
    do_something()
    logging.info('Finished')

if __name__ == '__main__':
    main()
  

Ответ №6:

Вы можете указать logger в качестве аргумента count_parrots() Или, что бы я сделал, создать класс parrots и использовать logger в качестве одного из его методов.