Python: наследование и логирование
Не знаю кто как, а я ленивый. Писать длинные строки кода, или повторять один и то-же код много раз в разных местах мне кажется неправильным и нерациональным. Это утверждение в полной мере относится и к записи отладочных сообщений. Если мне нужно организовать правильную запись логов в проекте, многочисленные мануалы рекомендуют использовать примерно такое:
В принципе,- всё ок, если делаем линейный скрипт или классы в одном файле. Можно использовать глобальную переменную, или, на худой конец, определять logger в каждом классе. А если классов,- сто? и в девятнадцати разных модулях? Лениво и неинтересно, давайте попробуем разные способы упростить себе жизнь:
Решение №1 "Логгер-переменная":
инициализировать логгер с помощью logger = logging.getLogger() и использовать во всех местах где хочется:
Быстро, экономно, но не интересно: зачем вообще заморачиваться с логгингом, если не использовать одну из самых лучших возможностей модуля,- фильтрация логов по источнику? Ведь в этом варианте у нас будет использоваться один и тот-же логгер для всех сообщений, с таким-же успехом можно написать функцию-обёртку над print() для разделения по уровням отладки или использовать print напрямую.
Решение №2 "Логгер как класс":
обернуть логгер и его методы в отдельный класс и использовать объект этого класса везде где нужно.
Ничем не лучше, чем глобальная переменная,- все вызовы записи в лог идут из одних и тех-же методов-обёрток, что сводит возможности фильтрации сообщений по источнику к нулю. Кроме того, это даже хуже, если учитывать промежуточный класс и его экземпляры. А даже если и заморочиться с реализацией синглетона,- не имеет смысла по соображениям, приведённым выше.
Решение №3 "Объектно-ориентированное логирование":
наследовать все классы от волшебного класса LoggingMix, написанного один раз и содержащего обёртки к методам экземпляра класса логирования. При этом логгер можно закопать в сам экземпляр класса-наследника, или даже сделать псевдо-Singleton на уровне корневого класса LoggingMix или классов-наследников. Что-бы не писать лишнего,- обёртки над методами логгера реализуем через лямбда-функции:
Тоже плохо: экземпляры дочерних классов становятся "тяжелее" на объект логгера, кроме того, при форматировании отладочных сообщений параметр %(lineno)s продолжает указывать на строку в суперклассе LoggingMix.
Решение №4 "Ныряем в ООП":
Предыдущее решение было почти правильным, но не доведённым до конца. Зачем нам методы-обёртки вызывающие методы экземпляра? Ведь эти методы уже есть у логгера, прикрутим тёплое к мягкому, в смысле используем нужные методы экземпляра логгера прямо в дочернем классе:
А вот это уже близко к истине. Наследование от такого класс позволяет использовать логгирование в экземплярах наследников вида self.debug(), кроме того не перекрываются номера строк. Кроме того, можно наследовать от такого класса практически где угодно, даже, например, в моделях или методах Django Class Based View:
import logging logger = logging.getLogger() ... logger.debug(<сообщение уровня DEBUG>) ...
В принципе,- всё ок, если делаем линейный скрипт или классы в одном файле. Можно использовать глобальную переменную, или, на худой конец, определять logger в каждом классе. А если классов,- сто? и в девятнадцати разных модулях? Лениво и неинтересно, давайте попробуем разные способы упростить себе жизнь:
Решение №1 "Логгер-переменная":
инициализировать логгер с помощью logger = logging.getLogger() и использовать во всех местах где хочется:
logger = logging.getLogger() ... def superfunc(*args): global logger logger.debug('superfunc!')
Быстро, экономно, но не интересно: зачем вообще заморачиваться с логгингом, если не использовать одну из самых лучших возможностей модуля,- фильтрация логов по источнику? Ведь в этом варианте у нас будет использоваться один и тот-же логгер для всех сообщений, с таким-же успехом можно написать функцию-обёртку над print() для разделения по уровням отладки или использовать print напрямую.
Решение №2 "Логгер как класс":
обернуть логгер и его методы в отдельный класс и использовать объект этого класса везде где нужно.
import logging class MyLogger(): def __init__(self): self.logger = logging.getLogger() def debug(self, msg): self.logger.debug(msg)
Ничем не лучше, чем глобальная переменная,- все вызовы записи в лог идут из одних и тех-же методов-обёрток, что сводит возможности фильтрации сообщений по источнику к нулю. Кроме того, это даже хуже, если учитывать промежуточный класс и его экземпляры. А даже если и заморочиться с реализацией синглетона,- не имеет смысла по соображениям, приведённым выше.
Решение №3 "Объектно-ориентированное логирование":
наследовать все классы от волшебного класса LoggingMix, написанного один раз и содержащего обёртки к методам экземпляра класса логирования. При этом логгер можно закопать в сам экземпляр класса-наследника, или даже сделать псевдо-Singleton на уровне корневого класса LoggingMix или классов-наследников. Что-бы не писать лишнего,- обёртки над методами логгера реализуем через лямбда-функции:
class LoggingMix(): def get_logger(self): if not hasattr(self.__class__, '__logger'): logger = self.__class__.__logger = logging.getLogger(self.__class__.__module__) return self.__class__.__logger logger = property( get_logger ) critical = lambda self,msg: self.logger.critical(msg) error = lambda self,msg: self.logger.error(msg) warning = lambda self,msg: self.logger.warning(msg) info = lambda self,msg: self.logger.info(msg) debug = lambda self,msg: self.logger.debug(msg)
Тоже плохо: экземпляры дочерних классов становятся "тяжелее" на объект логгера, кроме того, при форматировании отладочных сообщений параметр %(lineno)s продолжает указывать на строку в суперклассе LoggingMix.
Решение №4 "Ныряем в ООП":
Предыдущее решение было почти правильным, но не доведённым до конца. Зачем нам методы-обёртки вызывающие методы экземпляра? Ведь эти методы уже есть у логгера, прикрутим тёплое к мягкому, в смысле используем нужные методы экземпляра логгера прямо в дочернем классе:
class LoggingMix(): def __getattr__(self, name): if name in ['critical','error','warning','info','debug']: if not hasattr(self.__class__, '__logger'): self.__class__.__logger = logging.getLogger(self.__class__.__module__) return getattr(self.__class__.__logger, name) return super(LoggingMix, self).__getattr__(name)
А вот это уже близко к истине. Наследование от такого класс позволяет использовать логгирование в экземплярах наследников вида self.debug(), кроме того не перекрываются номера строк. Кроме того, можно наследовать от такого класса практически где угодно, даже, например, в моделях или методах Django Class Based View:
from mylogging import LoggingMix
from django.db import modelsclass ObjectWithLogging(LoggingMix, models.Model): def __init__(self): self.debug('init!') class CreateViewWithLogging(LoggingMix, CreateView): def dispatch(self, *args, **kwargs): self.debug('dispatch started!')
Дай тебе бог здоровья.)
ОтветитьУдалитьНичего более адекватного по применению логирования в более чем одном файле мною не было найдено. Эта статья очень помогла.