Spec-Zone.ru › Python 3.12

Руководство по ведению журнала

Автор:

Винай Саджип <vinay_sajip at red-dove dot com>

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

Базовый учебник по ведению журнала

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

Когда использовать ведение журнала

Вы можете получить доступ к функциональности ведения журнала, создав журнал с помощью logger = getLogger(__name__), а затем вызвав методы журнала debug(), info(), warning(), error() и critical(). Чтобы определить, когда использовать ведение журнала и какие методы журнала использовать, см. таблицу ниже. В ней указан лучший инструмент для каждой из ряда общих задач.

Задача, которую вы хотите выполнить

Лучший инструмент для задачи

Отображение вывода консоли для обычного использования командной строки или программы

print()

Сообщать об событиях, происходящих во время нормальной работы программы (например, для мониторинга состояния или расследования неполадок)

Метод журнала info() (или debug() метод для очень подробного вывода для целей диагностики)

Выдать предупреждение о конкретном событии во время выполнения

warnings.warn() в коде библиотеки, если проблема избежима, и приложение-клиент должно быть изменено для устранения предупреждения

Метод журнала warning(), если приложение-клиент ничего не может сделать по этому поводу, но событие все равно должно быть отмечено

Сообщить об ошибке, связанной с конкретным событием во время выполнения

Вызвать исключение

Сообщить об подавлении ошибки без повышения исключения (например, обработчик ошибок в долгоживущем процессе сервера)

Метод журнала error(), exception() или critical() метод, соответствующий конкретной ошибке и домену приложения

Методы журнала названы в соответствии с уровнем или степенью серьезности событий, которые они используются для отслеживания. Стандартные уровни и их применимость описаны ниже (в порядке возрастания серьезности):

Уровень

Когда он используется

DEBUG

Подробная информация, как правило, представляющая интерес только при диагностике проблем.

INFO

Подтверждение того, что все работает как ожидается.

WARNING

Указание на то, что произошло что-то неожиданное, или указывающее на некоторую проблему в ближайшем будущем (например, «низкий объем дискового пространства»). Программное обеспечение все еще работает как ожидается.

ERROR

Из-за более серьезной проблемы программное обеспечение не смогло выполнить определенную функцию.

CRITICAL

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

Уровень по умолчанию — WARNING, что означает, что будут отслеживаться только события этого уровня и выше, если пакет ведения журнала не настроен иначе.

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

Простой пример

Очень простой пример:

import logging
logging.warning('Watch out!')  # will print a message to the console
logging.info('I told you so')  # will not print anything

Если вы введете эти строки в скрипт и запустите его, вы увидите:

WARNING:root:Watch out!

выведенные на консоль. Сообщение INFO не появляется, потому что уровень по умолчанию — WARNING. Выведенное сообщение включает указание уровня и описание события, предоставленного в вызове ведения журнала, т.е. «Осторожно!». Фактический вывод может быть отформатирован довольно гибко, если вам это необходимо; варианты форматирования также будут объяснены позже.

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

Ведение журнала в файл

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

import logging
logger = logging.getLogger(__name__)
logging.basicConfig(filename='example.log', encoding='utf-8', level=logging.DEBUG)
logger.debug('This message should go to the log file')
logger.info('So should this')
logger.warning('And this, too')
logger.error('And non-ASCII stuff, too, like Øresund and Malmö')

Изменено в версии 3.9: Аргумент encoding был добавлен. В более ранних версиях Python или если он не указан, используемый кодировка — значение по умолчанию, используемое open(). Хотя в приведенном выше примере это не показано, теперь также можно передать аргумент errors, который определяет, как обрабатываются ошибки кодирования. Доступные значения и значение по умолчанию см. в документации для open().

И теперь, если мы откроем файл и посмотрим на то, что у нас есть, мы должны найти сообщения журнала:

DEBUG:__main__:This message should go to the log file
INFO:__main__:So should this
WARNING:__main__:And this, too
ERROR:__main__:And non-ASCII stuff, too, like Øresund and Malmö

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

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

--log=INFO

и у вас есть значение параметра, переданное для --log в некоторой переменной loglevel, вы можете использовать:

getattr(logging, loglevel.upper())

чтобы получить значение, которое вы передадите в basicConfig() через аргумент level. Вы можете проверить вводимые пользователем значения, возможно, как в следующем примере:

# assuming loglevel is bound to the string value obtained from the
# command line argument. Convert to upper case to allow the user to
# specify --log=DEBUG or --log=debug
numeric_level = getattr(logging, loglevel.upper(), None)
if not isinstance(numeric_level, int):
    raise ValueError('Invalid log level: %s' % loglevel)
logging.basicConfig(level=numeric_level, ...)

Вызов basicConfig() должен быть сделан до вызовов методов журнала, таких как debug(), info() и т.д. В противном случае данное событие ведения журнала может не быть обработано желаемым образом.

Если вы запустите приведенный выше скрипт несколько раз, сообщения из последующих запусков будут добавлены к файлу example.log. Если вы хотите, чтобы каждый запуск начинался заново, не запоминая сообщения из предыдущих запусков, вы можете указать аргумент filemode, изменив вызов в приведенном выше примере на:

logging.basicConfig(filename='example.log', filemode='w', level=logging.DEBUG)

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

Ведение журнала переменных данных

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

import logging
logging.warning('%s before you %s', 'Look', 'leap!')

будет отображено:

WARNING:root:Look before you leap!

Как видите, слияние переменных данных в сообщение об описании события использует старый стиль форматирования строк с процентами. Это для обеспечения обратной совместимости: пакет ведения журнала предшествует более новым вариантам форматирования, таким как str.format() и string.Template. Эти новые варианты форматирования поддерживаются, но изучение их выходит за рамки этого руководства: см. Использование конкретных стилей форматирования в вашем приложении для получения дополнительной информации.

Изменение формата отображаемых сообщений

Чтобы изменить формат отображения сообщений, нужно указать нужный вам формат:

import logging
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
logging.debug('This message should appear on the console')
logging.info('So should this')
logging.warning('And this, too')

что напечатает:

DEBUG:This message should appear on the console
INFO:So should this
WARNING:And this, too

Обратите внимание, что «корень», который появлялся в предыдущих примерах, исчез. Для полного набора элементов, которые могут появиться в строках формата, вы можете обратиться к документации для Атрибуты LogRecord, но для простого использования вам понадобятся только levelname (степень серьезности), message (описание события, включая переменные данные) и, возможно, отобразить время события. Это описано в следующем разделе.

Отображение даты/времени в сообщениях

Для отображения даты и времени события, вы должны поместить ‘%(asctime)s’ в строку формата:

import logging
logging.basicConfig(format='%(asctime)s %(message)s')
logging.warning('is when this event was logged.')

что должно вывести что-то вроде этого:

2010-12-12 11:41:42,612 is when this event was logged.

По умолчанию формат отображения даты/времени (показанный выше) похож на ISO8601 или RFC 3339. Если вам нужен больший контроль над форматированием даты/времени, предоставьте аргумент datefmt для basicConfig, как в этом примере:

import logging
logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
logging.warning('is when this event was logged.')

что выведет что-то вроде этого:

12/12/2010 11:46:36 AM is when this event was logged.

Формат аргумента datefmt такой же, как поддерживается time.strftime().

Следующие шаги

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

Если ваши потребности в журнале просты, используйте вышеуказанные примеры, чтобы включить ведение журнала в свои собственные скрипты, и если у вас возникнут проблемы или что-то не будет понятно, пожалуйста, задайте вопрос на Usenet-группе comp.lang.python (https://groups.google.com/g/comp.lang.python), и вы, вероятно, получите помощь в ближайшее время.

Вы всё ещё здесь? Вы можете продолжить чтение следующих разделов, которые предоставляют несколько более продвинутый/глубокий учебник, чем базовый выше. После этого вы можете взглянуть на Кулинарную книгу по ведению журнала.

Расширенный учебник по ведению журнала

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

  • Логгеры предоставляют интерфейс, который непосредственно использует код приложения.
  • Обработчики отправляют записи журнала (созданные логгерами) в соответствующее место назначения.
  • Фильтры обеспечивают более тонкую возможность определения записей журналов для вывода.
  • Форматировщики задают макет записей журналов в конечном выводе.

Информация о событии ведения журнала передается между логгерами, обработчиками, фильтрами и форматировщиками в экземпляре LogRecord.

Ведение журнала выполняется путем вызова методов экземпляров класса Logger (в дальнейшем называемые ломерами). Каждый экземпляр имеет имя, и они концептуально организованы в иерархии имен с использованием точек (точек с запятой) в качестве разделителей. Например, логгер с именем «scan» является родителем логгеров «scan.text», «scan.html» и «scan.pdf». Имена логгеров могут быть любыми, и указывают область приложения, в которой происходит залогированное сообщение.

Хорошим соглашением при именовании логгеров является использование логгера уровня модуля в каждом модуле, который использует ведение журнала, названного следующим образом:

logger = logging.getLogger(__name__)

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

Корень иерархии логгеров называется корневым логгером. Это логгер, используемый функциями debug(), info(), warning(), error() и critical(), которые просто вызывают одноименный метод корневого логгера. У функций и методов одинаковые сигнатуры. Имя корневого логгера печатается как ‘root’ в выводе журнала.

Конечно, можно регистрировать сообщения в разных местах назначения. В пакете предусмотрена поддержка записи сообщений журнала в файлы, расположения HTTP GET/POST, электронную почту через SMTP, общие сокеты, очереди или платформозависимые механизмы ведения журнала, такие как syslog или журнал событий Windows NT. Места назначения обслуживаются классами обработчиков. Вы можете создать свой собственный класс места назначения журнала, если у вас есть особые требования, которые не удовлетворяются встроенными классами обработчиков.

По умолчанию для сообщений журнала не задано никакого места назначения. Вы можете указать место назначения (например, консоль или файл), используя basicConfig(), как в примерах учебника. Если вы вызываете функции debug(), info(), warning(), error() и critical(), они будут проверять, не задано ли место назначения; и если оно не задано, они установят место назначения консоли (sys.stderr) и формат по умолчанию для отображаемого сообщения перед делегированием корневому логгеру для выполнения фактической отправки сообщения.

Формат по умолчанию, установленный basicConfig() для сообщений:

severity:logger name:message

Вы можете изменить это, передав строку формата в basicConfig() с ключевым аргументом format. Для всех параметров построения строки формата см. Объекты форматирования.

Поток ведения журнала

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

Поток логирования Создать LogRecord Вызов логирования в коде пользователя, например logger.info(...) Стоп Филтры, прикрепленные к логгеру, отклоняют запись? Передать запись обработчикам текущего логгера Флаг propagate текущего логгера? Родительский логгер? Установить текущий логгер на родительский Хотя бы один обработчик в иерархии? Использовать lastResort обработчик Обработчик включен для уровня записи? Филтры, прикрепленные к обработчику, отклоняют запись? Стоп Выдача (включая форматирование) Поток обработчика Логгер включён для уровня вызова? Нет Да Да Нет Нет Да Да Нет Нет Да Нет Да Нет Да Запись передана обработчику

Логгеры

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

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

Вот наиболее распространённые методы конфигурации:

  • Logger.setLevel() задаёт уровень важности самого низкого сообщения журнала, которое будет обрабатывать логгер, где debug — самый низкий встроенный уровень важности, а critical — самый высокий. Например, если уровень важности INFO, логгер будет обрабатывать только сообщения INFO, WARNING, ERROR и CRITICAL, игнорируя DEBUG.
  • Logger.addHandler() и Logger.removeHandler() добавляют и удаляют объекты обработчиков из объекта логгера. Обработчики рассматриваются более подробно в Обработчиках.
  • Logger.addFilter() и Logger.removeFilter() добавляют и удаляют объекты фильтров из объекта логгера. Фильтры рассматриваются более подробно в Объектах фильтров.

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

После конфигурации объекта логгера следующие методы создают сообщения журнала:

  • Logger.debug(), Logger.info(), Logger.warning(), Logger.error() и Logger.critical() все создают записи журнала с сообщением и уровнем, соответствующим их названиям методов. Сообщение на самом деле является строкой формата, которая может содержать стандартную синтаксическую конструкцию подстановки строк %s, %d, %f, и так далее. Остальные их аргументы — список объектов, соответствующих полям подстановки в сообщении. Что касается **kwargs, методы регистрации заботятся только о ключевом слове exc_info и используют его для определения необходимости регистрации информации об исключении.
  • Logger.exception() создаёт сообщение журнала, аналогичное Logger.error(). Разница в том, что Logger.exception() также выводит отладочную информацию о стеке вызовов. Этот метод следует вызывать только из обработчика исключений.
  • Logger.log() принимает уровень журнала в качестве явного аргумента. Это немного более подробный способ регистрации сообщений, чем использование удобных методов уровней журнала, перечисленных выше, но так регистрируются сообщения на пользовательских уровнях.

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

Логгеры имеют понятие эффективного уровня. Если уровень не задан явно для логгера, используется уровень его родительского логгера. Если родительский логгер не имеет явного уровня, исследуется его родительский логгер, и так далее — все предки проверяются до тех пор, пока не будет найден явный уровень. У корневого логгера всегда задан явный уровень (по умолчанию WARNING). При определении, нужно ли обрабатывать событие, используется эффективный уровень логгера, чтобы определить, передаётся ли событие обработчикам логгера.

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

Обработчики

Handler объекты отвечают за распределение соответствующих сообщений журнала (на основе уровня важности сообщений журнала) в указанное место назначения обработчика. Logger объекты могут добавить ноль или более объектов обработчиков к себе с помощью метода addHandler(). Например, приложение может хотеть отправлять все сообщения журнала в файл журнала, все сообщения уровня error или выше в stdout и все сообщения уровня critical на электронную почту. Для этого нужны три отдельных обработчика, каждый из которых отвечает за отправку сообщений определённого уровня важности в определённое место.

Стандартная библиотека включает в себя несколько типов обработчиков (см. Полезные обработчики); в учебниках в основном используются StreamHandler и FileHandler.

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

  • Метод setLevel(), как и в объектах логгера, задаёт самый низкий уровень важности, который будет направлен в соответствующее место назначения. Почему есть два метода setLevel()? Уровень, заданный в логгере, определяет, какие сообщения какого уровня важности он передаст своим обработчикам. Уровень, заданный в каждом обработчике, определяет, какие сообщения этот обработчик отправит.
  • setFormatter() выбирает объект Formatter для использования этим обработчиком.
  • addFilter() и removeFilter() соответственно настраивают и отключают объекты фильтров для обработчиков.

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

Форматизаторы

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

logging.Formatter.__init__(fmt=None, datefmt=None, style='%')

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

%Y-%m-%d %H:%M:%S

с миллисекундами в конце. style может быть одним из '%', '{', или '$'. Если какой-либо из них не указан, используется '%'.

Если style равно '%', строка формата сообщения использует стиль подстановки строк %(<dictionary key>)s; возможные ключи описаны в Атрибуты записи журнала. Если стиль равен '{', предполагается, что строка формата сообщения совместима с str.format() (используя ключевые аргументы), а если стиль равен '$', предполагается, что строка формата сообщения соответствует ожиданиям string.Template.substitute().

Изменено в версии 3.2: Добавлен параметр style.

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

'%(asctime)s - %(levelname)s - %(message)s'

Форматизаторы используют настраиваемую функцию для преобразования времени создания записи в кортеж. По умолчанию используется time.localtime(); чтобы изменить это для определенного экземпляра форматизатора, установите атрибут converter экземпляра в функцию с той же сигнатурой, что и time.localtime() или time.gmtime(). Чтобы изменить его для всех форматизаторов, например, если вы хотите, чтобы все времена регистрации отображались в формате GMT, установите атрибут converter в классе Formatter (на time.gmtime для отображения в формате GMT).

Настройка логирования

Программисты могут настроить логирование тремя способами:

  1. Явно создать логгеры, обработчики и форматизаторы с помощью кода Python, вызывающего методы конфигурации, перечисленные выше.
  2. Создать файл конфигурации логирования и прочитать его, используя функцию fileConfig().
  3. Создать словарь с информацией о конфигурации и передать его в функцию dictConfig().

Для справки по последним двум вариантам см. Функции конфигурации. Следующий пример настраивает очень простой логгер, обработчик консоли и простой форматизатора с помощью кода Python:

import logging

# create logger
logger = logging.getLogger('simple_example')
logger.setLevel(logging.DEBUG)

# create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

# create formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# add formatter to ch
ch.setFormatter(formatter)

# add ch to logger
logger.addHandler(ch)

# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')

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

$ python simple_logging_module.py
2005-03-19 15:10:26,618 - simple_example - DEBUG - debug message
2005-03-19 15:10:26,620 - simple_example - INFO - info message
2005-03-19 15:10:26,695 - simple_example - WARNING - warn message
2005-03-19 15:10:26,697 - simple_example - ERROR - error message
2005-03-19 15:10:26,773 - simple_example - CRITICAL - critical message

Следующий модуль Python создает логгер, обработчик и форматизатора, почти идентичные тем, что в примере выше, с единственной разницей в именах объектов:

import logging
import logging.config

logging.config.fileConfig('logging.conf')

# create logger
logger = logging.getLogger('simpleExample')

# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')

Вот файл logging.conf:

[loggers]
keys=root,simpleExample

[handlers]
keys=consoleHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=DEBUG
handlers=consoleHandler

[logger_simpleExample]
level=DEBUG
handlers=consoleHandler
qualname=simpleExample
propagate=0

[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)

[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s

Вывод почти идентичен выводу примера без файла конфигурации:

$ python simple_logging_config.py
2005-03-19 15:38:55,977 - simpleExample - DEBUG - debug message
2005-03-19 15:38:55,979 - simpleExample - INFO - info message
2005-03-19 15:38:56,054 - simpleExample - WARNING - warn message
2005-03-19 15:38:56,055 - simpleExample - ERROR - error message
2005-03-19 15:38:56,130 - simpleExample - CRITICAL - critical message

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

Предупреждение

Функция fileConfig() принимает необязательный параметр disable_existing_loggers, который по умолчанию равен True по соображениям обратной совместимости. Это может или не может быть тем, что вам нужно, так как это приведет к отключению любых логгеров, не являющихся корневыми, существовавших до вызова fileConfig(), если они (или предок) явно не указаны в конфигурации. Для получения дополнительной информации обратитесь к документации справки и укажите False для этого параметра, если хотите.

Словарь, переданный в dictConfig(), также может указывать булево значение с ключом disable_existing_loggers, которое, если не указано явно в словаре, также по умолчанию интерпретируется как True. Это приводит к поведению отключения логгеров, описанному выше, что может не соответствовать вашим требованиям. В таком случае явно укажите ключ со значением False.

Обратите внимание, что имена классов, упомянутые в файлах конфигурации, должны быть либо относительными к модулю logging, либо абсолютными значениями, которые могут быть разрешены с помощью обычных механизмов импорта. Таким образом, вы можете использовать либо WatchedFileHandler (относительно модуля logging), либо mypackage.mymodule.MyHandler (для класса, определенного в пакете mypackage и модуле mymodule, где mypackage доступен в пути импорта Python).

В Python 3.2 был представлен новый способ настройки логирования, использующий словари для хранения конфигурационной информации. Это предоставляет расширенный функционал по сравнению с подходом на основе файла конфигурации, описанного выше, и является рекомендуемым методом конфигурации для новых приложений и развертываний. Поскольку для хранения конфигурационной информации используется словарь Python, и так как вы можете заполнить этот словарь различными способами, у вас больше вариантов конфигурации. Например, вы можете использовать файл конфигурации в формате JSON или, если у вас есть доступ к функциям обработки YAML, файл в формате YAML для заполнения словаря конфигурации. Или, конечно, вы можете сконструировать словарь в коде Python, получить его в сериализованной форме через сокет или использовать любой подход, который подходит для вашего приложения.

Вот пример той же конфигурации, что и выше, в формате YAML для нового подхода на основе словаря:

version: 1
formatters:
  simple:
    format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
handlers:
  console:
    class: logging.StreamHandler
    level: DEBUG
    formatter: simple
    stream: ext://sys.stdout
loggers:
  simpleExample:
    level: DEBUG
    handlers: [console]
    propagate: no
root:
  level: DEBUG
  handlers: [console]

Дополнительную информацию о логировании с помощью словаря см. в Функции конфигурации.

Что происходит, если конфигурация не задана

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

Событие выводится с помощью «обработчика последнего средства», хранящегося в lastResort. Этот внутренний обработчик не связан ни с одним логгером и работает как StreamHandler, записывающий сообщение описания события в текущее значение sys.stderr (следовательно, учитывая любые перенаправления, которые могут быть в силе). Форматирование сообщения не выполняется — печатается только сообщение описания события. Уровень обработчика установлен на WARNING, поэтому все события с этим и более высоким уровнем серьезности будут выведены.

Изменено в версии 3.2: Для версий Python до 3.2 поведение следующее:

  • Если raiseExceptions равно False (режим производства), событие отбрасывается безмолвно.
  • Если raiseExceptions равно True (режим разработки), сообщение «Не удалось найти обработчики для логгера X.Y.Z» выводится один раз.

Чтобы получить поведение до 3.2, lastResort можно установить на None.

Настройка ведения журнала для библиотеки

При разработке библиотеки, использующей ведение журнала, следует позаботиться о документировании того, как библиотека использует ведение журнала — например, имена используемых логгеров. Также необходимо уделить внимание конфигурации ведения журнала. Если приложение, использующее библиотеку, не использует ведение журнала, а код библиотеки выполняет вызовы ведения журнала, то (как описано в предыдущем разделе) события уровня WARNING и выше будут выведены в sys.stderr. Это считается лучшим поведением по умолчанию.

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

Обработчик, ничего не делающий, включен в пакет ведения журнала: NullHandler (с Python 3.1). Экземпляр этого обработчика можно добавить в логгер верхнего уровня пространства имен ведения журнала, используемого библиотекой (если вы хотите предотвратить вывод событий вашей библиотеки в sys.stderr при отсутствии конфигурации ведения журнала). Если всё ведение журнала библиотекой foo выполняется с использованием логгеров с именами, соответствующими ‘foo.x’, ‘foo.x.y’ и т. д., то код:

import logging
logging.getLogger('foo').addHandler(logging.NullHandler())

должен иметь желаемый эффект. Если организация производит несколько библиотек, то имя логгера можно указать как ‘orgname.foo’, а не только ‘foo’.

Примечание

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

Примечание

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

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

Числовые значения уровней ведения журнала приведены в следующей таблице. Они в первую очередь представляют интерес, если вы хотите определить свои собственные уровни и вам нужно, чтобы они имели определенные значения относительно предопределенных уровней. Если вы определите уровень с тем же числовым значением, он перезапишет предопределенное значение; предопределенное имя будет потеряно.

Уровень

Числовое значение

CRITICAL

50

ERROR

40

WARNING

30

INFO

20

DEBUG

10

NOTSET

0

Уровни также могут быть связаны с логгерами, устанавливаясь либо разработчиком, либо при загрузке сохранённой конфигурации ведения журнала. Когда вызывается метод ведения журнала для логгера, логгер сравнивает свой собственный уровень с уровнем, связанным с вызовом метода. Если уровень логгера выше уровня вызова, сообщение ведения журнала фактически не генерируется. Это основной механизм управления объёмом вывода ведения журнала.

Сообщения ведения журнала кодируются как экземпляры класса LogRecord. Когда логгер принимает решение фактически записать событие, создаётся экземпляр LogRecord из сообщения ведения журнала.

Сообщения ведения журнала обрабатываются механизмом передачи с использованием обработчиков, которые являются экземплярами подклассов класса Handler. Обработчики отвечают за обеспечение того, чтобы сообщение журнала (в виде экземпляра LogRecord) оказывалось в определённом месте (или наборе мест), что полезно для целевой аудитории этого сообщения (такой как конечные пользователи, сотрудники службы поддержки, системные администраторы, разработчики). Обработчикам передаются экземпляры LogRecord, предназначенные для определённых мест назначения. Каждый логгер может иметь ноль, один или несколько обработчиков, связанных с ним (через метод addHandler() класса Logger). В дополнение к любым обработчикам, непосредственно связанным с логгером, вызываются все обработчики, связанные со всеми предками логгера для передачи сообщения (если флаг propagate для логгера установлен в значение false, в этом случае передача обработчикам предков прекращается).

Так же, как и для логгеров, обработчики могут иметь связанные с ними уровни. Уровень обработчика действует как фильтр так же, как уровень логгера. Если обработчик принимает решение фактически передать событие, используется метод emit() для отправки сообщения в его место назначения. Большинству пользовательских подклассов Handler потребуется переопределить этот метод emit().

Пользовательские уровни

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

Полезные обработчики

В дополнение к базовому классу Handler, предоставляются многие полезные подклассы:

  1. StreamHandler экземпляры отправляют сообщения в потоки (объекты, подобные файлам).
  2. FileHandler экземпляры отправляют сообщения в файлы на диске.
  3. BaseRotatingHandler — это базовый класс для обработчиков, которые вращают файлы журнала в определённый момент. Он не предназначен для прямого создания экземпляров. Вместо этого используйте RotatingFileHandler или TimedRotatingFileHandler.
  4. RotatingFileHandler экземпляры отправляют сообщения в файлы на диске с поддержкой максимального размера файла журнала и вращения файла журнала.
  5. TimedRotatingFileHandler экземпляры отправляют сообщения в файлы на диске, вращая файл журнала через определённые промежутки времени.
  6. SocketHandler экземпляры отправляют сообщения в сокеты TCP/IP. С версии 3.4 также поддерживаются сокеты Unix-домена.
  7. DatagramHandler экземпляры отправляют сообщения в сокеты UDP. С версии 3.4 также поддерживаются сокеты Unix-домена.
  8. SMTPHandler экземпляры отправляют сообщения на указанный адрес электронной почты.
  9. SysLogHandler экземпляры отправляют сообщения в демон Unix syslog, возможно, на удалённой машине.
  10. NTEventLogHandler экземпляры отправляют сообщения в журнал событий Windows NT/2000/XP.
  11. MemoryHandler экземпляры отправляют сообщения в буфер в памяти, который очищается всякий раз, когда выполняются определённые условия.
  12. HTTPHandler экземпляры отправляют сообщения на HTTP-сервер, используя семантику GET или POST.
  13. WatchedFileHandler экземпляры отслеживают файл, в который они ведут журнал. Если файл изменяется, он закрывается и переоткрывается с использованием имени файла. Этот обработчик полезен только на системах Unix-подобного типа; Windows не поддерживает используемый механизм.
  14. QueueHandler экземпляры отправляют сообщения в очередь, такие как реализованные в модулях queue или multiprocessing.
  15. NullHandler экземпляры не выполняют никаких действий с сообщениями об ошибках. Они используются разработчиками библиотек, которые хотят использовать логирование, но хотят избежать сообщения «Не удалось найти обработчики для регистра XXX», которое может отображаться, если пользователь библиотеки не настроил логирование. Дополнительную информацию см. в разделе Настройка логирования для библиотеки.

Добавлен в версии 3.1: Класс NullHandler.

Добавлен в версии 3.2: Класс QueueHandler.

Классы NullHandler, StreamHandler и FileHandler определены в базовом пакете логирования. Другие обработчики определены в подмодуле logging.handlers. (Также есть другой подмодуль logging.config для функциональности конфигурации.)

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

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

Когда фильтрация на основе уровня регистратора и/или уровня обработчика недостаточна, можно добавлять экземпляры класса Filter как в экземпляры Logger, так и в экземпляры Handler (через метод addFilter()). Перед дальнейшей обработкой сообщения как регистраторы, так и обработчики обращаются к всем своим фильтрам для получения разрешения. Если любой фильтр возвращает значение false, сообщение не обрабатывается далее.

Базовая функциональность фильтра Filter позволяет фильтровать по имени конкретного регистратора. Если эта функция используется, сообщения, отправленные в указанный регистратор и его дочерние элементы, допускаются через фильтр, а все остальные отбрасываются.

Исключения, возникающие во время логирования

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

Исключения SystemExit и KeyboardInterrupt никогда не поглощаются. Другие исключения, возникающие во время метода emit() подкласса Handler, передаются в метод handleError() этого обработчика.

В стандартной реализации метода handleError() в классе Handler проверяется, установлена ли переменная модульного уровня raiseExceptions. Если она установлена, трассировка стека выводится в sys.stderr. Если нет, исключение поглощается.

Примечание

Значение по умолчанию для raiseExceptions равно True. Это потому, что во время разработки вы обычно хотите получать уведомления обо всех возникающих исключениях. Рекомендуется установить значение raiseExceptions в False для использования в производственной среде.

END_OF_DOCUMENT_MARKER

Использование произвольных объектов в качестве сообщений

В предыдущих разделах и примерах предполагалось, что сообщение, передаваемое при регистрации события, является строкой. Однако это не единственная возможность. Вы можете передать произвольный объект в качестве сообщения, и его метод __str__() будет вызван, когда система регистрации нуждается в его преобразовании в строковое представление. На самом деле, если вы хотите, вы можете вообще избежать вычисления строкового представления — например, SocketHandler передает событие, сериализуя его и отправляя по каналу.

Оптимизация

Форматирование аргументов сообщения откладывается до тех пор, пока это невозможно избежать. Однако вычисление аргументов, передаваемых методу регистрации, также может быть дорогостоящим, и вы можете захотеть избежать его, если логгер просто отбросит ваше событие. Чтобы принять решение, как поступить, вы можете вызвать метод isEnabledFor(), который принимает аргумент уровня и возвращает true, если событие было бы создано логгером для этого уровня вызова. Вы можете написать код так:

if logger.isEnabledFor(logging.DEBUG):
    logger.debug('Message with %s, %s', expensive_func1(),
                                        expensive_func2())

Таким образом, если порог логгера установлен выше DEBUG, вызовы expensive_func1 и expensive_func2 никогда не будут выполнены.

Примечание

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

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

Информация, которую вы не хотите собирать

Как избежать ее сбора

Информация о том, откуда были сделаны вызовы.

Установите logging._srcfile в None. Это позволяет избежать вызова sys._getframe(), что может ускорить ваш код в средах, подобных PyPy (который не может ускорить код, использующий sys._getframe()).

Информация о потоках.

Установите logging.logThreads в False.

Текущий идентификатор процесса (os.getpid())

Установите logging.logProcesses в False.

Текущее имя процесса при использовании multiprocessing для управления несколькими процессами.

Установите logging.logMultiprocessing в False.

Текущее имя asyncio.Task при использовании asyncio.

Установите logging.logAsyncioTasks в False.

Также обратите внимание, что модуль основной регистрации содержит только базовые обработчики. Если вы не импортируете logging.handlers и logging.config, они не будут занимать память.

Другие ресурсы

См. также

Module logging

Справочник по API для модуля регистрации.

Module logging.config

API конфигурации для модуля регистрации.

Module logging.handlers

Полезные обработчики, включенные в модуль регистрации.

Кулинарная книга по регистрации

© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.12/howto/logging.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API