@domanskiy

Как передать logging из импортированных модулей в основную программу?

Есть модуль conMySQL который коннектится к MySQL и формирует SQL запрос. на выходе словарь.
В основной программе MyProg я импортирую conMySQL и использую его.
В основной программе MyProg для ведения журнала ошибок использую модуль logging с записью в файл.
В оба: MyProg и conMySQL импортирован модуль logging
Как основной программе MyProg получить логи ошибок работы модуля conMySQL... используя logging в conMySQL?
  • Вопрос задан
  • 815 просмотров
Решения вопроса 1
trapwalker
@trapwalker Куратор тега Python
Программист, энтузиаст
Чтобы правильно использовать модуль logging, нужно во всех своих модулях содавать логгеры следующим образом:
import logging
log = logging.getLogger(__name__)

В модулях, которые не запускаются сами по себе, никакого другого кода инициализации и настройки логирования не требуется.
Если вы в своём модуле желаете различать два или более вида логов, то можно сделать как-то так:
log = logging.getLogger(__name__)
class MyModel:
    log = logging.getLogger(__name__ + '.MyModel')
    # ...

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

В главном запускаемом файле кроме обычного создания логгера как во всех модулях будет ещё и инициализация и настройка системы логирования.
В самом простом виде это что-то вроде такого:
if __name__ == '__main__':
    logging.basicConfig(stream=sys.stderr, level='INFO', format='%(asctime)s %(levelname)-7s %(message)s')

Тут говорится, что все логи уровня >= INFO будут в указанном формате направлены в stderr.

В более сложном случае вы можете загрузить настройку логирования из конфигурационного файла или описать кодом:
if __name__ == '__main__':
    # У вас может быть несколько разных способов форматировать код для разных мест:
    formatter_simple = Formatter(u'%(relativeCreated)08d %(levelname)-7s %(message)s')
    formatter_complex = Formatter(u'%(asctime)s %(levelname)-7s [%(filename)21s:%(lineno)-4d] %(message)s')
    # Несколько разных хендлеров для перехвата нужного вида сообщений и отправки в правильное место:
    handler_null      = logging.NullHandler()
    handler_screen    = handler(fmt=formatter_simple, stream=sys.stderr)
    handler_main_file = handler(
        fmt=formatter_complex,
        cls=logging.handlers.TimedRotatingFileHandler,
        when='midnight',
        backupCount=5,
        encoding='utf-8',
        filename=local_path('log/server.log'),
    )
    handler_errors_file = handler(
        fmt=formatter_complex,
        cls=logging.handlers.TimedRotatingFileHandler,
        when='midnight',
        backupCount=5,
        encoding='utf-8',
        filename=local_path('log/errors.log'),
        level='ERROR',
    )
    # А потом описываем сами логгеры:
    #   это корневой логер, пропускает все сообщения через себя насквозь и в то же отдаёт их своим хендлерам
    log_root       = logger(None, level='DEBUG', propagate=1, handlers=[handler_errors_file, handler_main_file, handler_screen])
    #   этот логер перехватывает логи торнадо-приложения, пропускает через себя и отдаёт хендлерам:
    log_app        = logger('tornado.application', level='DEBUG', propagate=1, handlers=[handler_errors_file, handler_main_file, handler_screen])
    #   этот собирает логи событий модели уровня выше или равного INFO (не отладочные) 
    #     и отдаёт соответствующим хендлерам, дальше для обработки свои сообщения не пускает
    log_events     = logger('app.model.events',     level='INFO',  propagate=0, handlers=[handler_errors_file, handler_events_file])
    #   этот логер съедает и отдаёт спец-хендлеру (он не показан выше, но должен быть) все логи http доступа
    log_websrv     = logger('tornado.access',                    level='DEBUG', propagate=0, handlers=[handler_websrv_file])
    #    этот логер глотает и гасит за ненадобностью все логи, которые генерит библиотека PIL при работе с PNG-файлами
    log_pil        = logger('PIL.PngImagePlugin',                level='INFO',  propagate=0, handlers=[handler_null])

Если сообщение не перехвачено и не остановлено специфичным логером, оно улетает корневому логеру.
Ну примерно как-то так.
Само собой всю эту настройку лучше вынести в отдельный модуль и запускать при старте главного файла.
Логеры вроде log_pil, log_websrv и прочих сохранены в отдельные переменные, но по факту в коде эти переменные нигде не используются. Их можно и не присваивать вовсе. В модуле логирования все логгеры регистрируются глобально в списке и поэтому в каждом модуле не требуется ничего импортировать, достаточно создать логгер по имени. По этому имени логер ищется среди созданных или автоматом создаётся новый. Имена логгеров используют точечную нотацию, и по ступеням имени их удобно фильтровать.

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

P.S.
Старайтесь не использовать CamelCase в именовании файлов проекта. Не смотря на то, что негласное это правило часто нарушается даже популярными библиотеками, всё же это часто создаёт лишнюю неразбериху и проблемы.
В частности много геморроя можно поиметь переименовав файл сменив лишь регистр символа под контролем версий при работе с репозиторием на разных файловых системах. В винде, к примеру, регистр в именах сохраняется, но системой не различается, а в линукс различается. Таким образом винда не увидит переименования, а линукс увидит. Это может породить адский треш.
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 1
@evgs89
В главной программе создаёте логгер, настраиваете его, как вам нужно.
Во всех импортируемых:

import logging

...

log = logging.get_logger('имя модуля') # можно __name__
log.debug("logging ok")

Все сообщения будут обработаны корневым логгером.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы