Python – язык программирования, который поддерживает множество разнообразных и полезных инструментов для разработки программного обеспечения. Он часто используется для бизнес-приложений, а также для небольших игр. В крупных проектах может служить в качестве вспомогательного инструмента.

Зная возможности Python, программист сможет понять, что именно он способен реализовать в своих приложениях. Сегодня предстоит познакомиться с таким процессом, как логирование (Python logging). Также необходимо разобраться с тем, что собой представляют логи. Дополнительно будут внимательно изучены методы и атрибуты, позволяющие осуществлять логирование.

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

Определение

Python logging – это логирование или журналирование. Так называется средства отслеживания событий, происходящих в процессе запуска того или иного программного обеспечения.

Разработчик при использовании logging добавляет свой код вызова журналирования, чтобы указать, что произошли те или иные события. Событие определяется в качестве сообщения-описания, опционально содержащее изменяющиеся данные – те, которые потенциально могут меняться (различаться) для каждого наступившего события. Каждое событие имеет так называемую важность. Разработчик придает ее каждому «происшествию». Важность – это еще и уровень/серьезность.

Использование – когда нужно логирование

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

Вот таблица, которая поможет понять, когда целесообразно использовать print() для выполнения тех или иных задач, а когда – пользоваться logging:

Выполняемая процедураОптимальный инструмент для решения поставленной задачи
Вывод информации в консоли для обычного использования сценария командной строки или программного обеспеченияprint()
Вывод сообщений о событиях, которые происходят в процессе нормального функционирования программного обеспечения. Примером может служить мониторинг состояний или отслеживание неисправностейLogging.info(). Если необходимо добиться очень подробного вывода в целях дальнейшей диагностики, требуется использовать функцию logging.debug()
Вывод предупреждений о событиях в процессе выполненияWarnings.warn() – в коде библиотеки используется, если проблема может быть устранена, а клиентское приложение – должно быть изменено, чтобы исключить предупреждениеLogging.warning() – используется, если клиентская программа никак не реагирует на ситуацию, но событие все равно требует фиксации 
Отображение сообщения об ошибке, которая связана с событием во время исполненияПрограммист должен воспользоваться вызовом исключения
Вывод сообщения о подавлении ошибки без обращения к вызову исключений. В качестве примера можно привести ситуацию с нахождением обработчика ошибок в длительном серверном процессеВ зависимости от ошибки и области применения программного обеспечения могут использоваться функции logging.error(), logging.exception(), logging.critical()

Предложенная информация поможет понять, в каких случаях print() использовать не рекомендуется.

Уровни журналирования

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

В Python существуют шесть уровней логирования. Каждый из них получил собственное целое число, указывающее на важность лога.

Уровень логаОписаниеЧисло, указывающее на важность
NOTSETИспользуется при создании средства ведения журнала. Приводит к обработке всех сообщений, если логгер является корневым, или к делегации родительскому объекту – если логгер не является корневым. Корневой логгер создается с уровнем WARNING.0
DEBUGВывод подробной информации. Обычно соответствующие данные представляют интерес только для диагностики ошибок и неполадок.10
INFOПодтверждение нормальной (ожидаемой) работы программного продукта20
WARNINGСсылка на то, что в процессе работы произошло что-то неожиданное. Данный уровень также указывает на вероятность появления проблем в программном обеспечении в будущем. Примером может служить сообщение о недостаточном количестве пространства на жестком диске. Исходное приложение в соответствующем случае все еще работает нормально.30
ERRORВозникновение более серьезных сбоев, при которых программное обеспечение не смогло выполнить ту или иную функцию.40
CRITICALУровень, ссылающийся на очень серьезную (критическую) ошибку. Он указывает на то, что программное обеспечение больше не может продолжать дальнейшее функционирование.50

По умолчанию журналирование получает уровень WARNING. Это значит, что отслеживаться будут только события этого уровня или выше при условии, что пакет the logging Python не настроен на другие операции.

Все представленные уровни являются последовательно упорядоченными. Это значит, что актуально следующее выражение:

DEBUG < INFO < WARNING < ERROR < CRITICAL

Отдельного внимания требует NOTSET. Этот уровень будет рассмотрен более подробно, но чуть позже. Для начала необходимо запомнить, что отслеживаемые события могут быть обработаны различными способами. Наиболее простым вариантом является их вывод на консоль. Второй подход к реализации поставленной задачи – это запись логов в отдельный файл на жестком диске устройства. Эти операции тоже будут рассмотрены более подробно, но сначала предстоит получше познакомиться с главным модулем логирования Python.

Модуль logging

The logging Python – это модуль, который определяет функции и классы, отвечающие за гибкую систему журналирования. Он активно используется как программистами-новичками, так и опытными специалистами. В основном применяется большинством сторонних библиотек Python. За счет этого the logging может интегрировать логи с сообщениями разнообразных библиотек рассматриваемого языка для формирования единого журнала логов в итоговом программном обеспечении.

Основным преимуществом API-журналирования является то, что в логировании могут участвовать любые Python-модули. Это дает возможность включать собственные сообщения в программу, которые будут интегрированы с сообщениями от сторонних модулей.

The logging – модуль, оснащенный большой функциональностью. Он является достаточно быстрым. Распространяется в составе стандартной Python-библиотеки.

Для добавления упомянутого модуля необходимо написать следующую строчку в исходном коде программного обеспечения:

Import logging

Перед активным использованием журналирования необходимо ознакомиться с его классами и методами. Эта информация представлена ниже.

Logger-объекты

Логгеры имеют разнообразные атрибуты и методы. Для них не допускается создание экземпляров напрямую. Вместо этого разработчику необходимо вызвать функцию logger.getLogger(name) на уровне модуля. Несколько вызовов getLogger() с одним и тем же именем будут всегда производить возврат ссылки на один и тот же logger-объект.

Name – это потенциально иерархическое значение, которое разделяется точками. Точки могут отсутствовать, если имя состоит из одного слова. Представленные ниже логгеры размещаются в порядке иерархии. Они – потомки логгеров, размещенных в списке позициями выше.

Пример – логгер с именем foo. Его потомками будут foo.bar, foo.bam, foo.bar.baz. Иерархия имен логгеров будет точно такой же, как и иерархия Python-пакетов.

Далее будут представлены методы класса logging.loder. У этого класса есть разнообразные методы и атрибуты. Он является основным в рассматриваемом модуле.

Propagate

Если у атрибута устанавливается истинное значение, события, которые были зарегистрированы в соответствующем логгере, передаются обработчикам более высокого уровня (предкам) дополнительно к любым обработчикам, подключенным к данному логгеру. Сообщения будут передаваться непосредственно обработчикам логгеров предков – ни фильтры, ни их уровни не принимаются во внимание.

Если атрибут propagate принимает значение false, сообщения журнала передаваться обработчикам логирования не будут. По умолчанию для соответствующего атрибута устанавливается значение True.

setLevel(level)

Используется для установки уровня лога. Сообщения журнала, которые менее важные, чем установленный параметр level, будут проигнорированы системой. Если уровень серьезности у сообщения равен или больше level, они будут выводиться обработчиком (или обработчиками), если для его (их) уровней не задан более высокий уровень серьезности, чем level.

В процессе создания журналирования устанавливается уровень NOTSET. Это приводит к тому, что все сообщения будут обрабатываться согласно указанным в таблице выше инструкциям.

«Делегирование родителю» – это термин, который означает, что, если у логгера установлен уровень NOTSET, цепочка его логгеров-предков будет просматриваться до тех пор, пока не будет обнаружен предок с отличным от NOTSET уровнем. Также просмотр заканчивается при достижении корня.

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

Если достигнут корень, но у него установлен уровень NOTSET, все сообщения будут обрабатываться системой. Иначе корневой уровень рассматривается в качестве эффективного.

IsEnablefFor(level)

Используется для указания факта обработки имеющимся логгером сообщения с серьезностью level. Здесь происходит следующее:

  1. Метод осуществляет проверку уровня модуля, установленного logging.disable(level).
  2. После соответствующей проверки определяется эффективный уровень. Делается это при помощи getEffectiveLevel().

Существуют и другие интересные и полезные методы рассматриваемого класса. Каждый из них должен быть изучен перед непосредственным журналированием.

getEffectiveLevel()

Помогает указать эффективный уровень для имеющегося журнала (логгера). Если значение соответствующего параметра отличается от NOTSET, а также оно было установлено через setLevel(), оно будет возвращено. В противном случае иерархия будет возвращаться к корню до обнаружения значения, отличного от NOTSET. В конечном итоге соответствующий параметр тоже будет возвращен.

Возвращаемое значение – это целое число. Обычно оно представлено logging.DEBUG, logging.INFO и так далее.

getChild(suffix)

Отвечает за возврат логгера-потомка заданного журнала, как определено суффиксом. Это значит, что logging.getLogger(‘abc’).getChild(‘def.ghi’) будет возвращать тот же логгер, что и logging.getLogger(‘abc.def.ghi’).

Соответствующий метод удобен, когда родительский логгер именуется с использованием отличной от литеральной строки элементом. Примером может служить __name__.

Debug(msg), *args, **kwargs)

Метод регистрации сообщений с DEBUG-уровнем. Здесь:

  • msg – это строка формата сообщения;
  • args – аргументы, которые будут объединены в msg с использованием оператора форматирования строки;
  • kwargs – проверка четырех аргументов. К ним относят exc_info, stack_info, extra, stacklevel.

Если exc_info не имеет значения False, информация об исключении будет добавлена в сообщение журнала. При предоставлении кортежа исключения или экземпляра исключения соответствующие элементы будут успешно использованы. Иначе для получения данных об исключении нужно вызвать sys.exc_info().

Stack_info – это еще один необязательный ключевой аргумент. По умолчанию у него установлено значение False. Если оно меняется на True, данные о стеке будут добавлены к logging-сообщению, включая фактический вызов логирования.

Stacklevel имеет значение по умолчанию 1. Если оно больше, при вычислении номера строки и имени функции, заданных в LogRecord, будет пропускаться соответствующее количество фреймов стека.

Extra – аргумент, который может быть использован для передачи словаря, используемого для заполнения __dict__ LogRecord, сформированного для the logging Python.

Info/warning/error/critical(msg, *args, **kwargs)

Info отвечает за регистрацию сообщения с INFO-уровнем в журнале. Все аргументы здесь будут интерпретироваться в качестве debug().

При помощи методов error и critical будут регистрироваться сообщения с соответствующими уровнями. Аргументы, как и в предыдущем случае, интерпретируются в виде debug().

Отдельного внимания требует warning. Отвечает за регистрацию сообщений WARNING-уровня с аргументами debug(). Но в the Python есть устаревший метод для аналогичной операции. Он носит название warn. Задействовать его в программном коде не рекомендуется, хоть и принцип работы у него точно такой же, как и у метода warning.

Log(level, msg, *args, **kwargs)

Метод, который осуществляет запись в установленный логгер сообщения с целочисленным level-уровнем. В нем все остальные параметры (аргументы) будут интерпретированы как debug().

Exception(msg, *args, **kwargs)

Метод, который используется для регистрации сообщений с ERROR-уровнем в logging. Аргументы тут будут интерпретированы как debug().

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

addFilter(filter)

Используется для добавления указанного фильтра (filter) к логгеру.

removeFilter(filter)

Удаляет заданный фильтр из логгера.

Filter(record)

Используется, если запись должна пройти обработку. Этот метод позволяет применить фильтры Python logger к записи и вернуть True-значение.

Просмотр фильтров осуществляется поочередно. Происходит это до тех пор, пока один из них не вернет ложное значение. Если этого так и не произошло, запись будет передана обработчикам. В противном случае дальнейшая ее обработка не производится.

addHandler(hdlr)

Метод, добавляющий указанный обработчик к logging.

removeHandler(hdlr)

Используется для удаления обработчика из logging.

findCaller(stack_info=False, stacklevel=1)

Метод, который:

  1. Обнаруживает исходник имени файла и номер строки.
  2. Возвращает имя файла, номер строки, имя функции и информацию о стеке в виде кортежа из 4-х элементов.
  3. Если stack_info не является TRUE, система вернут None.

Stacklavel будет передан из кода, который отвечает за возврат debug() и другие API. При значении более единицы, избыток используется для пропуска стековых фреймов перед определением возвращаемых значений. Этот прием особо полезен при вызове logging API из кода помощника/оболочки, чтобы информация в журнале событий относилась не к коду помощника/оболочки, а к коду, который отвечает за его вызов.

Handle(record)

Метод, который будет обрабатывать запись и передавать ее всем обработчикам, имеющим связь с логгером и его предками. Это происходит до тех пор, пока система не обнаружит propagate=False.

Соответствующий метод используется для записей, полученных из сокета, а также записей, созданных локально. Фильтрация на уровне logging Python производится при помощи filter().

makeRecord(name. Level, fn, ino, msg, args, exc_info, func-None, extra=None, sinfo=None)

Фабричный метод. Используется для переопределения подкласса для создания специализированных экземпляров LogRecord.

hasHandlers()

Используется для проверки факта настройки для логгеров тех или иных обработчиков. Процедура выполняется путем поиска обработчиков в логгере и его родительских компонентах. Значение True возвращается, если обработчик найден. В противном случае метод возвращает False.

HasHandlers() прекращает иерархический поиск тогда, когда система обнаруживает logger с атрибутом propagate, установленным в False.

Форматирование журнала

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

С помощью форматирования журнала можно понять:

  • когда была сделана соответствующая запись;
  • из какого участка приложения поступило сообщение;
  • потоки и процессы.

Форматирование журнала имеет огромное значение для отладки многопоточных проектов. Вот один из простейших примеров ее реализации. Речь идет о форматировании записи «hello world»:

Логирование в Python

В logging соответствующая информация будет отображена так:

Логирование в Python

Теперь можно более подробно рассмотреть другие важные аспекты logging.

Обработчик журналов

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

Каждый обработчик поддерживает два ключевых поля:

  1. Форматер. Он отвечает за добавление в лог контекстной информации.
  2. Уровень лога. Служит для фильтрования журналов низшего уровня. Обработчик INFO-уровня не будет работать с журналами DEBUG-уровня.

Стандартная библиотека Python включает в себя обработчики, которых обычно достаточно для работы с приложениями. К ним относят StreamHandler и FileHandler.

Логирование в Python

Выше можно увидеть программный код их реализации.

Диспетчер журналирования

Диспетчер журналирования – то, что будет использоваться в программных кодах чаще всего. Этот элемент также является основным источником наибольших проблем в процессе разработки программного обеспечения.

Диспетчер журналирования может быть создан при помощи такого кодового фрагмента:

Логирование в Python

Он включает в себя три ключевых поля:

  1. Распространение. Это то, что определяет необходимость обработки logging (лога) родительским диспетчером. В настройках по умолчанию соответствующая опция (функция) является активной.
  2. Уровень. Здесь ситуация обстоит точно так же, как и в случае с обработчиком: уровень отвечает за фильтрацию менее важных журналов. Отличие заключается в том, что уровень будет проверяться исключительно в дочерних диспетчерах. Если осуществляется распространение вверх, уровень учитываться не будет.
  3. Список обработчиков, в которые необходимо направить попавшую в диспетчер запись. С помощью этого параметра удастся гибко настраивать обработку сообщений. В качестве примера можно создать обработчик, который будет записывать все, что имеет уровень DEBUG. А затем – обработчик для электронной почты – он будет работать только с CRITICAL-уровнем. Взаимоотношения диспетчера с обработчиками строятся, базируясь на схеме «издатель–потребитель». Запись передается всем обработчикам, стоит ей пройти уровневую проверку в диспетчере.

Имя logging диспетчера является уникальным. Это значит, что, если диспетчер с именем «toto» уже создан, последующее обращение к вызову logging.getLogger(“toto”) будет возвращать один и тот же объект.

Логирование в Python

Logging диспетчеры выстраиваются в соответствии с иерархией. На самой вершине будет находиться корневой диспетчер. Получить к нему доступ поможет logging.root(). Он вызывается при использовании методов вроде logging.warning или logging.debug. По умолчанию корневой журнал получает WARNING-уровень. Каждый журнал с меньшим уровнем будет игнорироваться.

Обработчик по умолчанию для корневого диспетчера создается автоматически. Это происходит при добавлении первой записи в журнал, выше WARNING-уровня. Обращаться к корневому диспетчеру не рекомендуется напрямую. То же самое касается методов вроде logging.debug().

Если создается новый диспетчер, в качестве родительского будет установлен корневой:

Логирование в Python

«Запись с точкой» используется диспетчером, когда есть объект, который будет дочерним по отношению к другому. Пример – объект «a.b», выступающий дочерним по отношению к «a». Этот принцип действует только при существовании «a». В противном случае родительским будет установлен корневой диспетчер.

Логирование в Python

В процессе работы диспетчера осуществляются следующие операции:

  1. Запись проходит фильтр по уровню.
  2. Проверяется не действительный, а эффективный уровень. Он обычно совпадает с диспетчерским.
  3. Если используется NOTSET-уровень, он будет исключением. Эффективный level будет равен level-значению первого диспетчера, расположенного выше в вертикали наследования.

По умолчанию NOTSET-уровень присваивается новому диспетчеру в logging. У корневого он будет равен WARNING. Это приводит к тому, что эффективный уровень нового тоже окажется WARNING.

Диспетчерский уровень при работе с logging используется разработчиками для пропуска записей, которые будут игнорироваться системой, если их уровни недостаточно высоки.

Способы записи

Logging в общих чертах теперь понятен. Можно ознакомиться с несколькими примерами журналирования. Ранее было сказано, что допустимо сохранение путем вывода информации в консоль, а также записью в отдельный файл. Эти концепции логирования приведены ниже.

Вывод в консоль

Вот простейший пример записи информации в консоль:

Логирование в Python

Если запустить соответствующий кодовый фрагмент, на экране появится следующая картина:

Логирование в Python

INFO не появляется из-за WARNING-уровня. Распечатанное сообщение включает в себя level и описание события, представленного в вызове logging. Речь идет о «Watch out!». Фактический вывод поддерживает возможность гибкого форматирование при необходимости. Эта информация на первых порах не пригодится.

Запись в файл

Наиболее распространенный случай – запись событий журналирования в отдельный файл. Вот пример реализации соответствующего приема в программном коде:

Логирование в Python

Если открыть файл и посмотреть, что там написано, разработчик увидит:

Логирование в Python

Здесь показано, как можно установить уровень журналирования, который действует в качестве порога для отслеживания.

Если хочется установить уровень журналирования при помощи параметра командной строки, рекомендуется использовать запись:

- -log=INFO

Теперь можно ознакомиться с лучшими практиками в процессе реализации logging.

Советы по журналированию

А вот несколько простых советов, которые помогут при журналировании:

  1. Не использовать только один корневой логгер. Это связано с тем, что контроль над ним сильно ограничен, а регистраторами управлять весьма проблематично. Лучше создавать logging (логгеры) для каждого модуля или компонента приложения.
  2. Использовать правильные logging levels. Они помогают обозначать степень серьезности сообщения журнала. Это способ классификации сообщений по значимости. Levels и их значения уже были представлены выше.
  3. Составлять содержательные сообщения для журнала.
  4. Использовать % для форматирования строк. Этот прием позволяет добиться совместимости со старыми версиями Python, в которых нет поддержки f-строк. % также используется для использования более широкого диапазона типов данных и при необходимости более точно управлять исходным кодом. Оно дает более высокую производительность, чем f-строки.
  5. Пользоваться ведением журнала в формате JSON. Это значительно упрощает работу с logging в приложении.
  6. Пользоваться ротацией файлов журнала. Это периодическое создание новых файлов журнала и архивирование/удаление старых. Прием помогает управлять размерами журнальных файлов, а также повышать производительность программы, упрощать отладку и повышать безопасность исходного проекта. Ротация может производиться по времени, размеру или гибридно.

Особенности logging были представлены выше. Это – всего лишь начало изучения журналирования. Быстрее и лучше разобраться с ним помогут дистанционные компьютерные курсы.

Интересует Python? Добро пожаловать на курс в Otus!