Документация Python неофициальный перевод
Содержание страницы

15.6. logging – средство логирования для Pythonlogging – Logging facility for Python

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

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

Регистрируемые сообщения также имеют связанные с ними уровни важности. Предоставляемые по умолчанию уровни: DEBUG, INFO, WARNING, ERROR и CRITICAL. Для удобства уровень важности сообщения указывается вызовом соответствующего метода Logger. Это методы debug(), info(), warning(), error() и critical(), которые соответствуют уровням по умолчанию. Вы не обязаны использовать только эти уровни: можно задать собственные и применять более общий метод Loggerlog(), который принимает явный аргумент уровня.

15.6.1. Учебное пособие по логированиюLogging tutorial

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

Конечно, можно регистрировать сообщения с разными уровнями подробности или направлять их в разные места. Поддержка записи сообщений в файлы, по HTTP GET/POST, электронной почте через SMTP, через обычные сокеты или через механизмы логирования, специфичные для ОС, – всё это поддерживается стандартным модулем. Вы также можете создать собственный класс назначения журнала, если ваши особые требования не удовлетворяются ни одним из встроенных классов.

15.6.1.1. Простые примерыSimple examples

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

import logging
LOG_FILENAME = 'example.log'
logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG)

logging.debug('This message should go to the log file')

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

DEBUG:root:This message should go to the log file

Если запускать скрипт повторно, новые сообщения журнала будут добавляться в файл. Чтобы каждый раз создавать новый файл, можно передать аргумент filemode в basicConfig() со значением 'w'. Однако вместо того чтобы управлять размером файла вручную, проще использовать RotatingFileHandler:

import glob
import logging
import logging.handlers

LOG_FILENAME = 'logging_rotatingfile_example.out'

# Настроить конкретный регистратор с нужным уровнем вывода
my_logger = logging.getLogger('MyLogger')
my_logger.setLevel(logging.DEBUG)

# Добавить обработчик сообщений журнала в регистратор
handler = logging.handlers.RotatingFileHandler(
              LOG_FILENAME, maxBytes=20, backupCount=5)

my_logger.addHandler(handler)

# Записать несколько сообщений
for i in range(20):
    my_logger.debug('i = %d' % i)

# Посмотреть, какие файлы созданы
logfiles = glob.glob('%s*' % LOG_FILENAME)

for filename in logfiles:
    print(filename)

В результате должно получиться 6 отдельных файлов, каждый из которых содержит часть истории журнала приложения:

logging_rotatingfile_example.out
logging_rotatingfile_example.out.1
logging_rotatingfile_example.out.2
logging_rotatingfile_example.out.3
logging_rotatingfile_example.out.4
logging_rotatingfile_example.out.5

Самый текущий файл всегда называется logging_rotatingfile_example.out, и каждый раз, когда он достигает предельного размера, он переименовывается с суффиксом .1. Каждый из существующих резервных файлов переименовывается для увеличения суффикса (.1 становится .2 и т.д.), а файл .6 удаляется.

Очевидно, в этом примере максимальный размер файла установлен чрезвычайно малым для наглядности. Следует установить maxBytes в подходящее значение.

Ещё одна полезная возможность API логирования – создавать разные сообщения на разных уровнях логирования. Это позволяет, например, наполнить код отладочными сообщениями, но снизить уровень логирования так, чтобы в рабочей системе эти сообщения не записывались. Уровни по умолчанию: CRITICAL, ERROR, WARNING, INFO, DEBUG и NOTSET.

Логгер, обработчик и вызов сообщения – каждый задаёт свой уровень. Сообщение выводится только в том случае, если обработчик и логгер настроены на вывод сообщений этого уровня или ниже. Например, если сообщение имеет уровень CRITICAL, а логгер настроен на ERROR, сообщение будет выведено. Если сообщение имеет уровень WARNING, а логгер настроен на вывод только ERROR, сообщение не будет выведено:

import logging
import sys

LEVELS = {'debug': logging.DEBUG,
          'info': logging.INFO,
          'warning': logging.WARNING,
          'error': logging.ERROR,
          'critical': logging.CRITICAL}

if len(sys.argv) > 1:
    level_name = sys.argv[1]
    level = LEVELS.get(level_name, logging.NOTSET)
    logging.basicConfig(level=level)

logging.debug('This is a debug message')
logging.info('This is an info message')
logging.warning('This is a warning message')
logging.error('This is an error message')
logging.critical('This is a critical error message')

Запустите скрипт с аргументом, например, ‘debug’ или ‘warning’, чтобы увидеть, какие сообщения отображаются на разных уровнях:

$ python logging_level_example.py debug
DEBUG:root:This is a debug message
INFO:root:This is an info message
WARNING:root:This is a warning message
ERROR:root:This is an error message
CRITICAL:root:This is a critical error message

$ python logging_level_example.py info
INFO:root:This is an info message
WARNING:root:This is a warning message
ERROR:root:This is an error message
CRITICAL:root:This is a critical error message

Вы заметите, что во всех этих сообщениях журнала присутствует root. Модуль logging поддерживает иерархию логгеров с разными именами. Простой способ понять, откуда пришло конкретное сообщение, – использовать отдельный объект логгера для каждого модуля. Каждый новый логгер «наследует» конфигурацию своего родителя, а сообщения, отправленные логгеру, включают имя этого логгера. При желании каждый логгер можно настроить по-разному, чтобы сообщения от разных модулей обрабатывались различными способами. Рассмотрим простой пример логирования из разных модулей, чтобы легко было отследить источник сообщения:

import logging

logging.basicConfig(level=logging.WARNING)

logger1 = logging.getLogger('package1.module1')
logger2 = logging.getLogger('package2.module2')

logger1.warning('This message comes from one module')
logger2.warning('And this message comes from another module')

И вывод:

$ python logging_modules_example.py
WARNING:package1.module1:This message comes from one module
WARNING:package2.module2:And this message comes from another module

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

15.6.1.2. ЛоггерыLoggers

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

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

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

  • Logger.setLevel() задаёт наименьшую степень важности сообщения, которое будет обрабатывать логгер; при этом debug – это наименьший встроенный уровень важности, а critical – наибольший. Например, если уровень важности установлен в info, логгер будет обрабатывать только сообщения info, warning, error и critical, игнорируя сообщения debug.
  • 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. Дочерние логгеры передают сообщения вверх обработчикам, связанным с их логгерами-предками. Благодаря этому нет необходимости определять и настраивать обработчики для всех логгеров, используемых приложением. Достаточно настроить обработчики для логгера верхнего уровня и создавать дочерние логгеры по мере необходимости.

15.6.1.3. ОбработчикиHandlers

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

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

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

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

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

15.6.1.4. ФорматировщикиFormatters

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

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

с добавленными в конце миллисекундами.

Строка формата сообщения использует подстановку строк в стиле %(<dictionary key>)s; возможные ключи описаны в разделе Объекты Formatter.

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

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

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

15.6.1.5. Настройка логированияConfiguring Logging

Программисты могут настраивать логирование двумя способами: явно создавать логгеры, обработчики и форматтеры в главном модуле с помощью перечисленных выше методов настройки (на Python) или создавая конфигурационный файл логирования. Следующий код – пример настройки очень простого логгера, обработчика консоли и простого форматтера в модуле 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)
# добавить ch в логгер
logger.addHandler(ch)

# код "application"
logger.debug("debug message")
logger.info("info message")
logger.warn("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")

# создать логгер
logger = logging.getLogger("simpleExample")

# код "application"
logger.debug("debug message")
logger.info("info message")
logger.warn("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
datefmt=

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

$ 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: в первую очередь разделение конфигурации и кода, а также возможность для не-программистов легко изменять параметры журналирования.

15.6.1.6. Настройка логирования для библиотекиConfiguring Logging for a Library

При разработке библиотеки, которая использует логирование, необходимо уделить внимание её настройке. Если использующее приложение не использует логирование, а код библиотеки вызывает функции логирования, то в консоль выводится одноразовое сообщение “No handlers could be found for logger X.Y.Z”. Это сообщение предназначено для выявления ошибок в настройке логирования, но может сбить с толку разработчика приложения, который не знает о логировании в библиотеке.

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

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

import logging

class NullHandler(logging.Handler):
    def emit(self, record):
        pass

Экземпляр этого обработчика следует добавить к корневому логгеру пространства имён логирования, используемого библиотекой. Если всё логирование библиотеки foo осуществляется с помощью логгеров с именами вида “foo.x.y”, то код:

import logging

h = NullHandler()
logging.getLogger("foo").addHandler(h)

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

Новое в версии 3.1: Класс NullHandler.

15.6.2. Уровни логированияLogging Levels

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

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

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

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

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

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

15.6.3. Полезные обработчикиUseful Handlers

Помимо базового класса Handler, предоставляется множество полезных подклассов:

  1. Экземпляры StreamHandler отправляют сообщения в потоки (объекты, подобные файлам).
  2. Экземпляры FileHandler отправляют сообщения в файлы на диске.
  1. BaseRotatingHandler – это базовый класс для обработчиков, которые выполняют ротацию файлов журнала в определённый момент. Он не предназначен для прямого создания экземпляров. Вместо этого используйте RotatingFileHandler или TimedRotatingFileHandler.
  2. Экземпляры RotatingFileHandler отправляют сообщения в файлы на диске с поддержкой максимального размера файла журнала и ротации.
  3. Экземпляры TimedRotatingFileHandler отправляют сообщения в файлы на диске, выполняя ротацию через заданные временные интервалы.
  4. SocketHandler отправляют сообщения в TCP/IP сокеты.
  5. DatagramHandler отправляют сообщения в UDP сокеты.
  6. Экземпляры SMTPHandler отправляют сообщения на указанный адрес электронной почты.
  7. Экземпляры SysLogHandler отправляют сообщения демону syslog Unix, возможно, на удалённой машине.
  8. Экземпляры NTEventLogHandler отправляют сообщения в журнал событий Windows NT/2000/XP.
  9. Экземпляры MemoryHandler отправляют сообщения в буфер в памяти, который сбрасывается при выполнении определённых условий.
  10. Экземпляры HTTPHandler отправляют сообщения на HTTP-сервер, используя семантику GET или POST.
  11. Экземпляры WatchedFileHandler следят за файлом, в который ведут журнал. Если файл изменяется, он закрывается и открывается заново по тому же имени. Этот обработчик полезен только в Unix-подобных системах; Windows не поддерживает используемый механизм.
  1. Экземпляры NullHandler ничего не делают с сообщениями об ошибках. Они используются разработчиками библиотек, которые хотят использовать логирование, но избежать сообщения “No handlers could be found for logger XXX”, которое может отображаться, если пользователь библиотеки не настроил логирование. См. Настройка логирования для библиотеки для получения дополнительной информации.

Новое в версии 3.1: Класс NullHandler.

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

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

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

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

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

15.6.4. Функции уровня модуляModule-Level Functions

В дополнение к классам, описанным выше, существует ряд функций уровня модуля.

logging.getLogger(name=None)

Возвращает логгер с указанным именем или, если name равно None, возвращает логгер, являющийся корневым логгером иерархии. Если имя указано, оно обычно представляет собой иерархическое имя, разделённое точками, например “a”, “a.b” или “a.b.c.d”. Выбор этих имён полностью зависит от разработчика, использующего логирование.

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

logging.getLoggerClass()

Возвращает либо стандартный класс Logger, либо последний класс, переданный в setLoggerClass(). Эта функция может быть вызвана из определения нового класса, чтобы гарантировать, что установка пользовательского класса Logger не отменит настройки, уже применённые другим кодом. Например:

class MyLogger(logging.getLoggerClass()):
    # ... переопределить поведение здесь
logging.debug(msg, *args, **kwargs)

Записывает сообщение с уровнем DEBUG в корневой регистратор. msg – это форматная строка сообщения, а args – аргументы, которые объединяются с msg с помощью оператора форматирования строк. (Обратите внимание, что это означает, что в форматной строке можно использовать ключевые слова вместе с единственным аргументом-словарём.)

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

Другой необязательный именованный аргумент – extra, который можно использовать для передачи словаря, заполняющего __dict__ объекта LogRecord, созданного для события логирования, пользовательскими атрибутами. Эти пользовательские атрибуты затем можно использовать по своему усмотрению. Например, их можно включить в записываемые сообщения. Пример:

FORMAT = "%(asctime)-15s %(clientip)s %(user)-8s %(message)s"
logging.basicConfig(format=FORMAT)
d = {'clientip': '192.168.0.1', 'user': 'fbloggs'}
logging.warning("Protocol problem: %s", "connection reset", extra=d)

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

2006-02-08 22:20:02,165 192.168.0.1 fbloggs  Protocol problem: connection reset

Ключи в словаре, переданном в extra, не должны пересекаться с ключами, используемыми системой логирования. (См. документацию Formatter для получения дополнительной информации о том, какие ключи используются системой логирования.)

Если вы решите использовать эти атрибуты в логируемых сообщениях, нужно соблюдать осторожность. Например, в приведённом выше примере Formatter настроен с форматной строкой, которая ожидает 'clientip' и 'user' в словаре атрибутов LogRecord. Если они отсутствуют, сообщение не будет записано, так как возникнет исключение форматирования строки. Поэтому в этом случае всегда нужно передавать словарь extra с этими ключами.

Хотя это может быть раздражающим, эта функция предназначена для использования в специализированных обстоятельствах, таких как многопоточные серверы, где один и тот же код выполняется во многих контекстах, и интересующие условия зависят от этого контекста (например, IP-адрес удалённого клиента и имя аутентифицированного пользователя в приведённом выше примере). В таких обстоятельствах, скорее всего, будут использоваться специализированные Formatter с конкретными Handler.

logging.info(msg, *args, **kwargs)
Записывает сообщение с уровнем INFO в корневой регистратор. Аргументы интерпретируются так же, как для debug().
logging.warning(msg, *args, **kwargs)
Регистрирует сообщение с уровнем WARNING в корневом логгере. Аргументы интерпретируются так же, как для debug().
logging.error(msg, *args, **kwargs)
Записывает сообщение с уровнем ERROR в корневой регистратор. Аргументы интерпретируются так же, как для debug().
logging.critical(msg, *args, **kwargs)
Регистрирует сообщение с уровнем CRITICAL в корневом логгере. Аргументы интерпретируются так же, как для debug().
logging.exception(msg, *args)
Регистрирует сообщение с уровнем ERROR в корневом логгере. Аргументы интерпретируются так же, как для debug(). Информация об исключении добавляется в сообщение журнала. Эта функция должна вызываться только из обработчика исключений.
logging.log(level, msg, *args, **kwargs)
Регистрирует сообщение с уровнем level в корневом логгере. Остальные аргументы интерпретируются так же, как для debug().
logging.disable(lvl)
Устанавливает переопределяющий уровень lvl для всех логгеров, который действует в обход собственного уровня логгера. Когда возникает необходимость временно снизить объём вывода сообщений во всём приложении, эта функция может быть полезна. Она отключает все вызовы логирования уровня lvl и ниже, так что если вызвать её со значением INFO, то все события уровня INFO и DEBUG будут отброшены, а события уровня WARNING и выше будут обрабатываться в соответствии с действующим уровнем логгера.
logging.addLevelName(lvl, levelName)
Связывает уровень lvl с текстом levelName во внутреннем словаре, который используется для сопоставления числовых уровней с текстовым представлением, например, когда Formatter форматирует сообщение. Эта функция также может использоваться для определения собственных уровней. Единственные ограничения: все используемые уровни должны быть зарегистрированы с помощью этой функции, уровни должны быть положительными целыми числами и должны возрастать в порядке увеличения серьёзности.
logging.getLevelName(lvl)
Возвращает текстовое представление уровня логирования lvl. Если уровень является одним из предопределённых уровней CRITICAL, ERROR, WARNING, INFO или DEBUG, то возвращается соответствующая строка. Если вы связали уровни с именами с помощью addLevelName(), то возвращается имя, которое вы связали с lvl. Если передаётся числовое значение, соответствующее одному из определённых уровней, возвращается соответствующее строковое представление. В противном случае возвращается строка «Level %s» % lvl.
logging.makeLogRecord(attrdict)
Создаёт и возвращает новый экземпляр LogRecord, атрибуты которого определяются attrdict. Эта функция полезна для приёма сериализованного (pickled) LogRecord словаря атрибутов, отправленного через сокет, и восстановления его как экземпляра LogRecord на принимающей стороне.
logging.basicConfig(**kwargs)

Выполняет базовую настройку системы журналирования, создавая StreamHandler с форматтером по умолчанию Formatter и добавляя его в корневой логгер. Функции debug(), info(), warning(), error() и critical() будут автоматически вызывать basicConfig(), если для корневого логгера не определено ни одного обработчика.

Эта функция ничего не делает, если для корневого логгера уже настроены обработчики.

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

Формат Описание
filename Указывает, что создаётся FileHandler, с использованием указанного имени файла, а не StreamHandler.
filemode Задает режим открытия файла, если указан filename (если filemode не указан, по умолчанию используется ‘a’).
format Использует указанную строку формата для обработчика.
datefmt Использовать указанный формат даты/времени.
level Установить уровень корневого регистратора на указанный уровень.
поток данных Использовать указанный поток данных для инициализации StreamHandler. Обратите внимание, что этот аргумент несовместим с «filename» – если оба присутствуют, «поток данных» игнорируется.
logging.shutdown()
Сообщает системе логирования о необходимости выполнить упорядоченное завершение работы, сбрасывая и закрывая все обработчики. Эту функцию следует вызывать при завершении приложения, и после этого вызова не следует использовать систему логирования.
logging.setLoggerClass(klass)
Указывает системе логирования использовать класс klass при создании экземпляра логгера. Класс должен определять __init__() таким образом, чтобы требовался только аргумент name, а __init__() должен вызывать Logger.__init__(). Эта функция обычно вызывается до создания любых логгеров приложениями, которым требуется использовать собственное поведение логгера.

См. также

PEP 282 – Система логирования
Предложение, описывающее эту возможность для включения в стандартную библиотеку Python.
Оригинальный пакет логирования Python
Это исходный код пакета logging. Версия, доступная на этом сайте, подходит для Python 1.5.2, 2.1.x и 2.2.x, в которых пакет logging не входит в стандартную библиотеку.

15.6.5. Объекты LoggerLogger Objects

Логгеры имеют следующие атрибуты и методы. Обратите внимание, что логгеры никогда не создаются напрямую, а всегда через функцию уровня модуля logging.getLogger(name).

Logger.propagate
Если это значение равно false, сообщения логирования не передаются этим логгером или его дочерними логгерами обработчикам логгеров более высокого уровня (предков). Конструктор устанавливает этот атрибут в 1.
Logger.setLevel(lvl)

Устанавливает порог для этого логгера в lvl. Сообщения логирования, уровень которых ниже lvl, будут игнорироваться. При создании логгера уровень устанавливается в NOTSET (что приводит к обработке всех сообщений, если логгер является корневым, или к делегированию родительскому логгеру, если логгер не корневой). Обратите внимание, что корневой логгер создаётся с уровнем WARNING.

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

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

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

Logger.isEnabledFor(lvl)
Указывает, будет ли сообщение с уровнем серьёзности lvl обработано этим логгером. Этот метод сначала проверяет уровень, установленный на уровне модуля с помощью logging.disable(lvl), а затем эффективный уровень логгера, определяемый getEffectiveLevel().
Logger.getEffectiveLevel()
Указывает эффективный уровень для данного логгера. Если с помощью setLevel() было установлено значение, отличное от NOTSET, возвращается оно. В противном случае выполняется обход иерархии к корню, пока не будет найдено значение, отличное от NOTSET, и возвращается это значение.
Logger.debug(msg, *args, **kwargs)

Записывает сообщение уровня DEBUG в этот логгер. msg – это строка форматирования сообщения, а args – аргументы, которые подставляются в msg с помощью оператора форматирования строк. (Обратите внимание, что это означает, что можно использовать ключевые слова в строке форматирования вместе с одним словарным аргументом.)

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

Другим необязательным именованным аргументом является extra, который можно использовать для передачи словаря, которым заполняется __dict__ объекта LogRecord, созданного для события логирования, пользовательскими атрибутами. Затем эти пользовательские атрибуты можно использовать по своему усмотрению. Например, их можно включать в логируемые сообщения. Например:

FORMAT = "%(asctime)-15s %(clientip)s %(user)-8s %(message)s"
logging.basicConfig(format=FORMAT)
d = { 'clientip' : '192.168.0.1', 'user' : 'fbloggs' }
logger = logging.getLogger("tcpserver")
logger.warning("Protocol problem: %s", "connection reset", extra=d)

выведет что-то вроде

2006-02-08 22:20:02,165 192.168.0.1 fbloggs  Protocol problem: connection reset

Ключи в словаре, переданном в extra, не должны пересекаться с ключами, используемыми системой логирования. (См. документацию Formatter для получения дополнительной информации о том, какие ключи используются системой логирования.)

Если вы решите использовать эти атрибуты в логируемых сообщениях, нужно соблюдать осторожность. Например, в приведённом выше примере Formatter настроен с форматной строкой, которая ожидает 'clientip' и 'user' в словаре атрибутов LogRecord. Если они отсутствуют, сообщение не будет записано, так как возникнет исключение форматирования строки. Поэтому в этом случае всегда нужно передавать словарь extra с этими ключами.

Хотя это может быть раздражающим, эта функция предназначена для использования в специализированных обстоятельствах, таких как многопоточные серверы, где один и тот же код выполняется во многих контекстах, и интересующие условия зависят от этого контекста (например, IP-адрес удалённого клиента и имя аутентифицированного пользователя в приведённом выше примере). В таких обстоятельствах, скорее всего, будут использоваться специализированные Formatter с конкретными Handler.

Logger.info(msg, *args, **kwargs)
Записывает сообщение уровня INFO в этот логгер. Аргументы интерпретируются так же, как для debug().
Logger.warning(msg, *args, **kwargs)
Записывает сообщение уровня WARNING в этот логгер. Аргументы интерпретируются так же, как для debug().
Logger.error(msg, *args, **kwargs)
Записывает сообщение уровня ERROR в этот логгер. Аргументы интерпретируются так же, как для debug().
Logger.critical(msg, *args, **kwargs)
Записывает сообщение с уровнем CRITICAL в этот логгер. Аргументы интерпретируются так же, как для debug().
Logger.log(lvl, msg, *args, **kwargs)
Записывает сообщение с целочисленным уровнем lvl в этот логгер. Остальные аргументы интерпретируются так же, как для debug().
Logger.exception(msg, *args)
Записывает сообщение с уровнем ERROR в этот логгер. Аргументы интерпретируются так же, как для debug(). Информация об исключении добавляется в сообщение лога. Этот метод следует вызывать только из обработчика исключений.
Logger.addFilter(filt)
Добавляет указанный фильтр filt к этому логгеру.
Logger.removeFilter(filt)
Удаляет указанный фильтр filt из этого логгера.
Logger.filter(record)
Применяет фильтры этого регистратора к записи и возвращает истинное значение, если запись должна быть обработана.
Logger.addHandler(hdlr)
Добавляет указанный обработчик hdlr к этому логгеру.
Logger.removeHandler(hdlr)
Удаляет указанный обработчик hdlr из этого логгера.
Logger.findCaller()
Находит имя исходного файла и номер строки вызывающего кода. Возвращает имя файла, номер строки и имя функции в виде кортежа из трёх элементов.
Logger.handle(record)
Обрабатывает запись, передавая её всем обработчикам, связанным с этим логгером и его предками (пока не будет найдено ложное значение propagate). Этот метод используется для распакованных записей, полученных из сокета, а также для записей, созданных локально. Фильтрация на уровне логгера применяется с помощью filter().
Logger.makeRecord(name, lvl, fn, lno, msg, args, exc_info, func=None, extra=None)
Это фабричный метод, который можно переопределить в подклассах для создания специализированных экземпляров LogRecord.

15.6.6. Простой примерBasic example

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

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

import logging

logging.debug('A debug message')
logging.info('Some information')
logging.warning('A shot across the bows')

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

WARNING:root:A shot across the bows

Поскольку не был указан конкретный регистратор, система использовала корневой регистратор. Сообщения уровней debug и info не появились, потому что по умолчанию корневой регистратор настроен на обработку только сообщений с уровнем WARNING и выше. Формат сообщений также задаётся по умолчанию, как и место вывода сообщений – sys.stderr. Уровень, формат и место вывода можно легко изменить, как показано в примере ниже:

import logging

logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s %(levelname)s %(message)s',
                    filename='myapp.log',
                    filemode='w')
logging.debug('A debug message')
logging.info('Some information')
logging.warning('A shot across the bows')

Метод basicConfig() используется для изменения настроек по умолчанию, в результате чего вывод (записанный в myapp.log) будет выглядеть примерно так:

2004-07-02 13:00:08,743 DEBUG A debug message
2004-07-02 13:00:08,743 INFO Some information
2004-07-02 13:00:08,743 WARNING A shot across the bows

На этот раз были обработаны все сообщения с уровнем DEBUG и выше, был изменён формат сообщений, а вывод пошёл в указанный файл, а не в консоль.

Форматирование использует старый стиль форматирования строк Python – см. раздел Старые операции форматирования строк. Строка формата принимает следующие общие спецификаторы. Полный список спецификаторов можно найти в документации Formatter.

Формат Описание
%(name)s Имя регистратора (канал журналирования).
%(levelname)s Текстовый уровень логирования сообщения ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL').
%(asctime)s Человекочитаемое время создания LogRecord. По умолчанию имеет вид «2003-07-08 16:49:45,896» (цифры после запятой – миллисекунды).
%(message)s Записанное сообщение.

Чтобы изменить формат даты/времени, можно передать дополнительный именованный параметр datefmt, как показано ниже:

import logging

logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s %(levelname)-8s %(message)s',
                    datefmt='%a, %d %b %Y %H:%M:%S',
                    filename='/temp/myapp.log',
                    filemode='w')
logging.debug('A debug message')
logging.info('Some information')
logging.warning('A shot across the bows')

в результате чего вывод будет выглядеть как

Fri, 02 Jul 2004 13:06:18 DEBUG    A debug message
Fri, 02 Jul 2004 13:06:18 INFO     Some information
Fri, 02 Jul 2004 13:06:18 WARNING  A shot across the bows

Строка формата даты соответствует требованиям strftime() – см. документацию модуля time.

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

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

import logging

logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s %(levelname)-8s %(message)s',
                    datefmt='%a, %d %b %Y %H:%M:%S',
                    filename='/temp/myapp.log',
                    filemode='w')
logging.error('Pack my box with %d dozen %s', 5, 'liquor jugs')

в результате чего получится

Wed, 21 Jul 2004 15:35:16 ERROR    Pack my box with 5 dozen liquor jugs

15.6.7. Логирование в несколько мест назначенияLogging to multiple destinations

Допустим, вы хотите вести журнал в консоль и файл с разными форматами сообщений и в разных ситуациях. Предположим, вы хотите записывать сообщения уровня DEBUG и выше в файл, а сообщения уровня INFO и выше – в консоль. Также предположим, что файл должен содержать временные метки, а консольные сообщения – нет. Вот как это можно сделать:

import logging

# настроить логирование в файл – подробнее см. предыдущий раздел
logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s',
                    datefmt='%m-%d %H:%M',
                    filename='/temp/myapp.log',
                    filemode='w')
# Определяет обработчик, который записывает сообщения уровня INFO и выше в sys.stderr.
console = logging.StreamHandler()
console.setLevel(logging.INFO)
# Задает формат, упрощенный для вывода в консоль.
formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
# Указывает обработчику использовать этот формат.
console.setFormatter(formatter)
# Добавляет обработчик в корневой логгер.
logging.getLogger('').addHandler(console)

# Теперь можно выполнять запись в корневой логгер или любой другой. Сначала корневой...
logging.info('Jackdaws love my big sphinx of quartz.')

# Теперь определите несколько других логгеров, которые могут представлять разные части приложения:
# приложение:

logger1 = logging.getLogger('myapp.area1')
logger2 = logging.getLogger('myapp.area2')

logger1.debug('Quick zephyrs blow, vexing daft Jim.')
logger1.info('How quickly daft jumping zebras vex.')
logger2.warning('Jail zesty vixen who grabbed pay from quack.')
logger2.error('The five boxing wizards jump quickly.')

При запуске на консоли вы увидите

root        : INFO     Jackdaws love my big sphinx of quartz.
myapp.area1 : INFO     How quickly daft jumping zebras vex.
myapp.area2 : WARNING  Jail zesty vixen who grabbed pay from quack.
myapp.area2 : ERROR    The five boxing wizards jump quickly.

а в файле увидите примерно такое

10-22 22:19 root         INFO     Jackdaws love my big sphinx of quartz.
10-22 22:19 myapp.area1  DEBUG    Quick zephyrs blow, vexing daft Jim.
10-22 22:19 myapp.area1  INFO     How quickly daft jumping zebras vex.
10-22 22:19 myapp.area2  WARNING  Jail zesty vixen who grabbed pay from quack.
10-22 22:19 myapp.area2  ERROR    The five boxing wizards jump quickly.

Как видите, сообщение DEBUG отображается только в файле. Остальные сообщения отправляются в оба места назначения.

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

15.6.8. Исключения, возникшие во время логированияExceptions raised during logging

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

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

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

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

15.6.9. Добавление контекстной информации в вывод логированияAdding contextual information to your logging output

Иногда требуется, чтобы вывод логирования содержал контекстную информацию в дополнение к параметрам, переданным в вызов логирования. Например, в сетевом приложении может быть желательно регистрировать специфичную для клиента информацию в журнале (например, имя пользователя удаленного клиента или IP-адрес). Хотя вы можете использовать параметр extra для этого, не всегда удобно передавать информацию таким образом. Хотя может возникнуть соблазн создавать экземпляры Logger для каждого соединения, это не очень хорошая идея, потому что эти экземпляры не собираются сборщиком мусора. Хотя на практике это не проблема, когда количество экземпляров Logger зависит от уровня детализации, которую вы хотите использовать при логировании приложения, это может быть трудно управляемым, если количество экземпляров Logger становится фактически неограниченным.

15.6.9.1. Использование LoggerAdapter для передачи контекстной информацииUsing LoggerAdapters to impart contextual information

Простой способ передавать контекстную информацию для вывода вместе с событием логирования – использовать класс LoggerAdapter. Этот класс устроен так, что выглядит как Logger, поэтому можно вызывать debug(), info(), warning(), error(), exception(), critical() и log(). Сигнатуры этих методов совпадают с их аналогами в Logger, так что экземпляры обоих типов можно использовать взаимозаменяемо.

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

def debug(self, msg, *args, **kwargs):
    """
    Передаёт вызов отладки нижележащему регистратору после добавления
    контекстной информации из данного экземпляра адаптера.
    """
    msg, kwargs = self.process(msg, kwargs)
    self.logger.debug(msg, *args, **kwargs)

Метод process() класса LoggerAdapter – это место, где контекстная информация добавляется в вывод логирования. Ему передаются сообщение и именованные аргументы вызова логирования, и он возвращает (возможно) изменённые версии этих данных для использования в вызове нижележащего логгера. В реализации по умолчанию этот метод оставляет сообщение без изменений, но добавляет ключ «extra» в именованные аргументы, значением которого является объект, подобный словарю, переданный в конструктор. Разумеется, если в вызове адаптера уже был передан именованный аргумент «extra», он будет молча перезаписан.

Преимущество использования «extra» в том, что значения из объекта, подобного словарю, сливаются в LogRecord.__dict__, что позволяет использовать настроенные строки с экземплярами Formatter, которым известны ключи этого объекта. Если нужен другой подход, например, требуется добавить контекстную информацию в начало или конец строки сообщения, достаточно создать подкласс LoggerAdapter и переопределить process(), чтобы он делал то, что нужно. Вот пример скрипта, использующего этот класс, который также демонстрирует, какое поведение, подобное словарю, требуется от произвольного «dict-like» объекта для использования в конструкторе:

import logging

class ConnInfo:
    """
    Пример класса, который показывает, как произвольный класс можно использовать в качестве
    хранилища контекстной информации 'extra', передаваемого LoggerAdapter.
    """

    def __getitem__(self, name):
        """
        Чтобы этот экземпляр выглядел как словарь.
        """
        from random import choice
        if name == "ip":
            result = choice(["127.0.0.1", "192.168.0.1"])
        elif name == "user":
            result = choice(["jim", "fred", "sheila"])
        else:
            result = self.__dict__.get(name, "?")
        return result

    def __iter__(self):
        """
        Чтобы разрешить итерацию по ключам, которые будут объединены в
        словарь LogRecord перед форматированием и выводом.
        """
        keys = ["ip", "user"]
        keys.extend(self.__dict__.keys())
        return keys.__iter__()

if __name__ == "__main__":
    from random import choice
    levels = (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL)
    a1 = logging.LoggerAdapter(logging.getLogger("a.b.c"),
                               { "ip" : "123.231.231.123", "user" : "sheila" })
    logging.basicConfig(level=logging.DEBUG,
                        format="%(asctime)-15s %(name)-5s %(levelname)-8s IP: %(ip)-15s User: %(user)-8s %(message)s")
    a1.debug("A debug message")
    a1.info("An info message with %s", "some parameters")
    a2 = logging.LoggerAdapter(logging.getLogger("d.e.f"), ConnInfo())
    for x in range(10):
        lvl = choice(levels)
        lvlname = logging.getLevelName(lvl)
        a2.log(lvl, "A message at %s level with %d %s", lvlname, 2, "parameters")

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

2008-01-18 14:49:54,023 a.b.c DEBUG    IP: 123.231.231.123 User: sheila   A debug message
2008-01-18 14:49:54,023 a.b.c INFO     IP: 123.231.231.123 User: sheila   An info message with some parameters
2008-01-18 14:49:54,023 d.e.f CRITICAL IP: 192.168.0.1     User: jim      A message at CRITICAL level with 2 parameters
2008-01-18 14:49:54,033 d.e.f INFO     IP: 192.168.0.1     User: jim      A message at INFO level with 2 parameters
2008-01-18 14:49:54,033 d.e.f WARNING  IP: 192.168.0.1     User: sheila   A message at WARNING level with 2 parameters
2008-01-18 14:49:54,033 d.e.f ERROR    IP: 127.0.0.1       User: fred     A message at ERROR level with 2 parameters
2008-01-18 14:49:54,033 d.e.f ERROR    IP: 127.0.0.1       User: sheila   A message at ERROR level with 2 parameters
2008-01-18 14:49:54,033 d.e.f WARNING  IP: 192.168.0.1     User: sheila   A message at WARNING level with 2 parameters
2008-01-18 14:49:54,033 d.e.f WARNING  IP: 192.168.0.1     User: jim      A message at WARNING level with 2 parameters
2008-01-18 14:49:54,033 d.e.f INFO     IP: 192.168.0.1     User: fred     A message at INFO level with 2 parameters
2008-01-18 14:49:54,033 d.e.f WARNING  IP: 192.168.0.1     User: sheila   A message at WARNING level with 2 parameters
2008-01-18 14:49:54,033 d.e.f WARNING  IP: 127.0.0.1       User: jim      A message at WARNING level with 2 parameters

15.6.9.2. Использование фильтров для передачи контекстной информацииUsing Filters to impart contextual information

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

Например, в веб-приложении обрабатываемый запрос (или, по крайней мере, его интересные части) может храниться в переменной threadlocal (threading.local), а затем извлекаться из Filter для добавления, скажем, информации из запроса – например, удаленного IP-адреса и имени пользователя – в LogRecord, используя имена атрибутов 'ip' и 'user', как в примере с LoggerAdapter выше. В этом случае можно использовать ту же строку формата для получения аналогичного вывода, показанного выше. Вот пример скрипта:

import logging
from random import choice

class ContextFilter(logging.Filter):
    """
    Это фильтр, который внедряет контекстную информацию в журнал.

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

    USERS = ['jim', 'fred', 'sheila']
    IPS = ['123.231.231.123', '127.0.0.1', '192.168.0.1']

    def filter(self, record):

        record.ip = choice(ContextFilter.IPS)
        record.user = choice(ContextFilter.USERS)
        return True

if __name__ == "__main__":
   levels = (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL)
   a1 = logging.LoggerAdapter(logging.getLogger("a.b.c"),
                              { "ip" : "123.231.231.123", "user" : "sheila" })
   logging.basicConfig(level=logging.DEBUG,
                       format="%(asctime)-15s %(name)-5s %(levelname)-8s IP: %(ip)-15s User: %(user)-8s %(message)s")
   a1 = logging.getLogger("a.b.c")
   a2 = logging.getLogger("d.e.f")

   f = ContextFilter()
   a1.addFilter(f)
   a2.addFilter(f)
   a1.debug("A debug message")
   a1.info("An info message with %s", "some parameters")
   for x in range(10):
       lvl = choice(levels)
       lvlname = logging.getLevelName(lvl)
       a2.log(lvl, "A message at %s level with %d %s", lvlname, 2, "parameters")

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

2010-09-06 22:38:15,292 a.b.c DEBUG    IP: 123.231.231.123 User: fred     A debug message
2010-09-06 22:38:15,300 a.b.c INFO     IP: 192.168.0.1     User: sheila   An info message with some parameters
2010-09-06 22:38:15,300 d.e.f CRITICAL IP: 127.0.0.1       User: sheila   A message at CRITICAL level with 2 parameters
2010-09-06 22:38:15,300 d.e.f ERROR    IP: 127.0.0.1       User: jim      A message at ERROR level with 2 parameters
2010-09-06 22:38:15,300 d.e.f DEBUG    IP: 127.0.0.1       User: sheila   A message at DEBUG level with 2 parameters
2010-09-06 22:38:15,300 d.e.f ERROR    IP: 123.231.231.123 User: fred     A message at ERROR level with 2 parameters
2010-09-06 22:38:15,300 d.e.f CRITICAL IP: 192.168.0.1     User: jim      A message at CRITICAL level with 2 parameters
2010-09-06 22:38:15,300 d.e.f CRITICAL IP: 127.0.0.1       User: sheila   A message at CRITICAL level with 2 parameters
2010-09-06 22:38:15,300 d.e.f DEBUG    IP: 192.168.0.1     User: jim      A message at DEBUG level with 2 parameters
2010-09-06 22:38:15,301 d.e.f ERROR    IP: 127.0.0.1       User: sheila   A message at ERROR level with 2 parameters
2010-09-06 22:38:15,301 d.e.f DEBUG    IP: 123.231.231.123 User: fred     A message at DEBUG level with 2 parameters
2010-09-06 22:38:15,301 d.e.f INFO     IP: 123.231.231.123 User: fred     A message at INFO level with 2 parameters

15.6.10. Логирование в один файл из нескольких процессовLogging to a single file from multiple processes

Хотя логирование потокобезопасно, и логирование в один файл из нескольких потоков в одном процессе поддерживается, логирование в один файл из нескольких процессов не поддерживается, поскольку в Python нет стандартного способа сериализовать доступ к одному файлу из нескольких процессов. Если необходимо вести логирование в один файл из нескольких процессов, один из способов – настроить все процессы на отправку логов в SocketHandler и запустить отдельный процесс, реализующий сокет-сервер, который читает данные из сокета и пишет их в файл. (При желании можно выделить один поток в одном из существующих процессов для выполнения этой функции.) В следующем разделе этот подход описан подробнее, включая рабочий сокет-приёмник, который можно использовать как отправную точку для адаптации в собственных приложениях.

Если вы используете свежую версию Python, включающую модуль multiprocessing, можно написать собственный обработчик, который использует класс блокировки Lock из этого модуля для сериализации доступа к файлу из ваших процессов. Существующие FileHandler и его подклассы в настоящее время не используют multiprocessing, хотя в будущем могут начать. Обратите внимание: на данный момент модуль multiprocessing не предоставляет работающей функциональности блокировок на всех платформах (см. http://bugs.python.org/issue3770).

15.6.11. Отправка и получение событий логирования по сетиSending and receiving logging events across a network

Допустим, требуется отправлять события логирования по сети и обрабатывать их на принимающей стороне. Простой способ – прикрепить экземпляр SocketHandler к корневому логгеру на отправляющей стороне:

import logging, logging.handlers

rootLogger = logging.getLogger('')
rootLogger.setLevel(logging.DEBUG)
socketHandler = logging.handlers.SocketHandler('localhost',
                    logging.handlers.DEFAULT_TCP_LOGGING_PORT)
# Не нужно использовать форматтер, так как сокет-обработчик отправляет событие в виде неформатированного пикла.
# неформатированный pickle
rootLogger.addHandler(socketHandler)

# Теперь можно выполнять запись в корневой логгер или любой другой. Сначала корневой...
logging.info('Jackdaws love my big sphinx of quartz.')

# Теперь определите несколько других логгеров, которые могут представлять разные части приложения:
# приложение:

logger1 = logging.getLogger('myapp.area1')
logger2 = logging.getLogger('myapp.area2')

logger1.debug('Quick zephyrs blow, vexing daft Jim.')
logger1.info('How quickly daft jumping zebras vex.')
logger2.warning('Jail zesty vixen who grabbed pay from quack.')
logger2.error('The five boxing wizards jump quickly.')

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

import pickle
import logging
import logging.handlers
import socketserver
import struct


class LogRecordStreamHandler(socketserver.StreamRequestHandler):
    """Обработчик для потокового запроса логирования.

    Это по сути записывает запись, используя ту политику логирования, которая настроена локально.
    настроено локально.
    """

    def handle(self):
        """
        Обрабатывает несколько запросов – каждый ожидается в формате: 4-байтовая длина, затем запись журнала в формате пикл.
        Записывает запись в соответствии с политикой, настроенной локально.
        согласно локально настроенной политике.
        """
        while True:
            chunk = self.connection.recv(4)
            if len(chunk) < 4:
                break
            slen = struct.unpack(">L", chunk)[0]
            chunk = self.connection.recv(slen)
            while len(chunk) < slen:
                chunk = chunk + self.connection.recv(slen - len(chunk))
            obj = self.unPickle(chunk)
            record = logging.makeLogRecord(obj)
            self.handleLogRecord(record)

    def unPickle(self, data):
        return pickle.loads(data)

    def handleLogRecord(self, record):
        # Если указано имя, используется именованный логгер, а не тот, что
        # подразумевается записью.
        if self.server.logname is not None:
            name = self.server.logname
        else:
            name = record.name
        logger = logging.getLogger(name)
        # Примечание: КАЖДАЯ запись попадает в журнал. Это связано с тем, что Logger.handle
        # обычно вызывается ПОСЛЕ фильтрации на уровне регистратора. Если требуется
        # выполнять фильтрацию, делайте это на стороне клиента, чтобы не тратить
        # циклы процессора и сетевую пропускную способность!
        logger.handle(record)

class LogRecordSocketReceiver(socketserver.ThreadingTCPServer):
    """простой приёмник логов на основе TCP-сокетов, подходящий для тестирования.
    """

    allow_reuse_address = 1

    def __init__(self, host='localhost',
                 port=logging.handlers.DEFAULT_TCP_LOGGING_PORT,
                 handler=LogRecordStreamHandler):
        socketserver.ThreadingTCPServer.__init__(self, (host, port), handler)
        self.abort = 0
        self.timeout = 1
        self.logname = None

    def serve_until_stopped(self):
        import select
        abort = 0
        while not abort:
            rd, wr, ex = select.select([self.socket.fileno()],
                                       [], [],
                                       self.timeout)
            if rd:
                self.handle_request()
            abort = self.abort

def main():
    logging.basicConfig(
        format="%(relativeCreated)5d %(name)-15s %(levelname)-8s %(message)s")
    tcpserver = LogRecordSocketReceiver()
    print("About to start TCP server...")
    tcpserver.serve_until_stopped()

if __name__ == "__main__":
    main()

Сначала запустите сервер, затем клиент. На стороне клиента в консоль ничего не выводится; на стороне сервера вы должны увидеть что-то вроде:

About to start TCP server...
   59 root            INFO     Jackdaws love my big sphinx of quartz.
   59 myapp.area1     DEBUG    Quick zephyrs blow, vexing daft Jim.
   69 myapp.area1     INFO     How quickly daft jumping zebras vex.
   69 myapp.area2     WARNING  Jail zesty vixen who grabbed pay from quack.
   69 myapp.area2     ERROR    The five boxing wizards jump quickly.

Обратите внимание, что в некоторых сценариях использование pickle связано с рисками безопасности. Если это вас касается, вы можете использовать альтернативную схему сериализации, переопределив метод makePickle() и реализовав свою альтернативу в нём, а также адаптировав приведённый выше скрипт для работы с вашей сериализацией.

15.6.12. Использование произвольных объектов в качестве сообщенийUsing arbitrary objects as messages

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

15.6.13. ОптимизацияOptimization

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

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

чтобы, если порог регистратора установлен выше DEBUG, вызовы expensive_func1() и expensive_func2() никогда не выполнялись.

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

Что не нужно собирать Как избежать сбора
Информация о том, откуда были сделаны вызовы. Установить logging._srcfile в None.
Информация о потоках. Установите logging.logThreads в 0.
Информация о процессе. Установите logging.logProcesses в 0.

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

15.6.14. Объекты-обработчикиHandler Objects

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

Handler.__init__(level=NOTSET)
Инициализирует экземпляр Handler, устанавливая его уровень, устанавливая список фильтров в пустой список и создавая блокировку (с помощью createLock()) для сериализации доступа к механизму ввода-вывода.
Handler.createLock()
Инициализирует блокировку потока, которую можно использовать для сериализации доступа к нижележащей функциональности ввода-вывода, которая может не быть потокобезопасной.
Handler.acquire()
Захватывает блокировку потока, созданную с помощью createLock().
Handler.release()
Освобождает блокировку потока, полученную с помощью acquire().
Handler.setLevel(lvl)
Устанавливает порог для этого обработчика в lvl. Сообщения журнала, которые менее серьезны, чем lvl, будут игнорироваться. При создании обработчика уровень устанавливается в NOTSET (из-за чего обрабатываются все сообщения).
Handler.setFormatter(form)
Устанавливает Formatter для этого обработчика в form.
Handler.addFilter(filt)
Добавляет указанный фильтр фильтр в данный обработчик.
Handler.removeFilter(filt)
Удаляет указанный фильтр фильтр из данного обработчика.
Handler.filter(record)
Применяет фильтры этого обработчика к записи и возвращает true, если запись должна быть обработана.
Handler.flush()
Обеспечивает сброс всего вывода логирования. Данная версия ничего не делает и предназначена для реализации в подклассах.
Handler.close()
Освобождает все ресурсы, используемые обработчиком. Эта версия не производит вывод, но удаляет обработчик из внутреннего списка обработчиков, который закрывается при вызове shutdown(). Подклассы должны гарантировать, что этот метод вызывается из переопределенных методов close().
Handler.handle(record)
Условно выдает указанную запись логирования в зависимости от фильтров, которые могут быть добавлены к обработчику. Оборачивает фактическую выдачу записи в захват/освобождение блокировки потока ввода-вывода.
Handler.handleError(record)
Этот метод следует вызывать из обработчиков при возникновении исключения во время вызова emit(). По умолчанию он ничего не делает, то есть исключения молча игнорируются. В системе логирования это в основном то, что нужно – большинство пользователей не интересуются ошибками в системе логирования, их больше волнуют ошибки приложения. Однако при желании можно заменить этот метод собственным обработчиком. Указанная запись (record) – это та, которая обрабатывалась в момент возникновения исключения.
Handler.format(record)
Форматирует запись – если задан форматировщик, использует его. В противном случае использует форматировщик по умолчанию для модуля.
Handler.emit(record)
Делает все необходимое для фактической записи указанной записи журнала. Эта версия предназначена для реализации в подклассах и поэтому вызывает NotImplementedError.

15.6.14.1. StreamHandler

Класс StreamHandler, находящийся в основном пакете logging, направляет вывод логирования в потоки, такие как sys.stdout, sys.stderr или любой объект, похожий на файл (точнее, любой объект, поддерживающий методы write() и flush()).

class logging.StreamHandler(stream=None)

Возвращает новый экземпляр класса StreamHandler. Если указан параметр поток данных, экземпляр использует его для вывода логирования; в противном случае используется sys.stderr.

emit(record)
Если задан форматтер, он используется для форматирования записи. Затем запись записывается в поток с завершающим символом новой строки. Если присутствует информация об исключении, она форматируется с помощью traceback.print_exception() и добавляется в поток.
flush()
Сбрасывает поток данных, вызывая его метод flush(). Обратите внимание, что метод close() наследуется от Handler и поэтому не выводит ничего, так что иногда может потребоваться явный вызов flush().

15.6.14.2. FileHandler

Класс FileHandler, находящийся в основном пакете logging, направляет вывод логирования в файл на диске. Он наследует функциональность вывода от StreamHandler.

class logging.FileHandler(filename, mode='a', encoding=None, delay=0)

Возвращает новый экземпляр класса FileHandler. Указанный файл открывается и используется как поток для логирования. Если mode не указан, используется 'a'. Если encoding не равен None, файл открывается с указанной кодировкой. Если delay равен true, открытие файла откладывается до первого вызова emit(). По умолчанию файл растёт бесконечно.

close()
Закрывает файл.
emit(record)
Выводит запись в файл.

15.6.14.3. NullHandler

Новое в версии 3.1.

Класс NullHandler, находящийся в основном пакете logging, не выполняет никакого форматирования или вывода. По сути, это обработчик-пустышка, предназначенный для разработчиков библиотек.

class logging.NullHandler

Возвращает новый экземпляр класса NullHandler.

emit(record)
Этот метод ничего не делает.

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

15.6.14.4. WatchedFileHandler

Класс WatchedFileHandler, находящийся в модуле logging.handlers, представляет собой FileHandler, который следит за файлом, в который ведётся логирование. Если файл изменяется, он закрывается и открывается заново по тому же имени файла.

Файл может измениться из-за использования таких программ, как newsyslog и logrotate, которые выполняют ротацию файлов журнала. Этот обработчик, предназначенный для использования в Unix/Linux, следит, не изменился ли файл с момента последнего вызова emit. (Считается, что файл изменился, если изменились его устройство или inode.) Если файл изменился, старый поток файла закрывается, а файл открывается для получения нового потока.

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

class logging.handlers.WatchedFileHandler(filename[, mode[, encoding[, delay]]])

Возвращает новый экземпляр класса WatchedFileHandler. Указанный файл открывается и используется как поток для логирования. Если mode не указан, используется 'a'. Если encoding не равен None, файл открывается с указанной кодировкой. Если delay равен true, открытие файла откладывается до первого вызова emit(). По умолчанию файл растёт бесконечно.

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

15.6.14.5. RotatingFileHandler

Класс RotatingFileHandler, находящийся в модуле logging.handlers, поддерживает ротацию файлов журналов на диске.

class logging.handlers.RotatingFileHandler(filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=0)

Возвращает новый экземпляр класса RotatingFileHandler. Указанный файл открывается и используется как поток для журналирования. Если mode не указан, используется 'a'. Если encoding не равен None, файл открывается с указанной кодировкой. Если delay равен истине, открытие файла откладывается до первого вызова emit(). По умолчанию файл растёт бесконечно.

С помощью параметров maxBytes и backupCount можно настроить ротацию файла при достижении заданного размера. Когда размер собирается быть превышен, файл закрывается и новый файл молча открывается для записи. Ротация происходит всякий раз, когда текущий файл журнала достигает длины, близкой к maxBytes; если maxBytes равен нулю, ротация не происходит. Если backupCount не равен нулю, система сохраняет старые файлы журналов, добавляя к имени файла расширения «.1», «.2» и т.д. Например, при backupCount равном 5 и исходном имени файла app.log вы получите app.log, app.log.1, app.log.2, вплоть до app.log.5. Файл, в который ведется запись, всегда app.log. Когда этот файл заполняется, он закрывается и переименовывается в app.log.1, а если существуют файлы app.log.1, app.log.2 и т.д., то они переименовываются соответственно в app.log.2, app.log.3 и так далее.

doRollover()
Выполняет ротацию, как описано выше.
emit(record)
Записывает запись (record) в файл, выполняя при необходимости ротацию, как описано ранее.

15.6.14.6. TimedRotatingFileHandler

Класс TimedRotatingFileHandler, находящийся в модуле logging.handlers, поддерживает ротацию файлов журналов на диске через заданные промежутки времени.

class logging.handlers.TimedRotatingFileHandler(filename, when='h', interval=1, backupCount=0, encoding=None, delay=0, utc=False)

Возвращает новый экземпляр класса TimedRotatingFileHandler. Указанный файл открывается и используется как поток для журналирования. При ротации также устанавливается суффикс имени файла. Ротация происходит на основе произведения when и interval.

Параметр when указывает тип interval. Список возможных значений приведён ниже. Обратите внимание, что регистр не учитывается.

Значение Тип интервала
'S' Секунды
'M' Минуты
'H' Часы
'D' Дни
'W' День недели (0 = понедельник)
'midnight' Переключение в полночь

Система будет сохранять старые файлы журнала, добавляя расширения к имени файла. Расширения основаны на дате и времени, с использованием формата strftime %Y-%m-%d_%H-%M-%S или его начальной части, в зависимости от интервала ротации.

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

Если аргумент utc равен true, используется время в UTC; в противном случае используется местное время.

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

Если delay равен true, открытие файла откладывается до первого вызова emit().

doRollover()
Выполняет ротацию, как описано выше.
emit(record)
Выводит запись в файл, обеспечивая ротацию, как описано выше.

15.6.14.7. SocketHandler

Класс SocketHandler, расположенный в модуле logging.handlers, отправляет вывод журнала на сетевой сокет. Базовый класс использует TCP-сокет.

class logging.handlers.SocketHandler(host, port)

Возвращает новый экземпляр класса SocketHandler, предназначенного для взаимодействия с удаленной машиной, адрес которой задается host и port.

close()
Закрывает сокет.
emit()
Сериализует словарь атрибутов записи и записывает его в сокет в бинарном формате. Если при работе с сокетом возникает ошибка, пакет молча отбрасывается. Если соединение было ранее потеряно, оно восстанавливается. Для десериализации записи на принимающей стороне в LogRecord используйте функцию makeLogRecord().
handleError()
Обрабатывает ошибку, возникшую во время emit(). Наиболее вероятная причина – потеря соединения. Закрывает сокет, чтобы можно было повторить попытку при следующем событии.
makeSocket()
Это фабричный метод, позволяющий подклассам определять точный тип сокета, который они хотят. Реализация по умолчанию создает TCP-сокет (socket.SOCK_STREAM).
makePickle(record)

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

Обратите внимание, что модуль pickle не является полностью безопасным. Если вас беспокоит безопасность, вы можете переопределить этот метод, чтобы реализовать более надёжный механизм. Например, можно подписывать данные с помощью HMAC и проверять подпись на принимающей стороне, либо отключить десериализацию глобальных объектов на принимающей стороне.

send(packet)
Отправляет сериализованную с помощью pickle строку packet в сокет. Эта функция допускает частичную отправку, которая может произойти, когда сеть занята.

15.6.14.8. DatagramHandler

Класс DatagramHandler, расположенный в модуле logging.handlers , наследуется от SocketHandler для поддержки отправки сообщений журнала через UDP-сокеты.

class logging.handlers.DatagramHandler(host, port)

Возвращает новый экземпляр класса DatagramHandler, предназначенного для взаимодействия с удаленной машиной, адрес которой задается host и port.

emit()
Сериализует словарь атрибутов записи и записывает его в сокет в бинарном формате. Если при работе с сокетом возникает ошибка, пакет молча отбрасывается. Для десериализации записи на принимающей стороне в LogRecord используйте функцию makeLogRecord().
makeSocket()
Фабричный метод SocketHandler здесь переопределен для создания UDP-сокета (socket.SOCK_DGRAM).
send(s)
Отправляет сериализованную с помощью pickle строку в сокет.

15.6.14.9. SysLogHandler

Класс SysLogHandler, расположенный в модуле logging.handlers, поддерживает отправку сообщений журнала в удаленный или локальный системный журнал Unix.

class logging.handlers.SysLogHandler(address=('localhost', SYSLOG_UDP_PORT), facility=LOG_USER)

Возвращает новый экземпляр класса SysLogHandler, предназначенный для связи с удаленной Unix-машиной, адрес которой задается параметром address в виде кортежа (host, port). Если address не указан, используется ('localhost', 514). Адрес используется для открытия UDP-сокета. Альтернативой кортежу (host, port) является указание адреса в виде строки, например “/dev/log”. В этом случае для отправки сообщения в syslog используется сокет домена Unix. Если facility не указан, используется LOG_USER.

close()
Закрывает сокет, подключённый к удалённому узлу.
emit(record)
Запись форматируется и отправляется на сервер syslog. Если присутствует информация об исключении, она не отправляется на сервер.
encodePriority(facility, priority)

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

Символические значения LOG_ определены в SysLogHandler и соответствуют значениям, определённым в заголовочном файле sys/syslog.h.

Приоритеты

Имя (строка) Символическое значение
alert LOG_ALERT
crit или critical LOG_CRIT
отладка LOG_DEBUG
аварийная или паника LOG_EMERG
ошибка или ошибка LOG_ERR
информация LOG_INFO
уведомление LOG_NOTICE
предупреждение или предупреждение LOG_WARNING

Средства

Имя (строка) Символическое значение
аутентификация LOG_AUTH
authpriv LOG_AUTHPRIV
cron LOG_CRON
демон LOG_DAEMON
ftp LOG_FTP
ядро LOG_KERN
lpr LOG_LPR
почта LOG_MAIL
новости LOG_NEWS
syslog LOG_SYSLOG
user LOG_USER
uucp LOG_UUCP
local0 LOG_LOCAL0
local1 LOG_LOCAL1
local2 LOG_LOCAL2
local3 LOG_LOCAL3
local4 LOG_LOCAL4
local5 LOG_LOCAL5
local6 LOG_LOCAL6
local7 LOG_LOCAL7
mapPriority(levelname)
Сопоставляет имя уровня журналирования с именем приоритета syslog. Возможно, потребуется переопределить его, если используются пользовательские уровни или стандартный алгоритм не подходит для ваших нужд. Стандартный алгоритм сопоставляет DEBUG, INFO, WARNING, ERROR и CRITICAL с соответствующими именами syslog, а все остальные имена уровней – с «warning».

15.6.14.10. NTEventLogHandler

Класс NTEventLogHandler, находящийся в модуле logging.handlers, поддерживает отправку сообщений журналирования в журнал событий локальной Windows NT, Windows 2000 или Windows XP. Перед использованием необходимо установить расширения Win32 от Марка Хэммонда для Python.

class logging.handlers.NTEventLogHandler(appname, dllname=None, logtype='Application')

Возвращает новый экземпляр класса NTEventLogHandler. Параметр appname используется для определения имени приложения, отображаемого в журнале событий. С использованием этого имени создается соответствующая запись реестра. Параметр dllname должен содержать полный путь к .dll или .exe-файлу с определениями сообщений для хранения в журнале (если не указан, используется 'win32service.pyd' – он устанавливается вместе с расширениями Win32 и содержит несколько базовых определений сообщений-заполнителей. Обратите внимание, что использование этих заполнителей приведет к увеличению размера журнала, так как весь источник сообщений хранится в журнале. Если нужны более компактные журналы, следует передать имя собственного .dll или .exe, содержащего нужные определения сообщений). Параметр logtype может быть одним из 'Application', 'System' или 'Security', по умолчанию 'Application'.

close()
На этом этапе можно удалить имя приложения из реестра как источник записей журнала событий. Однако после этого события не будут отображаться в средстве просмотра журнала событий так, как предполагалось – для получения имени .dll требуется доступ к реестру. Текущая версия этого не делает.
emit(record)
Определяет идентификатор сообщения, категорию и тип события, а затем записывает сообщение в журнал событий NT.
getEventCategory(record)
Возвращает категорию события для записи. Переопределите этот метод, чтобы указать собственные категории. Данная версия возвращает 0.
getEventType(record)
Возвращает тип события для записи. Переопределите этот метод, если нужно задать собственные типы. В данной версии используется сопоставление через атрибут typemap обработчика, который устанавливается в __init__() в виде словаря, содержащего сопоставления для DEBUG, INFO, WARNING, ERROR и CRITICAL. При использовании собственных уровней потребуется либо переопределить этот метод, либо поместить подходящий словарь в атрибут typemap обработчика.
getMessageID(record)
Возвращает идентификатор сообщения для записи. При использовании собственных сообщений можно передать в логгер идентификатор msg вместо строки форматирования. Затем здесь можно выполнить поиск по словарю для получения идентификатора сообщения. Данная версия возвращает 1 – базовый идентификатор сообщения в win32service.pyd.

15.6.14.11. SMTPHandler

Класс SMTPHandler, находящийся в модуле logging.handlers, поддерживает отправку сообщений журналирования на адрес электронной почты через SMTP.

class logging.handlers.SMTPHandler(mailhost, fromaddr, toaddrs, subject, credentials=None)

Возвращает новый экземпляр класса SMTPHandler. Экземпляр инициализируется адресами отправителя, получателя и темой письма. Параметр toaddrs должен быть списком строк. Для указания нестандартного порта SMTP используется кортеж (host, port) в аргументе mailhost. Если передана строка, используется стандартный порт SMTP. Если SMTP-сервер требует аутентификации, можно указать кортеж (username, password) в аргументе credentials.

emit(record)
Форматирует запись и отправляет её указанным получателям.
getSubject(record)
Чтобы задать тему письма, зависящую от записи, переопределите этот метод.

15.6.14.12. MemoryHandler

Класс MemoryHandler, находящийся в модуле logging.handlers, поддерживает буферизацию записей журналирования в памяти с периодической отправкой их в обработчик target. Сброс происходит, когда буфер полон или когда появляется событие определённой важности или выше.

MemoryHandler является подклассом более общего BufferingHandler, который является абстрактным классом. Он буферизирует записи журналирования в памяти. Каждый раз, когда запись добавляется в буфер, вызывается shouldFlush() для проверки необходимости сброса буфера. Если сброс нужен, то flush() должен выполнить необходимые действия.

class logging.handlers.BufferingHandler(capacity)

Инициализирует обработчик буфером указанной ёмкости.

emit(record)
Добавляет запись в буфер. Если shouldFlush() возвращает true, вызывает flush() для обработки буфера.
flush()
Вы можете переопределить этот метод, чтобы задать собственное поведение при сбросе. Текущая версия просто очищает буфер.
shouldFlush(record)
Возвращает true, если буфер заполнен до ёмкости. Этот метод можно переопределить для реализации собственных стратегий сброса.
class logging.handlers.MemoryHandler(capacity, flushLevel=ERROR, target=None)

Возвращает новый экземпляр класса MemoryHandler. Экземпляр инициализируется с размером буфера capacity. Если flushLevel не указан, используется ERROR. Если target не указан, перед тем как обработчик начнёт работать, цель нужно задать с помощью setTarget().

close()
Вызывает flush(), устанавливает цель в None и очищает буфер.
flush()
Для MemoryHandler сброс означает просто отправку буферизированных записей целевому обработчику, если он есть. Переопределите, если требуется иное поведение.
setTarget(target)
Устанавливает целевой обработчик для данного обработчика.
shouldFlush(record)
Проверяет, заполнен ли буфер или поступила запись уровня flushLevel или выше.

15.6.14.13. HTTPHandler

Класс HTTPHandler, расположенный в модуле logging.handlers, поддерживает отправку сообщений журнала на веб-сервер, используя семантику GET или POST.

class logging.handlers.HTTPHandler(host, url, method='GET')

Возвращает новый экземпляр класса HTTPHandler. Экземпляр инициализируется адресом хоста, URL и HTTP-методом. host может быть в формате host:port, если требуется указать определённый номер порта. Если method не указан, используется GET.

emit(record)
Отправляет запись на веб-сервер в виде словаря в процентной кодировке.

15.6.15. Объекты FormatterFormatter Objects

Formatter имеют следующие атрибуты и методы. Они отвечают за преобразование LogRecord в (обычно) строку, которая может интерпретироваться человеком или внешней системой. Базовый Formatter позволяет задать строку форматирования. Если она не указана, используется значение по умолчанию '%(message)s'.

Объект Formatter можно инициализировать строкой форматирования, использующей атрибуты LogRecord, например, как в упомянутом выше значении по умолчанию, использующем тот факт, что сообщение пользователя и аргументы предварительно форматируются в атрибут LogRecord's message. Эта строка форматирования содержит стандартные %-ключи сопоставления Python. Дополнительную информацию о форматировании строк см. в разделе Old String Formatting Operations.

Полезные ключи отображения в LogRecord на данный момент:

Формат Описание
%(name)s Имя логгера (канала журналирования).
%(levelno)s Числовой уровень логирования сообщения (DEBUG, INFO, WARNING, ERROR, CRITICAL).
%(levelname)s Текстовый уровень логирования сообщения ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL').
%(pathname)s Полный путь к исходному файлу, из которого был сделан вызов логирования (если доступен).
%(filename)s Имя файла из пути.
%(module)s Модуль (часть имени файла).
%(funcName)s Имя функции, содержащей вызов логирования.
%(lineno)d Номер строки исходного кода, из которой был сделан вызов логирования (если доступен).
%(created)f Время создания LogRecord (возвращаемое time.time()).
%(relativeCreated)d Время в миллисекундах, когда LogRecord был создан, относительно времени загрузки модуля logging.
%(asctime)s Человекочитаемое время создания LogRecord. По умолчанию оно имеет вид “2003-07-08 16:49:45,896” (числа после запятой – миллисекунды времени).
%(msecs)d Миллисекундная часть времени, когда был создан LogRecord.
%(поток)d Идентификатор потока (если доступен).
%(threadName)s Имя потока (если доступно).
%(процесс)d Идентификатор процесса (если доступен).
%(processName)s Имя процесса (если доступно).
%(message)s Записанное сообщение, вычисляемое как msg % args.
class logging.Formatter(fmt=None, datefmt=None)

Возвращает новый экземпляр класса Formatter. Экземпляр инициализируется строкой форматирования для всего сообщения, а также строкой форматирования для части даты/времени сообщения. Если fmt не указан, используется '%(message)s'. Если datefmt не указан, используется формат даты ISO8601.

format(record)
Словарь атрибутов записи используется как операнд для операции форматирования строки. Возвращает результирующую строку. Перед форматированием словаря выполняется несколько подготовительных шагов. Атрибут message записи вычисляется с помощью msg % args. Если строка форматирования содержит '(asctime)', вызывается formatTime() для форматирования времени события. Если присутствует информация об исключении, она форматируется с помощью formatException() и добавляется к сообщению. Обратите внимание, что отформатированная информация об исключении кэшируется в атрибуте exc_text. Это полезно, поскольку информацию об исключении можно сериализовать и отправить по сети, но следует быть осторожным, если имеется более одного подкласса Formatter, который настраивает форматирование информации об исключении. В этом случае вам придется очищать кэшированное значение после того, как форматировщик выполнит свое форматирование, чтобы следующий форматировщик, обрабатывающий событие, не использовал кэшированное значение, а пересчитал его заново.
formatTime(record, datefmt=None)
Этот метод должен вызываться из format() форматировщиком, который хочет использовать отформатированное время. Этот метод может быть переопределен в форматировщиках для любых специфических требований, но базовое поведение следующее: если указан datefmt (строка), он используется с time.strftime() для форматирования времени создания записи. В противном случае используется формат ISO8601. Возвращается результирующая строка.
formatException(exc_info)
Форматирует указанную информацию об исключении (стандартный кортеж исключения, возвращаемый sys.exc_info()) в виде строки. Эта реализация по умолчанию просто использует traceback.print_exception(). Полученная строка возвращается.

15.6.16. Объекты FilterFilter Objects

Filter могут использоваться обработчиками Handler и регистраторами Logger для более тонкой фильтрации, чем предусмотрено уровнями. Базовый класс фильтра допускает только события, которые находятся ниже определённой точки в иерархии регистраторов. Например, фильтр, инициализированный строкой “A.B”, будет пропускать события, зарегистрированные регистраторами “A.B”, “A.B.C”, “A.B.C.D”, “A.B.D” и т.д., но не “A.BB”, “B.A.B” и т.д. Если инициализирован пустой строкой, пропускаются все события.

class logging.Filter(name='')

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

filter(record)
Следует ли регистрировать указанную запись? Возвращает ноль, если нет, и ненулевое значение, если да. Если это уместно, запись может быть изменена на месте данным методом.

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

15.6.17. Объекты LogRecordLogRecord Objects

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

class logging.LogRecord(name, lvl, pathname, lineno, msg, args, exc_info, func=None)

Содержит всю информацию, относящуюся к регистрируемому событию.

Основная информация передаётся в параметрах msg и args, которые объединяются с помощью msg % args для создания поля message записи.

args
Кортеж аргументов, используемых при форматировании msg.
exc_info
Кортеж исключения (наподобие sys.exc_info) или None, если информация об исключении недоступна.
func
Имя исходной функции (то есть в которой был выполнен вызов регистрации).
lineno
Номер строки в исходном файле.
lvl
Числовой уровень регистрации.
message
Связывается с результатом getMessage() при вызове Formatter.format(record).
msg
Предоставленная пользователем строка форматирования или произвольный объект (см. Использование произвольных объектов в качестве сообщений), используемый в getMessage().
name
Имя регистратора, который выдал запись.
pathname
Абсолютный путь к исходному файлу.
getMessage()
Возвращает сообщение для данного экземпляра LogRecord после объединения любых предоставленных пользователем аргументов с сообщением.

15.6.18. Объекты LoggerAdapterLoggerAdapter Objects

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

class logging.LoggerAdapter(logger, extra)

Возвращает экземпляр LoggerAdapter, инициализированный базовым экземпляром Logger и объектом, подобным словарю.

process(msg, kwargs)
Изменяет сообщение и/или именованные аргументы, переданные в вызов логирования, чтобы вставить контекстную информацию. Эта реализация берёт объект, переданный как extra конструктору, и добавляет его в kwargs с ключом 'extra'. Возвращаемое значение – кортеж (msg, kwargs), содержащий (возможно изменённые) версии переданных аргументов.

Помимо перечисленного, LoggerAdapter поддерживает все методы логирования класса Logger, то есть debug(), info(), warning(), error(), exception(), critical() и log(). Эти методы имеют те же сигнатуры, что и их аналоги в Logger, поэтому экземпляры обоих типов можно использовать взаимозаменямо.

Метод isEnabledFor() был добавлен в LoggerAdapter. Этот метод делегирует вызов базовому логгеру.

15.6.19. ПотокобезопасностьThread Safety

Модуль logging спроектирован так, чтобы быть потокобезопасным без каких-либо специальных действий требуемых от его клиентов. Это достигается за счёт использования threading блокировок; одна блокировка сериализует доступ к общим данным модуля, а каждый обработчик также создаёт блокировку для сериализации доступа к своему нижележащему вводу-выводу.

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

15.6.20. КонфигурацияConfiguration

15.6.20.1. Функции конфигурацииConfiguration functions

Следующие функции настраивают модуль логирования. Они находятся в модуле logging.config. Их использование необязательно – настроить модуль логирования можно как с помощью этих функций, так и напрямую через основной API (определённый в самом logging) и определением обработчиков, объявленных либо в logging, либо в logging.handlers.

logging.fileConfig(fname, defaults=None, disable_existing_loggers=True)

Читает конфигурацию логирования из файла формата configparser с именем fname. Эту функцию можно вызывать несколько раз из приложения, что позволяет конечному пользователю выбирать из различных готовых конфигураций (если разработчик предоставляет механизм для отображения вариантов и загрузки выбранной конфигурации). Значения по умолчанию, передаваемые в ConfigParser, можно указать в аргументе defaults.

Если disable_existing_loggers равен True, все существующие логгеры, которые не являются дочерними по отношению к именованным логгерам, будут отключены.

logging.listen(port=DEFAULT_LOGGING_CONFIG_PORT)

Запускает сокет-сервер на указанном порту и ожидает новые конфигурации. Если порт не указан, используется значение по умолчанию модуля DEFAULT_LOGGING_CONFIG_PORT. Конфигурации логирования отправляются в виде файла, пригодного для обработки функцией fileConfig(). Возвращает экземпляр поток, на котором можно вызвать start() для запуска сервера и к которому при необходимости можно применить join(). Чтобы остановить сервер, вызовите stopListening().

Чтобы отправить конфигурацию в сокет, прочитайте файл конфигурации и отправьте его в сокет в виде строки байтов, перед которой идёт четырёхбайтовая длина, упакованная в двоичном виде с помощью struct.pack('>L', n).

logging.stopListening()
Останавливает сервер прослушивания, созданный вызовом listen(). Обычно вызывается перед вызовом join() на возвращаемом значении listen().

15.6.20.2. Формат файла конфигурацииConfiguration file format

The configuration file format understood by fileConfig() is based on configparser functionality. The file must contain sections called [loggers], [handlers] and [formatters] which identify by name the entities of each type which are defined in the file. For each such entity, there is a separate section which identifies how that entity is configured. Thus, for a logger named log01 in the [loggers] section, the relevant configuration details are held in a section [logger_log01]. Similarly, a handler called hand01 in the [handlers] section will have its configuration held in a section called [handler_hand01], while a formatter called form01 in the [formatters] section will have its configuration specified in a section called [formatter_form01]. The root logger configuration must be specified in a section called [logger_root].

Примеры таких разделов в файле приведены ниже.

[loggers]
keys=root,log02,log03,log04,log05,log06,log07

[handlers]
keys=hand01,hand02,hand03,hand04,hand05,hand06,hand07,hand08,hand09

[formatters]
keys=form01,form02,form03,form04,form05,form06,form07,form08,form09

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

[logger_root]
level=NOTSET
handlers=hand01

The level entry can be one of DEBUG, INFO, WARNING, ERROR, CRITICAL or NOTSET. For the root logger only, NOTSET means that all messages will be logged. Level values are eval()uated in the context of the logging package’s namespace.

The handlers entry is a comma-separated list of handler names, which must appear in the [handlers] section. These names must appear in the [handlers] section and have corresponding sections in the configuration file.

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

[logger_parser]
level=DEBUG
handlers=hand01
propagate=1
qualname=compiler.parser

The level and handlers entries are interpreted as for the root logger, except that if a non-root logger’s level is specified as NOTSET, the system consults loggers higher up the hierarchy to determine the effective level of the logger. The propagate entry is set to 1 to indicate that messages must propagate to handlers higher up the logger hierarchy from this logger, or 0 to indicate that messages are not propagated to handlers up the hierarchy. The qualname entry is the hierarchical channel name of the logger, that is to say the name used by the application to get the logger.

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

[handler_hand01]
class=StreamHandler
level=NOTSET
formatter=form01
args=(sys.stdout,)

Запись class указывает класс обработчика (определяемый с помощью eval() в пространстве имён пакета logging). Значение level интерпретируется так же, как для логгеров, а NOTSET означает «логировать всё».

The formatter entry indicates the key name of the formatter for this handler. If blank, a default formatter (logging._defaultFormatter) is used. If a name is specified, it must appear in the [formatters] section and have a corresponding section in the configuration file.

Запись args при вычислении eval() в контексте пространства имён пакета logging представляет собой список аргументов для конструктора класса обработчика. Обратитесь к конструкторам соответствующих обработчиков или к примерам ниже, чтобы узнать, как формируются типичные записи.

[handler_hand02]
class=FileHandler
level=DEBUG
formatter=form02
args=('python.log', 'w')

[handler_hand03]
class=handlers.SocketHandler
level=INFO
formatter=form03
args=('localhost', handlers.DEFAULT_TCP_LOGGING_PORT)

[handler_hand04]
class=handlers.DatagramHandler
level=WARN
formatter=form04
args=('localhost', handlers.DEFAULT_UDP_LOGGING_PORT)

[handler_hand05]
class=handlers.SysLogHandler
level=ERROR
formatter=form05
args=(('localhost', handlers.SYSLOG_UDP_PORT), handlers.SysLogHandler.LOG_USER)

[handler_hand06]
class=handlers.NTEventLogHandler
level=CRITICAL
formatter=form06
args=('Python Application', '', 'Application')

[handler_hand07]
class=handlers.SMTPHandler
level=WARN
formatter=form07
args=('localhost', 'from@abc', ['user1@abc', 'user2@xyz'], 'Logger Subject')

[handler_hand08]
class=handlers.MemoryHandler
level=NOTSET
formatter=form08
target=
args=(10, ERROR)

[handler_hand09]
class=handlers.HTTPHandler
level=NOTSET
formatter=form09
args=('localhost:9022', '/log', 'GET')

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

[formatter_form01]
format=F1 %(asctime)s %(levelname)s %(message)s
datefmt=
class=logging.Formatter

Запись format – это общая строка формата, а запись datefmt – это строка формата даты/времени, совместимая с strftime(). Если она пуста, пакет подставляет дату/время в формате ISO8601, что почти эквивалентно указанию строки формата даты "%Y-%m-%d %H:%M:%S". Формат ISO8601 также задаёт миллисекунды, которые добавляются к результату использования указанной выше строки формата через запятую. Пример времени в формате ISO8601: 2003-01-23 00:29:50,411.

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

15.6.20.3. Пример сервера конфигурацииConfiguration server example

Вот пример модуля, использующего сервер конфигурации журналирования:

import logging
import logging.config
import time
import os

# Прочитать начальный конфигурационный файл.
logging.config.fileConfig("logging.conf")

# Создать и запустить слушатель на порту 9999.
t = logging.config.listen(9999)
t.start()

logger = logging.getLogger("simpleExample")

try:
    # Прокрутить вызовы логирования, чтобы увидеть разницу.
    # Создавать новые конфигурации до нажатия Ctrl+C.
    while True:
        logger.debug("debug message")
        logger.info("info message")
        logger.warn("warn message")
        logger.error("error message")
        logger.critical("critical message")
        time.sleep(5)
except KeyboardInterrupt:
    # очистка
    logging.config.stopListening()
    t.join()

А вот скрипт, который принимает имя файла и отправляет этот файл на сервер, предварив его двоично-закодированной длиной, в качестве новой конфигурации журналирования:

#!/usr/bin/env python
import socket, sys, struct

data_to_send = open(sys.argv[1], "r").read()

HOST = 'localhost'
PORT = 9999
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print("connecting...")
s.connect((HOST, PORT))
print("sending config...")
s.send(struct.pack(">L", len(data_to_send)))
s.send(data_to_send)
s.close()
print("complete")

15.6.21. Дополнительные примерыMore examples

15.6.21.1. Несколько обработчиков и форматировщиковMultiple handlers and formatters

Логгеры – это обычные объекты Python. У метода addHandler() нет минимального или максимального ограничения на количество добавляемых обработчиков. Иногда бывает полезно, чтобы приложение записывало все сообщения всех уровней в текстовый файл и одновременно выводило ошибки и выше на консоль. Чтобы это настроить, достаточно сконфигурировать соответствующие обработчики. Вызовы логирования в коде приложения останутся без изменений. Вот небольшая модификация предыдущего простого примера конфигурации на основе модуля:

import logging

logger = logging.getLogger("simple_example")
logger.setLevel(logging.DEBUG)
# создать файловый обработчик, записывающий даже отладочные сообщения
fh = logging.FileHandler("spam.log")
fh.setLevel(logging.DEBUG)
# создать консольный обработчик с более высоким уровнем логирования
ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
# создать форматтер и добавить его к обработчикам
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
ch.setFormatter(formatter)
fh.setFormatter(formatter)
# добавить обработчики в логгер
logger.addHandler(ch)
logger.addHandler(fh)

# код "application"
logger.debug("debug message")
logger.info("info message")
logger.warn("warn message")
logger.error("error message")
logger.critical("critical message")

Обратите внимание, что код «приложения» безразличен к наличию нескольких обработчиков. Всё, что изменилось – это добавление и настройка нового обработчика с именем fh.

Возможность создавать новые обработчики с фильтрами более высокого или низкого уровня серьезности может быть очень полезна при написании и тестировании приложения. Вместо использования множества операторов print для отладки используйте logger.debug: в отличие от операторов print, которые вам придется удалить или закомментировать позже, операторы logger.debug могут оставаться в исходном коде и оставаться неактивными до тех пор, пока они снова не понадобятся. В этом случае единственное, что нужно сделать, – изменить уровень серьезности логгера и/или обработчика на debug.

15.6.21.2. Использование логирования в нескольких модуляхUsing logging in multiple modules

Выше упоминалось, что многократные вызовы logging.getLogger('someLogger') возвращают ссылку на один и тот же объект логгера. Это верно не только в пределах одного модуля, но и между модулями, пока они находятся в одном процессе интерпретатора Python. Это справедливо для ссылок на один и тот же объект; кроме того, код приложения может определить и настроить родительский логгер в одном модуле и создать (но не настраивать) дочерний логгер в другом модуле, и все вызовы логгера к дочернему будут передаваться родительскому. Вот пример главного модуля:

import logging
import auxiliary_module

# создать логгер с именем "spam_application"
logger = logging.getLogger("spam_application")
logger.setLevel(logging.DEBUG)
# создать файловый обработчик, записывающий даже отладочные сообщения
fh = logging.FileHandler("spam.log")
fh.setLevel(logging.DEBUG)
# создать консольный обработчик с более высоким уровнем логирования
ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
# создать форматтер и добавить его к обработчикам
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
fh.setFormatter(formatter)
ch.setFormatter(formatter)
# добавить обработчики к логгеру
logger.addHandler(fh)
logger.addHandler(ch)

logger.info("creating an instance of auxiliary_module.Auxiliary")
a = auxiliary_module.Auxiliary()
logger.info("created an instance of auxiliary_module.Auxiliary")
logger.info("calling auxiliary_module.Auxiliary.do_something")
a.do_something()
logger.info("finished auxiliary_module.Auxiliary.do_something")
logger.info("calling auxiliary_module.some_function()")
auxiliary_module.some_function()
logger.info("done with auxiliary_module.some_function()")

А вот вспомогательный модуль:

import logging

# создать логгер
module_logger = logging.getLogger("spam_application.auxiliary")

class Auxiliary:
    def __init__(self):
        self.logger = logging.getLogger("spam_application.auxiliary.Auxiliary")
        self.logger.info("creating an instance of Auxiliary")
    def do_something(self):
        self.logger.info("doing something")
        a = 1 + 1
        self.logger.info("done doing something")

def some_function():
    module_logger.info("received a call to \"some_function\"")

Вывод выглядит так:

2005-03-23 23:47:11,663 - spam_application - INFO -
   creating an instance of auxiliary_module.Auxiliary
2005-03-23 23:47:11,665 - spam_application.auxiliary.Auxiliary - INFO -
   creating an instance of Auxiliary
2005-03-23 23:47:11,665 - spam_application - INFO -
   created an instance of auxiliary_module.Auxiliary
2005-03-23 23:47:11,668 - spam_application - INFO -
   calling auxiliary_module.Auxiliary.do_something
2005-03-23 23:47:11,668 - spam_application.auxiliary.Auxiliary - INFO -
   doing something
2005-03-23 23:47:11,669 - spam_application.auxiliary.Auxiliary - INFO -
   done doing something
2005-03-23 23:47:11,670 - spam_application - INFO -
   finished auxiliary_module.Auxiliary.do_something
2005-03-23 23:47:11,671 - spam_application - INFO -
   calling auxiliary_module.some_function()
2005-03-23 23:47:11,672 - spam_application.auxiliary - INFO -
   received a call to "some_function"
2005-03-23 23:47:11,673 - spam_application - INFO -
   done with auxiliary_module.some_function()