Ведение журнала

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

Быстрый обзор

Django использует и расширяет встроенный модуль Python logging для ведения системного журнала. Этот модуль подробно рассматривается в собственной документации Python; в данном разделе представлен краткий обзор.

Состав игроков

Конфигурация ведения журнала Python состоит из четырех частей:

Логеры

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

Логер настроен на уровень журнала. Этот уровень журнала описывает серьезность сообщений, обрабатываемых логером. Python определяет следующие уровни журнала:

  • DEBUG: системная информация низкого уровня для отладки
  • INFO: Общая информация о системе
  • WARNING: Информация, описывающая возникшую незначительную проблему.
  • ERROR: Информация, описывающая возникшую серьезную проблему.
  • CRITICAL: Информация, описывающая возникшую критическую проблему.

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

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

Как только регистратор определил, что сообщение должно быть обработано, оно передается в Handler.

Обработчики

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

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

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

Фильтры

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

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

Фильтры также можно использовать для изменения записи журнала перед ее выдачей. Например, вы можете написать фильтр, который понижает уровень ERROR записей журнала до WARNING записей, если выполняется определенный набор критериев.

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

Форматировщики

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

Последствия для безопасности

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

  • какая информация собирается
  • где он будет впоследствии храниться
  • как она будет передана
  • кто может иметь к нему доступ.

Чтобы помочь контролировать сбор конфиденциальной информации, вы можете явно указать определенную конфиденциальную информацию, которая будет отфильтрована из отчетов об ошибках - читайте подробнее о том, как это сделать filter error reports.

AdminEmailHandler

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

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

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

Библиотека протоколирования Python предоставляет несколько способов настройки протоколирования, начиная от программного интерфейса и заканчивая конфигурационными файлами. По умолчанию Django использует dictConfig format.

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

По умолчанию установка LOGGING объединяется с Django’s default logging configuration по следующей схеме.

Если ключ disable_existing_loggers в директ-конфиге LOGGING установлен в True (что является dictConfig по умолчанию, если ключ отсутствует), то все регистраторы из конфигурации по умолчанию будут отключены. Отключенные регистраторы - это не то же самое, что удаленные; регистратор будет по-прежнему существовать, но будет молча отбрасывать все, что записывается в него, даже не передавая записи в родительский регистратор. Таким образом, вы должны быть очень осторожны, используя 'disable_existing_loggers': True; вероятно, это не то, что вам нужно. Вместо этого вы можете установить disable_existing_loggers в False и переопределить некоторые или все логгеры по умолчанию; или вы можете установить LOGGING_CONFIG в None и handle logging config yourself.

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

Примеры

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

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

settings.py
import os

LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
        },
    },
    "root": {
        "handlers": ["console"],
        "level": "WARNING",
    },
}

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

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

settings.py
import os

LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
        },
    },
    "root": {
        "handlers": ["console"],
        "level": "WARNING",
    },
    "loggers": {
        "django": {
            "handlers": ["console"],
            "level": os.getenv("DJANGO_LOG_LEVEL", "INFO"),
            "propagate": False,
        },
    },
}

По умолчанию этот конфиг отправляет сообщения от логгера django уровня INFO или выше на консоль. Это тот же уровень, что и стандартная конфигурация логирования Django, за исключением того, что стандартная конфигурация отображает записи журнала только при уровне DEBUG=True. Django не записывает много сообщений такого уровня INFO. Однако с этой конфигурацией вы также можете установить переменную окружения DJANGO_LOG_LEVEL=DEBUG, чтобы увидеть все отладочные журналы Django, которые очень подробны, поскольку включают все запросы к базе данных.

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

settings.py
LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "handlers": {
        "file": {
            "level": "DEBUG",
            "class": "logging.FileHandler",
            "filename": "/path/to/django/debug.log",
        },
    },
    "loggers": {
        "django": {
            "handlers": ["file"],
            "level": "DEBUG",
            "propagate": True,
        },
    },
}

Если вы используете этот пример, не забудьте изменить путь 'filename' на место, доступное для записи пользователю, который запускает приложение Django.

Наконец, вот пример довольно сложной настройки протоколирования:

settings.py
LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "verbose": {
            "format": "{levelname} {asctime} {module} {process:d} {thread:d} {message}",
            "style": "{",
        },
        "simple": {
            "format": "{levelname} {message}",
            "style": "{",
        },
    },
    "filters": {
        "special": {
            "()": "project.logging.SpecialFilter",
            "foo": "bar",
        },
        "require_debug_true": {
            "()": "django.utils.log.RequireDebugTrue",
        },
    },
    "handlers": {
        "console": {
            "level": "INFO",
            "filters": ["require_debug_true"],
            "class": "logging.StreamHandler",
            "formatter": "simple",
        },
        "mail_admins": {
            "level": "ERROR",
            "class": "django.utils.log.AdminEmailHandler",
            "filters": ["special"],
        },
    },
    "loggers": {
        "django": {
            "handlers": ["console"],
            "propagate": True,
        },
        "django.request": {
            "handlers": ["mail_admins"],
            "level": "ERROR",
            "propagate": False,
        },
        "myproject.custom": {
            "handlers": ["console", "mail_admins"],
            "level": "INFO",
            "filters": ["special"],
        },
    },
}

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

  • Идентифицирует конфигурацию как имеющую формат „dictConfig версии 1“. В настоящее время это единственная версия формата dictConfig.

  • Определяет два форматера:

    • simple, который выводит имя уровня журнала (например, DEBUG) и сообщение журнала.

      Строка format - это обычная строка форматирования Python, описывающая детали, которые должны быть выведены в каждой строке журнала. Полный список деталей, которые могут быть выведены, можно найти в Formatter Objects.

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

  • Определяет два фильтра:

    • project.logging.SpecialFilter, используя псевдоним special. Если для данного фильтра требуются дополнительные аргументы, они могут быть предоставлены как дополнительные ключи в словаре конфигурации фильтра. В этом случае аргументу foo будет присвоено значение bar при инстанцировании SpecialFilter.
    • django.utils.log.RequireDebugTrue, который передает записи, когда DEBUG становится True.
  • Определяет два обработчика:

    • console, обработчик StreamHandler, который печатает любое сообщение INFO (или выше) в sys.stderr. Этот обработчик использует формат вывода simple.
    • mail_admins, обработчик AdminEmailHandler, который отправляет любое сообщение ERROR (или выше) на сайт ADMINS. Этот обработчик использует фильтр special.
  • Настраивает три регистратора:

    • django, который передает все сообщения обработчику console.
    • django.request, который передает все сообщения ERROR обработчику mail_admins. Кроме того, этот логгер помечен как не распространяющий сообщения. Это означает, что сообщения журнала, записанные в django.request, не будут обработаны регистратором django.
    • myproject.custom, который передает все сообщения уровня INFO или выше, которые также проходят фильтр special, двум обработчикам - console и mail_admins. Это означает, что все сообщения уровня INFO (или выше) будут выведены на консоль; ERROR и CRITICAL сообщения также будут выведены по электронной почте.

Пользовательская конфигурация протоколирования

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

Параметр LOGGING_CONFIG определяет вызываемую функцию, которая будет использоваться для настройки логгеров Django. По умолчанию он указывает на функцию Python logging.config.dictConfig(). Однако, если вы хотите использовать другой процесс конфигурации, вы можете использовать любую другую вызываемую функцию, принимающую один аргумент. Содержимое LOGGING будет предоставлено в качестве значения этого аргумента при настройке логирования.

Отключение конфигурации ведения журнала

Если вы не хотите настраивать ведение журнала вообще (или хотите вручную настроить ведение журнала, используя свой собственный подход), вы можете установить LOGGING_CONFIG на None. Это отключит процесс конфигурирования для Django’s default logging.

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

Вот пример, который отключает конфигурацию логирования Django, а затем вручную настраивает логирование:

settings.py
LOGGING_CONFIG = None

import logging.config

logging.config.dictConfig(...)

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

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