Руководство по Python модулю logging

Логирование

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

Цель логирования

Логирование ведется в следующих целях:

  • Сбор информации
  • Исправление проблем
  • Генерация статистики
  • Аудит
  • Профилирование

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

Какие события регистрировать

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

Какие события не регистрировать

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

Лучшие практики логирования

Ниже приведены некоторые рекомендации по ведению журнала.

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

Модуль logging

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

Компоненты модуля logging

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

Иерархия logging в Python

Регистраторы Python образуют иерархию. Регистратор с именем main является родителем main.new.

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

Уровни логирования Python

Уровни используются для определения серьезности события. Существует шесть уровней ведения журнала:

  • CRITICAL
  • ERROR
  • WARNING
  • INFO
  • DEBUG
  • NOTSET

Если уровень ведения журнала установлен на WARNING, все сообщения WARNING, ERROR и CRITICAL записываются в файл журнала или консоль. Если установлено значение ERROR, регистрируются только сообщения ERROR и CRITICAL.

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

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

Корневой логгер

Все регистраторы являются потомками корневого регистратора. Каждый регистратор передает сообщения журнала своему родителю. Новые регистраторы создаются с помощью метода getLogger(name). Вызов функции без имени (getLogger()) возвращает корневой логгер.

Корневой регистратор всегда имеет явный набор уровней, который по умолчанию WARNING.

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

Простой пример логирования Python

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

simple.py

#!/usr/bin/env python

import logging

logging.debug('Это отладочное сообщение')
logging.info('Это информационное сообщение')
logging.warning('Это предупреждающее сообщение')
logging.error('Это сообщение об ошибке')
logging.critical('Это критическое сообщение')

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

$ simple.py
WARNING:root:Это предупреждающее сообщение
ERROR:root:Это сообщение об ошибке
CRITICAL:root:Это критическое сообщение

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

Установка уровня логирования

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

set_level.py

#!/usr/bin/env python

import logging

logger = logging.getLogger('dev')
logger.setLevel(logging.DEBUG)

logging.debug('Это отладочное сообщение')
logging.info('Это информационное сообщение')
logging.warning('Это предупреждающее сообщение')
logging.error('Это сообщение об ошибке')
logging.critical('Это критическое сообщение')

В этом примере мы изменили уровень ведения журнала на DEBUG.

logger = logging.getLogger('dev')

getLogger() возвращает регистратор с указанным именем. Если имя None, он возвращает корневой логгер. Имя может быть разделенной точкой точкой, определяющей иерархию журналирования; например, 'a', 'a.b' или 'a.b.c'. Обратите внимание, что существует неявное корневое имя, которое не отображается.

$ set_level.py
Это предупреждающее сообщение
Это сообщение об ошибке
Это критическое сообщение

Теперь все сообщения были записаны.

Эффективный уровень ведения журнала в Python

Эффективный уровень ведения журнала - это уровень, установленный в явном виде или определяемый родителями журнала.

effective_level.py

#!/usr/bin/env python

import logging

main_logger = logging.getLogger('main')
main_logger.setLevel(5)

dev_logger = logging.getLogger('main.dev')

print(main_logger.getEffectiveLevel())
print(dev_logger.getEffectiveLevel())

В этом примере мы рассмотрим эффективный уровень логирования двух логгеров.

dev_logger = logging.getLogger('main.dev')

Уровень dev_logger не установлен; затем используется уровень его родителя.

$ effective_level.py
5
5

Это вывод.

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

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

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

handlers.py

#!/usr/bin/env python

import logging

logger = logging.getLogger('dev')
logger.setLevel(logging.INFO)

fileHandler = logging.FileHandler('test.log')
fileHandler.setLevel(logging.INFO)

consoleHandler = logging.StreamHandler()
consoleHandler.setLevel(logging.INFO)

logger.addHandler(fileHandler)
logger.addHandler(consoleHandler)

logger.info('информационное сообщение')

В примере создаются два обработчика для регистратора: обработчик файлов и обработчик консоли.

fileHandler = logging.FileHandler('test.log')

FileHandler отправляет записи журнала в файл test.log.

consoleHandler = logging.StreamHandler()

StreamHandler отправляет записи журнала в поток. Если поток не указан, используется sys.stderr.

logger.addHandler(fileHandler)

Обработчик добавляется в логгер с помощью addHandler().

Средства форматирования журнала Python

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

formatter.py

#!/usr/bin/env python

import logging

logger = logging.getLogger('dev')
logger.setLevel(logging.INFO)

consoleHandler = logging.StreamHandler()
consoleHandler.setLevel(logging.INFO)

logger.addHandler(consoleHandler)

formatter = logging.Formatter('%(asctime)s  %(name)s  %(levelname)s: %(message)s')
consoleHandler.setFormatter(formatter)

logger.info('информационное сообщение')

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

formatter = logging.Formatter('%(asctime)s  %(name)s  %(levelname)s: %(message)s')

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

consoleHandler.setFormatter(formatter)

Форматтер устанавливается на обработчик с помощью setFormatter().

$ formatter.py
2019-03-28 14:53:27,446  dev  INFO: информационное сообщение

Сообщение с определенным форматом отображается в консоли.

Python logging basicConfig

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

basic_config.py

#!/usr/bin/env python

import logging

logging.basicConfig(filename='test.log', format='%(filename)s: %(message)s',
                    level=logging.DEBUG)

logging.debug('Это отладочное сообщение')
logging.info('Это информационное сообщение')
logging.warning('Это предупреждающее сообщение')
logging.error('Это сообщение об ошибке')
logging.critical('Это критическое сообщение')

В этом примере корневая программа регистрации настраивается с помощью basicConfig.

logging.basicConfig(filename='test.log', format='%(filename)s: %(message)s',
    level=logging.DEBUG)

С помощью имени файла filename мы устанавливаем файл, в который мы записываем сообщения журнала. Формат format определяет, что записывается в файл; у нас есть имя файла и сообщение. С уровнем level мы устанавливаем порог регистрации.

$ basic_config.py
$ cat test.log
basic_config.py: Это отладочное сообщение
basic_config.py: Это информационное сообщение
basic_config.py: Это предупреждающее сообщение
basic_config.py: Это сообщение об ошибке
basic_config.py: Это критическое сообщение

После запуска программы у нас есть пять сообщений, записанных в файл test.log.

Python logging fileConfig

fileConfig() считывает конфигурацию ведения журнала из файла формата configparser.

log.conf

[loggers]
keys=root,dev

[handlers]
keys=consoleHandler

[formatters]
keys=extend,simple

[logger_root]
level=INFO
handlers=consoleHandler

[logger_dev]
level=INFO
handlers=consoleHandler
qualname=dev
propagate=0

[handler_consoleHandler]
class=StreamHandler
level=INFO
formatter=extend
args=(sys.stdout,)

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

[formatter_simple]
format=%(asctime)s - %(message)s

log.conf определяет регистратор, обработчик и форматер.

file_config.py

#!/usr/bin/env python

import logging
import logging.config

logging.config.fileConfig(fname='log.conf')

logger = logging.getLogger('dev')
logger.info('Это информационное сообщение')

В этом примере файл конфигурации журнала считывается из файла log.conf.

$ file_config.py
2019-03-28 15:26:31,137 - dev - INFO - Это информационное сообщение

Переменные в Python logging

Динамические данные записываются с использованием форматирования строки.

log_variable.py

#!/usr/bin/env python

import logging

root = logging.getLogger()
root.setLevel(logging.INFO)

log_format = '%(asctime)s %(filename)s: %(message)s'
logging.basicConfig(filename="test.log", format=log_format)

# случился инцидент

error_message = 'аутентификация не удалась'

root.error(f'error: {error_message}')

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

2019-03-21 14:17:23,196 log_variable.py: error: аутентификация не удалась

Форматирование даты/времени в Python logging

Дата и время включаются в сообщение журнала вместе с записью журнала. С помощью опции конфигурации datefmt мы можем отформатировать строку datetime.

date_time.py

#!/usr/bin/env python

import logging

logger = logging.getLogger()
logger.setLevel(logging.DEBUG)

log_format = '%(asctime)s %(filename)s: %(message)s'
logging.basicConfig(filename="test.log", format=log_format,
                    datefmt='%Y-%m-%d %H:%M:%S')

logger.info("информационное сообщение")

В примере форматируется дата и время сообщения журнала.

log_format = '%(asctime)s %(filename)s: %(message)s'

Мы включаем строку datetime в журнал вместе с asctime.

logging.basicConfig(filename="test.log", format=log_format,
                    datefmt='%Y-%m-%d %H:%M:%S')

Опция datefmt форматирует строку даты и времени.

2019-03-21 14:17:23,196 log_variable.py: error: аутентификация не удалась
2019-03-21 14:23:33 date_time.py: информационное сообщение

Обратите внимание на разницу в формате строки даты и времени.

Трассировка стека логирования Python

Трассировка стека - это стек вызовов функций, которые выполнялись до точки возникновения исключений. Трассировка стека включается опцией exc_info.

stack_trace.py

#!/usr/bin/env python

import logging

log_format = '%(asctime)s %(filename)s: %(message)s'
logging.basicConfig(filename="test.log", format=log_format)

vals = [1, 2]

try:
    print(vals[4])

except Exception as e:
    logging.error("произошло исключение", exc_info=True)

В этом примере мы регистрируем исключение, которое выдается при попытке доступа к несуществующему списковому индексу.

logging.error("произошло исключение", exc_info=True)

Трассировка стека включается в журнал, устанавливая для exc_info значение True.

2019-03-21 14:56:21,313 stack_trace.py: произошло исключение
Traceback (most recent call last):
  File "C:\Users\Jano\Documents\pyprogs\pylog\stack_trace.py", line 11, in <module>
    print(vals[4])
IndexError: list index out of range

Трассировка стека включена в журнал.

Python logging getLogger

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

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

get_logger.py

#!/usr/bin/env python

import logging
import sys

main = logging.getLogger('main')
main.setLevel(logging.DEBUG)

handler = logging.FileHandler('my.log')

format = logging.Formatter('%(asctime)s  %(name)s
    %(levelname)s: %(message)s')
handler.setFormatter(format)

main.addHandler(handler)

main.info('информационное сообщение')
main.critical('критическое сообщение')
main.debug('отладочное сообщение')
main.warning('предупреждение')
main.error('сообщение об ошибке')

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

main = logging.getLogger('main')
main.setLevel(logging.DEBUG)

Создан регистратор с именем main; мы устанавливаем уровень ведения журнала на DEBUG.

handler = logging.FileHandler('my.log')

Обработчик файла создан. Сообщения будут записаны в файл my.log.

format = logging.Formatter('%(asctime)s  %(name)s
    %(levelname)s: %(message)s')
handler.setFormatter(format)

Форматер создан. Он включает в себя время, имя регистратора, уровень регистрации и сообщение для входа. Форматтер устанавливается на обработчик с помощью setFormatter().

main.addHandler(handler)

Обработчик добавляется в логгер с помощью addHandler().

$ cat my.log
2019-03-21 14:15:45,439  main  INFO: информационное сообщение
2019-03-21 14:15:45,439  main  CRITICAL: критическое сообщение
2019-03-21 14:15:45,439  main  DEBUG: отладочное сообщение
2019-03-21 14:15:45,439  main  WARNING: предупреждение
2019-03-21 14:15:45,439  main  ERROR: сообщение об ошибке

Конфигурация YAML для логирования в Python

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

$ pip install pyyaml

Нам нужно установить модуль pyyaml.

config.yaml

version: 1

formatters:
  simple:
    format: "%(asctime)s %(name)s: %(message)s"
  extended:
    format: "%(asctime)s %(name)s %(levelname)s: %(message)s"

handlers:
  console:
    class: logging.StreamHandler
    level: INFO
    formatter: simple

  file_handler:
    class: logging.FileHandler
    level: INFO
    filename: test.log
    formatter: extended
    propagate: false

loggers:
  dev:
    handlers: [console, file_handler]
  test:
    handlers: [file_handler]
root:
  handlers: [file_handler]

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

log_yaml.py

#!/usr/bin/env python

import logging
import logging.config
import yaml

with open('config.yaml', 'r') as f:

    log_cfg = yaml.safe_load(f.read())

logging.config.dictConfig(log_cfg)

logger = logging.getLogger('dev')
logger.setLevel(logging.INFO)

logger.info('Это информационное сообщение')
logger.error('Это сообщение об ошибке')

В этом примере мы читаем файл конфигурации и используем dev logger.

$ log_yaml.py
2019-03-28 11:36:54,854 dev: Это информационное сообщение
2019-03-28 11:36:54,855 dev: Это сообщение об ошибке

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

...
2019-03-28 11:36:54,854 dev INFO: Это информационное сообщение
2019-03-28 11:36:54,855 dev ERROR: Это сообщение об ошибке

Внутри файла test.log находятся сообщения журнала. Они производятся расширенным форматером с дополнительной информацией.

http://zetcode.com/python/logging/

Вернуться на верх