logging.config — Конфигурация ведения журнала

Источник: Lib/logging/config.py


В этом разделе описывается API для настройки модуля протоколирования.

Функции конфигурации

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

logging.config.dictConfig(config)

Берет конфигурацию протоколирования из словаря. Содержимое этого словаря описано в Схема словаря конфигурации ниже.

Если в процессе конфигурирования возникла ошибка, эта функция выдаст сообщение ValueError, TypeError, AttributeError или ImportError с соответствующим описанием. Ниже приведен (возможно, неполный) список условий, при которых возникает ошибка:

  • Значение level, которое не является строкой или является строкой, не соответствующей фактическому уровню протоколирования.

  • Значение propagate, которое не является булевой функцией.

  • Идентификатор, не имеющий соответствующего назначения.

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

  • Недопустимое имя регистратора.

  • Неспособность определить внутренний или внешний объект.

Парсинг выполняется классом DictConfigurator, конструктору которого передается словарь, используемый для конфигурации, и метод configure(). Модуль logging.config имеет вызываемый атрибут dictConfigClass, который изначально имеет значение DictConfigurator. Вы можете заменить значение dictConfigClass на подходящую собственную реализацию.

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

def dictConfig(config):
    dictConfigClass(config).configure()

Например, подкласс DictConfigurator может вызвать DictConfigurator.__init__() в своем собственном __init__(), затем установить пользовательские префиксы, которые будут использоваться в последующем вызове configure(). dictConfigClass будет привязан к этому новому подклассу, а затем dictConfig() может быть вызван точно так же, как и в стандартном, не настроенном состоянии.

Added in version 3.2.

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

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

Он поднимет значение FileNotFoundError, если файл не существует, и RuntimeError, если файл недействителен или пуст.

Параметры:
  • fname – Имя файла, или файлоподобный объект, или экземпляр, производный от RawConfigParser. Если передан экземпляр, производный от RawConfigParser, он используется как есть. В противном случае инстанцируется ConfigParser, и конфигурация считывается им из объекта, переданного в fname. Если у этого объекта есть метод readline(), предполагается, что это файлоподобный объект, и он считывается с помощью read_file(); в противном случае предполагается, что это имя файла, и оно передается в read().

  • defaults – В этом аргументе можно указать значения по умолчанию, которые будут передаваться в ConfigParser.

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

  • encoding – Кодировка, используемая для открытия файла, когда fname - это имя файла.

Изменено в версии 3.4: Экземпляр подкласса RawConfigParser теперь принимается в качестве значения для fname. Это облегчает:

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

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

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

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

logging.config.listen(port=DEFAULT_LOGGING_CONFIG_PORT, verify=None)

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

Аргумент verify, если он указан, должен представлять собой вызываемый модуль, который должен проверить, являются ли байты, полученные через сокет, действительными и должны ли они быть обработаны. Это может быть сделано путем шифрования и/или подписи того, что отправляется через сокет, так что вызываемый verify может выполнить проверку подписи и/или дешифровку. Вызываемый модуль verify вызывается с единственным аргументом - байтами, полученными через сокет, и должен возвращать байты для обработки или None, чтобы указать, что байты должны быть отброшены. Возвращаемые байты могут быть такими же, как и переданные (например, когда выполняется только проверка), а могут быть совершенно другими (возможно, если выполняется расшифровка).

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

Примечание

Поскольку часть конфигурации передается через eval(), использование этой функции может подвергнуть ее пользователей риску безопасности. Хотя функция привязывается только к сокету на localhost и не принимает соединения с удаленных машин, существуют сценарии, в которых недоверенный код может быть запущен под учетной записью процесса, вызывающего listen(). В частности, если процесс, вызывающий listen(), работает на многопользовательской машине, где пользователи не могут доверять друг другу, то злоумышленник может организовать запуск по сути произвольного кода в процессе пользователя-жертвы, просто подключившись к его сокету listen() и отправив конфигурацию, которая запускает любой код, который злоумышленник хочет получить в процессе жертвы. Это особенно легко сделать, если используется порт по умолчанию, но несложно, даже если используется другой порт. Чтобы избежать этого, используйте аргумент verify в listen(), чтобы предотвратить применение нераспознанных конфигураций.

Изменено в версии 3.4: Был добавлен аргумент verify.

Примечание

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

logging.config.stopListening()

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

Соображения безопасности

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

Схема словаря конфигурации

Описание конфигурации протоколирования требует перечисления различных объектов, которые необходимо создать, и связей между ними; например, вы можете создать обработчик с именем „console“, а затем сказать, что регистратор с именем „startup“ будет отправлять свои сообщения обработчику „console“. Эти объекты не ограничиваются теми, что предоставляются модулем logging, поскольку вы можете написать свой собственный форматтер или класс обработчика. В параметры этих классов также могут потребоваться внешние объекты, такие как sys.stderr. Синтаксис для описания этих объектов и связей определен в Объектные соединения ниже.

Детали схемы словаря

Словарь, переданный в dictConfig(), должен содержать следующие ключи:

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

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

  • formatters - соответствующее значение будет представлять собой dict, в котором каждый ключ - это идентификатор форматера, а каждое значение - это dict, описывающий, как настроить соответствующий экземпляр Formatter.

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

    • format

    • datefmt

    • style

    • validate (начиная с версии >=3.8)

    • defaults (начиная с версии >=3.12)

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

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

    В конфигурационной дикте ищется ключ name (по умолчанию пустая строка), который используется для создания экземпляра logging.Filter.

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

    В дикте конфигурации выполняется поиск следующих ключей:

    • class (обязательно). Это полное имя класса обработчика.

    • level (необязательно). Уровень обработчика.

    • formatter (необязательно). Идентификатор форматера для этого обработчика.

    • filters (необязательно). Список идентификаторов фильтров для этого обработчика.

      Изменено в версии 3.11: filters может принимать экземпляры фильтров в дополнение к идентификаторам.

    Все другие ключи передаются в конструктор обработчика в качестве аргументов ключевых слов. Например, в данном фрагменте:

    handlers:
      console:
        class : logging.StreamHandler
        formatter: brief
        level   : INFO
        filters: [allow_foo]
        stream  : ext://sys.stdout
      file:
        class : logging.handlers.RotatingFileHandler
        formatter: precise
        filename: logconfig.log
        maxBytes: 1024
        backupCount: 3
    

    обработчик с идентификатором console инстанцируется как logging.StreamHandler, используя sys.stdout в качестве базового потока. Обработчик с идентификатором file инстанцируется как logging.handlers.RotatingFileHandler с аргументами ключевого слова filename='logconfig.log', maxBytes=1024, backupCount=3.

  • loggers - соответствующее значение будет представлять собой dict, в котором каждый ключ - это имя логгера, а каждое значение - dict, описывающий, как настроить соответствующий экземпляр логгера.

    В дикте конфигурации выполняется поиск следующих ключей:

    • level (необязательно). Уровень регистратора.

    • propagate (необязательно). Настройка распространения для регистратора.

    • filters (необязательно). Список идентификаторов фильтров для этого регистратора.

      Изменено в версии 3.11: filters может принимать экземпляры фильтров в дополнение к идентификаторам.

    • handlers (необязательно). Список идентификаторов обработчиков для этого регистратора.

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

  • root - это будет конфигурация для корневого регистратора. Обработка конфигурации будет такой же, как и для любого регистратора, за исключением того, что параметр propagate не будет применяться.

  • incremental - интерпретировать ли конфигурацию как инкрементную по отношению к существующей конфигурации. По умолчанию это значение равно False, что означает, что указанная конфигурация заменяет существующую конфигурацию с той же семантикой, которая используется в существующем fileConfig(). API.

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

  • disable_existing_loggers - нужно ли отключать все существующие некорневые регистраторы. Этот параметр отражает одноименный параметр в fileConfig(). Если параметр отсутствует, то по умолчанию он принимает значение True. Это значение игнорируется, если параметр incremental имеет значение True.

Инкрементная конфигурация

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

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

Таким образом, если ключ incremental в конфигурационной дикте присутствует и равен True, система будет полностью игнорировать любые записи formatters и filters и обрабатывать только настройки level в записях handlers и настройки level и propagate в записях loggers и root.

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

Объектные соединения

Схема описывает набор объектов протоколирования - логгеров, обработчиков, форматоров, фильтров, - которые связаны друг с другом в графе объектов. Таким образом, схема должна представлять связи между объектами. Например, допустим, что после настройки определенный логгер привязан к определенному обработчику. Для целей данного обсуждения можно сказать, что регистратор представляет собой источник, а обработчик - пункт назначения связи между ними. Конечно, в конфигурируемых объектах это представлено регистратором, хранящим ссылку на обработчик. В дикте конфигурации это делается путем присвоения каждому объекту назначения идентификатора, который однозначно его идентифицирует, а затем использования этого идентификатора в конфигурации объекта-источника для указания на то, что между объектом-источником и объектом назначения с этим идентификатором существует соединение.

Например, рассмотрим следующий фрагмент YAML:

formatters:
  brief:
    # configuration for formatter with id 'brief' goes here
  precise:
    # configuration for formatter with id 'precise' goes here
handlers:
  h1: #This is an id
   # configuration of handler with id 'h1' goes here
   formatter: brief
  h2: #This is another id
   # configuration of handler with id 'h2' goes here
   formatter: precise
loggers:
  foo.bar.baz:
    # other configuration for logger 'foo.bar.baz'
    handlers: [h1, h2]

(Примечание: здесь используется YAML, потому что он немного более читабелен, чем эквивалентная исходная форма словаря на Python).

Идентификаторы для регистраторов - это имена регистраторов, которые будут использоваться программно для получения ссылки на эти регистраторы, например foo.bar.baz. Идентификаторы для форматоров и фильтров могут быть любыми строковыми значениями (например, brief, precise выше), и они являются переходными, так как имеют значение только для обработки словаря конфигурации и используются для определения связей между объектами, и не сохраняются нигде после завершения вызова конфигурации.

Приведенный выше фрагмент указывает, что к логгеру с именем foo.bar.baz должны быть прикреплены два обработчика, которые описываются идентификаторами обработчиков h1 и h2. Форматировщик для h1 описывается идентификатором brief, а форматор для h2 - идентификатором precise.

Объекты, определяемые пользователем

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

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

formatters:
  brief:
    format: '%(message)s'
  default:
    format: '%(asctime)s %(levelname)-8s %(name)-15s %(message)s'
    datefmt: '%Y-%m-%d %H:%M:%S'
  custom:
      (): my.package.customFormatterFactory
      bar: baz
      spam: 99.9
      answer: 42

В приведенном выше фрагменте YAML определены три форматера. Первый, с идентификатором brief, представляет собой стандартный экземпляр logging.Formatter с указанной строкой формата. Второй, с идентификатором default, имеет более длинный формат, а также явно определяет формат времени, и в результате будет создан logging.Formatter, инициализированный этими двумя строками формата. Показанные в исходном тексте Python, форматеры brief и default имеют конфигурационные подсловари:

{
  'format' : '%(message)s'
}

и:

{
  'format' : '%(asctime)s %(levelname)-8s %(name)-15s %(message)s',
  'datefmt' : '%Y-%m-%d %H:%M:%S'
}

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

{
  '()' : 'my.package.customFormatterFactory',
  'bar' : 'baz',
  'spam' : 99.9,
  'answer' : 42
}

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

my.package.customFormatterFactory(bar='baz', spam=99.9, answer=42)

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

Значения ключей bar, spam и answer в приведенном выше примере не должны быть словарями конфигурации или ссылками, такими как cfg://foo или ext://bar, поскольку они не будут обработаны механизмом конфигурации, а будут переданы в вызываемый модуль как есть.

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

Изменено в версии 3.11: Член filters в handlers и loggers может принимать не только идентификаторы, но и экземпляры фильтров.

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

{
  '()' : 'my.package.customFormatterFactory',
  'bar' : 'baz',
  'spam' : 99.9,
  'answer' : 42,
  '.' {
    'foo': 'bar',
    'baz': 'bozz'
  }
}

возвращаемый форматтер будет иметь атрибут foo, установленный в 'bar', и атрибут baz, установленный в 'bozz'.

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

Значения атрибутов foo и baz в приведенном выше примере не должны быть словарями конфигурации или ссылками, такими как cfg://foo или ext://bar, потому что они не будут обрабатываться механизмом конфигурации, а будут установлены как значения атрибутов as-is.

Порядок настройки обработчика

Обработчики конфигурируются в алфавитном порядке их ключей, и сконфигурированный обработчик заменяет словарь конфигурации в (рабочей копии) словаря handlers в схеме. Если вы используете такую конструкцию, как cfg://handlers.foo, то первоначально handlers['foo'] указывает на словарь конфигурации для обработчика с именем foo, а затем (после того как этот обработчик будет настроен) - на настроенный экземпляр обработчика. Таким образом, cfg://handlers.foo может разрешаться либо в словарь, либо в экземпляр обработчика. Вообще, разумно называть обработчики таким образом, чтобы зависимые обработчики конфигурировались _после_ всех обработчиков, от которых они зависят; это позволяет использовать что-то вроде cfg://handlers.foo при конфигурировании обработчика, который зависит от обработчика foo. Если бы этот зависимый обработчик был назван bar, возникли бы проблемы, поскольку конфигурирование bar было бы предпринято до конфигурирования foo, а foo еще не был бы сконфигурирован. Однако если бы зависимый обработчик был назван foobar, он был бы сконфигурирован после foo, в результате чего cfg://handlers.foo разрешился бы в сконфигурированный обработчик foo, а не в его словарь конфигурации.

Доступ к внешним объектам

Бывают случаи, когда конфигурация должна ссылаться на внешние по отношению к ней объекты, например sys.stderr. Если диктант конфигурации строится с помощью кода на Python, это несложно, но возникает проблема, когда конфигурация предоставляется через текстовый файл (например, JSON, YAML). В текстовом файле нет стандартного способа отличить sys.stderr от буквенной строки 'sys.stderr'. Чтобы облегчить это различие, система конфигурации ищет определенные специальные префиксы в строковых значениях и обрабатывает их особым образом. Например, если в качестве значения в конфигурации указана литеральная строка 'ext://sys.stderr', то префикс ext:// будет удален, а оставшаяся часть значения будет обработана с помощью обычных механизмов импорта.

Работа с такими префиксами аналогична работе с протоколами: существует общий механизм поиска префиксов, соответствующих регулярному выражению ^(?P<prefix>[a-z]+)://(?P<suffix>.*)$, и если prefix распознан, то suffix обрабатывается в зависимости от префикса, и результат обработки заменяет строковое значение. Если префикс не распознан, то строковое значение будет оставлено как есть.

Доступ к внутренним объектам

Помимо внешних объектов, иногда возникает необходимость ссылаться на объекты в конфигурации. Это будет сделано неявно системой конфигурации для вещей, о которых она знает. Например, строковое значение 'DEBUG' для level в регистраторе или обработчике будет автоматически преобразовано в значение logging.DEBUG, а записи handlers, filters и formatter будут принимать идентификатор объекта и преобразовываться в соответствующий целевой объект.

Однако для пользовательских объектов, которые не известны модулю logging, необходим более общий механизм. Например, рассмотрим logging.handlers.MemoryHandler, который принимает аргумент target, являющийся другим обработчиком, на который можно делегировать полномочия. Поскольку система уже знает об этом классе, то в конфигурации заданный target должен быть просто идентификатором объекта соответствующего целевого обработчика, и система разрешит обработчик по этому идентификатору. Однако если пользователь определит my.package.MyHandler, у которого есть обработчик alternate, система конфигурации не будет знать, что alternate ссылается на обработчик. Чтобы учесть это, система общего разрешения позволяет пользователю указать:

handlers:
  file:
    # configuration of file handler goes here

  custom:
    (): my.package.MyHandler
    alternate: cfg://handlers.file

Буквальная строка 'cfg://handlers.file' будет разрешена аналогично строкам с префиксом ext://, но искать ее нужно в самой конфигурации, а не в пространстве имен импорта. Механизм позволяет получить доступ по точке или по индексу, аналогично тому, как это сделано в str.format. Таким образом, если взять следующий фрагмент:

handlers:
  email:
    class: logging.handlers.SMTPHandler
    mailhost: localhost
    fromaddr: my_app@domain.tld
    toaddrs:
      - support_team@domain.tld
      - dev_team@domain.tld
    subject: Houston, we have a problem.

в конфигурации строка 'cfg://handlers' будет разрешаться в дикту с ключом handlers, строка 'cfg://handlers.email будет разрешаться в дикту с ключом email в дикту handlers и так далее. Строка 'cfg://handlers.email.toaddrs[1] разрешится в 'dev_team@domain.tld', а строка 'cfg://handlers.email.toaddrs[0]' - в значение 'support_team@domain.tld'. Для доступа к значению subject можно использовать либо 'cfg://handlers.email.subject', либо, что эквивалентно, 'cfg://handlers.email[subject]'. Последняя форма используется только в том случае, если ключ содержит пробелы или неалфавитно-цифровые символы. Обратите внимание, что символы [ и ] в ключах недопустимы. Если значение индекса состоит только из десятичных цифр, доступ будет осуществляться по соответствующему целочисленному значению, при необходимости возвращаясь к строковому значению.

Если задана строка cfg://handlers.myhandler.mykey.123, она будет преобразована в config_dict['handlers']['myhandler']['mykey']['123']. Если строка указана как cfg://handlers.myhandler.mykey[123], система попытается получить значение из config_dict['handlers']['myhandler']['mykey'][123] и вернется к config_dict['handlers']['myhandler']['mykey']['123'], если это не удастся.

Разрешение импорта и пользовательские импортеры

Разрешение импорта по умолчанию использует встроенную функцию __import__() для выполнения импорта. Возможно, вы захотите заменить ее собственным механизмом импорта: в этом случае вы можете заменить атрибут importer в классе DictConfigurator или его суперклассе, классе BaseConfigurator. Однако следует быть осторожным из-за способа доступа к функциям из классов через дескрипторы. Если вы используете вызываемую функцию Python для импорта и хотите определить ее на уровне класса, а не экземпляра, вам нужно обернуть ее с помощью staticmethod(). Например:

from importlib import import_module
from logging.config import BaseConfigurator

BaseConfigurator.importer = staticmethod(import_module)

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

Настройка QueueHandler и QueueListener

Если вы хотите сконфигурировать QueueHandler, то, учитывая, что он обычно используется вместе с QueueListener, вы можете сконфигурировать оба вместе. После настройки экземпляр QueueListener будет доступен в качестве атрибута listener созданного обработчика, а тот, в свою очередь, будет доступен вам, используя getHandlerByName() и передавая имя, которое вы использовали для QueueHandler в своей конфигурации. Схема словаря для настройки пары показана в примере YAML-фрагмента ниже.

handlers:
  qhand:
    class: logging.handlers.QueueHandler
    queue: my.module.queue_factory
    listener: my.package.CustomListener
    handlers:
      - hand_name_1
      - hand_name_2
      ...

Клавиши queue и listener являются необязательными.

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

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

  • Строка, которая разрешается в вызываемую функцию, которая при вызове без аргументов возвращает экземпляр queue.Queue, который необходимо использовать. Этот вызываемый элемент может быть подклассом queue.Queue или функцией, возвращающей подходящий экземпляр очереди, например my.module.queue_factory().

  • Дикта с ключом '()', которая строится обычным способом, как описано в Объекты, определяемые пользователем. Результатом этого построения должен быть экземпляр queue.Queue.

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

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

  • Подкласс logging.handlers.QueueListener. Это, конечно, возможно только в том случае, если вы создаете или изменяете словарь конфигурации в коде.

  • Строка, которая разрешается в класс, являющийся подклассом QueueListener, например 'my.package.CustomListener'.

  • Дикта с ключом '()', который строится обычным способом, как описано в Объекты, определяемые пользователем. Результатом этой конструкции должен быть вызываемый объект с той же сигнатурой, что и инициализатор QueueListener.

Если клавиша listener отсутствует, используется клавиша logging.handlers.QueueListener.

Значения под ключом handlers - это имена других обработчиков в конфигурации (не показаны в приведенном выше фрагменте), которые будут переданы слушателю очереди.

Любые пользовательские классы обработчиков и слушателей очередей должны быть определены с теми же сигнатурами инициализации, что и QueueHandler и QueueListener.

Added in version 3.12.

Формат файла конфигурации

Формат конфигурационного файла, понимаемый fileConfig(), основан на функциональности configparser. Файл должен содержать секции [loggers], [handlers] и [formatters], которые идентифицируют по имени сущности каждого типа, определенные в файле. Для каждой такой сущности есть отдельная секция, которая определяет, как эта сущность конфигурируется. Так, для регистратора с именем log01 в секции [loggers] соответствующие сведения о конфигурации содержатся в секции [logger_log01]. Аналогично, обработчик с именем hand01 в секции [handlers] будет иметь свою конфигурацию в секции [handler_hand01], а форматтер с именем form01 в секции [formatters] будет иметь свою конфигурацию, указанную в секции [formatter_form01]. Конфигурация корневого регистратора должна быть указана в секции [logger_root].

Примечание

API fileConfig() API является более старым, чем dictConfig(). API и не обеспечивает функциональности, покрывающей некоторые аспекты протоколирования. Например, с помощью fileConfig() нельзя настроить объекты Filter, которые обеспечивают фильтрацию сообщений за пределами простых целочисленных уровней. Если вам необходимо иметь экземпляры Filter в вашей конфигурации протоколирования, вам придется использовать dictConfig(). Обратите внимание, что в будущем функциональность конфигурации будет расширена за счет dictConfig(), поэтому стоит рассмотреть возможность перехода на этот новый API, когда это будет удобно.

Примеры этих секций в файле приведены ниже.

[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

Запись level может быть одной из DEBUG, INFO, WARNING, ERROR, CRITICAL или NOTSET. Только для корневого регистратора значение NOTSET означает, что в журнал будут записываться все сообщения. Значения уровня - это evaluated в контексте пространства имен пакета logging.

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

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

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

Записи level и handlers интерпретируются как для корневого регистратора, за исключением того, что если уровень не корневого регистратора указан как NOTSET, система обращается к регистраторам выше по иерархии, чтобы определить эффективный уровень регистратора. Запись propagate имеет значение 1, чтобы указать, что сообщения должны распространяться на обработчики выше по иерархии от этого регистратора, или 0, чтобы указать, что сообщения не распространяются на обработчики выше по иерархии. Элемент qualname - это иерархическое имя канала регистратора, то есть имя, используемое приложением для получения регистратора.

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

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

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

Элемент formatter указывает на ключевое имя форматера для этого обработчика. Если он пуст, используется формат по умолчанию (logging._defaultFormatter). Если указано имя, оно должно присутствовать в секции [formatters] и иметь соответствующий раздел в конфигурационном файле.

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

Необязательный элемент kwargs, когда evaluated в контексте пространства имен пакета logging, является аргументом ключевого слова dict в конструкторе класса обработчика. Если он не указан, то по умолчанию принимает значение {}.

[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')
kwargs={'timeout': 10.0}

[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')
kwargs={'secure': True}

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

[formatter_form01]
format=F1 %(asctime)s %(levelname)s %(message)s %(customfield)s
datefmt=
style=%
validate=True
defaults={'customfield': 'defaultvalue'}
class=logging.Formatter

Аргументы для конфигурации форматера совпадают с ключами в схеме словаря formatters section.

Запись defaults, если она evaluated в контексте пространства имен пакета logging, представляет собой словарь значений по умолчанию для полей пользовательского форматирования. Если он не указан, по умолчанию используется значение None.

Примечание

Из-за использования eval(), как описано выше, существуют потенциальные риски безопасности, возникающие при использовании listen() для отправки и получения конфигураций через сокеты. Риски ограничены случаями, когда несколько пользователей, не имеющих взаимного доверия, выполняют код на одной машине; более подробную информацию см. в документации по listen().

См.также

Модуль logging

Ссылка на API для модуля протоколирования.

Модуль logging.handlers

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