Программирование PC Python Logging HOWTO Sat, March 29 2025  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.


Python Logging HOWTO Печать
Добавил(а) microsin   

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

Примечание: на этой страничке содержится обучающая информация по использованию лога (перевод [1]). 

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

Выполняемая задача Что для этого лучше использовать
Отобразить вывод консоли для обычного использования скрипта командной строки или программы. print()
Сообщить от событиях, которые возникают во время нормальной работы программы (например для мониторинга состояния или исследования неисправностей). Вызов методов info() (или debug() с подробным вызовом в целях диагностики) логгера.
Выдача предупреждения, относящегося к определенному runtime-событию. warnings.warn() в библиотечном коде, если проблему можно избежать, и клиентское приложение должно быть изменено для устранения предупреждения.

Метод логгера warning(), если клиентское приложение ничего не может сделать с ситуацией, но событие все равно должно быть отмечено.
Сообщение о событии ошибки, относящейся к определенному runtime-событию. Выбрасывание исключения (exception).
Сообщение о подавлении ошибки без создания исключения (например, обработчик ошибки в продолжительном серверном процессе). Методы error(), exception() или critical() логгера, подходящие по серьезности (уровню) к определенной ошибке и в зависимости от домена приложения.

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

Уровень Когда используется
DEBUG Подробная информация, обычно интересующая только когда необходимо диагностировать проблемы.
INFO Подтверждение, что нормально произошли определенные вещи в программе, которые были ожидаемо выполнены.
WARNING Индикация, что произошло что-то неожиданное, или индикация какой-то проблемы, которая может возникнуть в будущем (например 'disk space low'). Программа все еще будет продолжать работать, как ожидалось.
ERROR Из-за более серьезной проблемы программа не может выполнить какую-то функцию.
CRITICAL Серьезная ошибка, показывающая что программа сама по себе не может продолжить работу.

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

События могут отслеживаться различными способами. Самый простой способ отслеживания - печать сообщений в консоль. Другой общий способ - записывать сообщения о событиях в файл на диске и их последующий анализ с помощью текстового редактора или runtime с помощью утилиты tail [3].

Простой пример использования модуля лога:

import logging

# Печать предупреждающего сообщения в консоль: logging.warning('Внимание, возможна проблема!')

# Это сообщение не напечатается, потому что по умолчанию
# модуль logging сконфигурирован на уровень WARNING: logging.info('А я что тебе говорил...')

Если вы запустите это скрипт, то увидите следующее сообщение в консоли:

WARNING:root:Watch out!

Сообщение уровня INFO, которое выдает метод info(), не будет отображено, потому что пороговый уровень WARNING установлен по умолчанию как настроенный уровень серьезности лога. Печатаемое сообщение включает метку уровня серьезности (WARNING) и сообщение, описывающее событие (Внимание, возможна проблема!). Если необходимо, то фактический вывод может быть отформатирован достаточно гибко; параметры форматирования будут описаны далее.

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

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

import logging
logger = logging.getLogger(__name__) logging.basicConfig(filename='example.log', encoding='utf-8', level=logging.DEBUG) logger.debug('Это сообщение должно попасть в файл лога.') logger.info('Должно ли это попасть в лог?') logger.warning('И это тоже.')

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

Теперь если вы откроете файл лога example.log, то увидите в нем следующие сообщения:

DEBUG:__main__:Это сообщение должно попасть в файл лога.
INFO:__main__:Должно ли это попасть в лог?
WARNING:__main__:И это тоже.

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

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

--log=INFO

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

getattr(logging, loglevel.upper())

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

# Предполагается, что loglevel привязан к строковому значению,
# полученному из аргумента командной строки. Преобразуйте
# это значение в верхний регистр, чтобы пользователь мог
# указать --log=DEBUG или --log=debug numeric_level = getattr(logging, loglevel.upper(), None)
if not isinstance(numeric_level, int): raise ValueError('Недопустимый уровень лога: %s' % loglevel) logging.basicConfig(level=numeric_level, ...)

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

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

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

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

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

import logging
logging.warning('%s броду, не суйся %s', 'Не зная', 'в воду!')

.. отобразит:

WARNING:root:Не зная броду, не суйся в воду!

Как видите, при слиянии данных переменных с сообщеним с описанием события используется старый %-стиль форматирования строки. Это делается по соображениям обратной совместимости: пакет logging предварительно датирует новые параметры форматирования, такие как str.format() и string.Template. Эти более новые опции форматирования поддерживаются, но их изучение выходит за рамки этого руководства: дополнительные сведения см. в разделе "Using particular formatting styles throughout your application" руководства [4].

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

import logging
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG) logging.debug('Это сообщение должно попасть в файл лога.') logging.info('Должно ли это попасть в лог?') logging.warning('И это тоже.')

Этот код выведет:

DEBUG:Это сообщение должно попасть в файл лога.
INFO:Должно ли это попасть в лог?
WARNING:И это тоже.

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

Отображение даты/времени в сообщениях. Чтобы зафиксировать в сообщении лога метку времени события, вы можете поместить '%(asctime)s' в свою строку формата:

import logging
logging.basicConfig(format='%(asctime)s %(message)s') logging.warning('когда это событие было записано в лог.')

Этот код выведет:

2025-03-17 10:06:00,874 когда это событие было записано в лог.

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

import logging
logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p') logging.warning('когда это событие было записано в лог.')

Этот код выведет:

03/17/2025 05:59:48 PM когда это событие было записано в лог.

Строка формата datefmt='%y%m%d %H:%M:%S' выведет время в 24-часовом формате:

250317 18:03:01 когда это событие было записано в лог.

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

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

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

[Расширенное руководство по модулю logging]

Библиотека logging представляет модульный принцип организации кода, и предоставляет несколько категорий компонентов: логгеры (logger), обработчики (handler), фильтры (filter) и форматировщики (formatter).

• Объекты Logger предоставляют интерфейс, который напрямую использует код приложения.
• Объекты обработчиков Handler посылают записи лога (log records, созданные логгерами) в подходящее место назначения.
• Объекты фильтров Filter предоставляют более детальное средство для определения того, какие записи лога должны выводиться.
• Объекты форматировщиков Formatter определяют окончательный вид формата вывода записей лога.

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

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

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

logger = logging.getLogger(__name__)

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

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

Конечно, есть возможность организовать вывод сообщения лога в разные места назначения. Поддержка лога включена для записи сообщений в файлы, запросы HTTP GET/POST, через электронную почту по протоколу SMTP, традиционные сокеты (generic sockets), очереди (queues) или специфичные для операционной системы механизмы лога, такие как syslog на Linux или логи событий (event log) на Windows NT. Места назначения лога обслуживаются классами обработчиков (Handler). Вы можете создать свой собственный класс места назначения лога, если имеются какие-то специальные требования, которым не удовлетворяют готовые встроенные классы обработчиков.

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

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

важность:имя_логгера:сообщение

Вы можете изменить это, передавая строку формата в basicConfig() через именованный аргумент format. Описание всех опций, относящихся к построению строки формата, см. в описании объектов Formatter [2].

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

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

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

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

Logger.setLevel() указывает сообщение лога с самой низкой важностью, которое будет обработано логгером, где уровень debug это самый низкий встроенный уровень серьезности вывода, а critical это самый высокий уровень серьезности. Например, если уровень серьезности лога установлен INFO, то логгер будет обрабатывать только сообщения уровня INFO, WARNING, ERROR и CRITICAL, и будет игнорировать сообщения DEBUG.
Logger.addHandler() и Logger.removeHandler() добавляют и удаляют объекты обработчика в объекте логгера. Обработчики более подробно рассматриваются далее в секции Handler.
Logger.addFilter() и Logger.removeFilter() добавляют и удаляют объекты в объекте логгера. Фильтры более подробно рассматриваются в описании объектов Filter [2].

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

Когда объект логгера сконфигурирован, следующие методы создают сообщения лога:

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

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

У логгеров существует концепция эффективного уровня. Если уровень не установлен явно в логгере, то в качестве эффективного уровня используется уровень его родителя. Если родительский объект не имеет явно установленного уровня, то проверяется его родительский объект, и так далее - просмотр всех предков выполняется до тех пор, пока не будет найден явно установленный уровень. Корневой логгер (root logger) всегда имеет явно установленный уровень (WARNING по умолчанию). При принятии решения об обработке события лога эффективный уровень используется для того, чтобы определить, должно ли событие быть передано обработчикам логгера.

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

Handler. Объекты обработчика (Handler) отвечают за отправку соответствующих сообщений лога (на основе серьезности сообщений лога) в указанное место назначения обработчика. Объекты логгера могут добавлять себе ноль или большее количество обработчиков с помощью метода addHandler(). В качестве примера, приложение может захотеть посылать все сообщения лога в файл, все сообщения уровня ошибки или выше в stdout, и все сообщения уровня critical по адресу email. Этот сценарий требует трех отдельных обработчиков, где каждый обработчик отвечает за отправку сообщений определенного уровня серьезности в определенное место назначения.

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

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

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

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

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

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

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

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

Стиль это один из вариантов '%', '{' или '$'. Если ни один из этих вариантов не указан, то используется '%'.

Если стиль '%', то строка формата сообщения использует подстановку строк в стиле %(< ключ словаря>)s; возможные ключи документированы в описании атрибутов LogRecord [2]. Если стиль '{', то подразумевается строка формата сообщения, совместимая с 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().

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

import logging

# Создание логгера: logger = logging.getLogger('simple_example') logger.setLevel(logging.DEBUG)

# Создание обработчика консоли и установка уровня debug: ch = logging.StreamHandler() ch.setLevel(logging.DEBUG)

# Создание форматтера: formatter = logging.Formatter('%(asctime)s \ - %(name)s - %(levelname)s - %(message)s')

# Добавление форматтера к объекту обработчика ch: ch.setFormatter(formatter)

# Добавление объекта обработчика к логгеру: logger.addHandler(ch)

# Код 'приложения': logger.debug('сообщение уровня debug') logger.info('сообщение уровня info') logger.warning('сообщение предупреждения') logger.error('сообщение уровня ошибки') logger.critical('критическое сообщение')

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

2005-03-19 15:10:26,618 - simple_example - DEBUG - сообщение уровня debug
2005-03-19 15:10:26,620 - simple_example - INFO - сообщение уровня info
2005-03-19 15:10:26,695 - simple_example - WARNING - сообщение предупреждения
2005-03-19 15:10:26,697 - simple_example - ERROR - сообщение уровня ошибки
2005-03-19 15:10:26,773 - simple_example - CRITICAL - критическое сообщение

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

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

# Создание логгера: logger = logging.getLogger('simpleExample')

# Код 'приложения': 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

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

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() может также указать значение Boolean с ключом disable_existing_loggers, которое, если не указано явно в словаре, также по умолчанию интерпретируется как True. Это приводит к описанному выше поведению отключения логгера, которые может быть не тем, что вам нужно - в таком случае предоставьте ключ disable_existing_loggers с явным значением False.

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

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

Вот пример такой же конфигурации, как и была показана выше, в формате 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]

Для дополнительной информации про logging с использованием словаря см. секцию "Configuration functions" документации [6].

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

Событие выводится с использованием 'обработчика последнего шанса' (handler of last resort), сохраненного в lastResort. Этот внутренний обработчик не связан ни с одним логгером, и действует как StreamHandler, который записывает сообщение описания события в текущее значение sys.stderr (таким образом, будут соблюдены любые перенаправления, которые могут быть в силе). На сообщении не производится никакое форматирование - распечатывается чисто сообщение с описанием события. Уровень важности обработчика установлен в WARNING, так что все события с этим и более высоким уровнем будут попадать в лог.

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

• Если raiseExceptions == False (режим производства), то событие тихо отбрасывается.
• Если raiseExceptions == True (режим разработки), то однократно печатается сообщение ‘No handlers could be found for logger X.Y.Z’.

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

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

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

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

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

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

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

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

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

Уровень Числовое значение
NOTSET 0
DEBUG 10
INFO 20
WARNING 30
ERROR 40
CRITICAL 50

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

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

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

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

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

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

В дополнение к базовому классу Handler предоставляется множество полезных субклассов обработчиков:

1. Экземпляры StreamHandler посылают сообщения в потоки (streams, объекты наподобие файлов).

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 посылают сообщения по указанному адресу email.

9. Экземпляры SysLogHandler посылают сообщения демону Unix syslog, возможно на удаленную машину.

10. Экземпляры NTEventLogHandler посылают сообщения в лог событий Windows NT/2000/XP.

11. Экземпляры MemoryHandler посылают сообщения в буфер памяти, который сливается (flushed) по удовлетворении специальному критерию.

12. Экземпляры HTTPHandler посылают сообщения на сервер HTTP, используя семантику GET либо POST.

13. Экземпляры WatchedFileHandler отслеживают файл, куда они посылают лог. Если файл изменился, то он закрывается и открываются заново с использованием имени файла. Этот обработчик полезен только на системах семейства Unix; Windows не поддерживает соответствующий механизм низкого уровня.

14. Экземпляры QueueHandler посылают сообщения в очередь, например реализованную в модулях queue или multiprocessing.

15. Экземпляры NullHandler ничего не делают с сообщениями ошибки. Они используются разработчиками библиотеки, кто хочет использовать logging, но также хотят избежать сообщения 'No handlers could be found for logger XXX', которое может отображаться, если пользователь библиотеки не сконфигурировал logging. См. описание конфигурирования лога библиотеки для дополнительной информации.

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

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

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

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

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

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

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

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

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

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

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

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

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

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()).
Информация потоков (Threading). Установите logging.logThreads в False.
Идентификатор текущего потока (process ID, os.getpid()). Установите logging.logProcesses в False.
Имя текущего процесса, когда используется multiprocessing для управления несколькими процессами. Установите logging.logMultiprocessing в False.
Имя текущего asyncio.Task, когда используется asyncio. Установите logging.logAsyncioTasks в False.

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

[Ссылки]

1. Python Logging HOWTO site:python.org.
2. Python: модуль вывода в лог.
3. Просмотр сообщений dmesg в реальном времени.
4. Python Logging Cookbook site:python.org.
5. time.strftime(format[, t]).
6. logging.config — Logging configuration site:python.org. Описание API для конфигурации модуля logging.
7. logging.handlers — Logging handlers site:python.org. Полезные обработчики, включенные в модуль logging.

 

Добавить комментарий


Защитный код
Обновить

Top of Page