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

Краткое руководство по ведению журнала

Django использует встроенный модуль Python logging для ведения системного журнала. Использование этого модуля подробно обсуждается в собственной документации Python. Однако, если вы никогда не использовали фреймворк ведения журналов Python (или даже если использовали), вот краткое руководство.

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

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

Логеры

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

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

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

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

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

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

Обработчики

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

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

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

Фильтры

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

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

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

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

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

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

Использование протоколирования

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

# import the logging library
import logging

# Get an instance of a logger
logger = logging.getLogger(__name__)

def my_view(request, arg1, arg):
    ...
    if bad_mojo:
        # Log an error message
        logger.error('Something went wrong!')

И это все! Каждый раз, когда активируется условие bad_mojo, будет записываться запись в журнал ошибок.

Именование регистраторов

Вызов logging.getLogger() получает (при необходимости создает) экземпляр регистратора. Экземпляр регистратора идентифицируется именем. Это имя используется для идентификации регистратора в целях конфигурации.

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

# Get an instance of a specific named logger
logger = logging.getLogger('project.interesting.stuff')

Пунктирные пути имен регистраторов определяют иерархию. Логгер project.interesting считается родителем логгера project.interesting.stuff; логгер project является родителем логгера project.interesting.

Почему иерархия важна? Потому что логгеры могут быть настроены на распространение своих вызовов логирования на их родителей. Таким образом, вы можете определить один набор обработчиков в корне дерева логгеров и перехватить все вызовы логирования в поддереве логгеров. Обработчик протоколирования, определенный в пространстве имен project, будет перехватывать все сообщения протоколирования, выданные на регистраторах project.interesting и project.interesting.stuff.

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

Ведение журнала вызовов

Экземпляр регистратора содержит метод входа для каждого из уровней журнала по умолчанию:

  • logger.debug()
  • logger.info()
  • logger.warning()
  • logger.error()
  • logger.critical()

Существуют два других способа ведения журнала:

  • logger.log(): Вручную выдает сообщение журнала с определенным уровнем регистрации.
  • logger.exception(): Создает сообщение регистрации уровня ERROR, обернув текущий кадр стека исключений.

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

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

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

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

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.

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

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

import os

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

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

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

settings.py
LOGGING_CONFIG = None

import logging.config
logging.config.dictConfig(...)

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

Расширения для ведения журнала в Django

Django предоставляет ряд утилит для обработки уникальных требований протоколирования в среде веб-сервера.

Логеры

Django предоставляет несколько встроенных регистраторов.

django

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

django.request

Сообщения журнала, связанные с обработкой запросов. Ответы 5XX выводятся как сообщения ERROR; ответы 4XX выводятся как сообщения WARNING. Запросы, которые регистрируются в журнале django.security, не регистрируются в django.request.

Сообщения этого регистратора имеют следующий дополнительный контекст:

  • status_code: Код ответа HTTP, связанный с запросом.
  • request: Объект запроса, сгенерировавший сообщение протоколирования.

django.server

Регистрация сообщений, связанных с обработкой запросов, полученных сервером, вызванным командой runserver. Ответы HTTP 5XX регистрируются как сообщения ERROR, ответы 4XX регистрируются как сообщения WARNING, а все остальное регистрируется как INFO.

Сообщения этого регистратора имеют следующий дополнительный контекст:

  • status_code: Код ответа HTTP, связанный с запросом.
  • request: Объект запроса, сгенерировавший сообщение протоколирования.

django.template

Сообщения журнала, связанные с отрисовкой шаблонов.

  • Отсутствующие контекстные переменные регистрируются в виде сообщений DEBUG.

django.db.backends

Сообщения, относящиеся к взаимодействию кода с базой данных. Например, каждый SQL-оператор прикладного уровня, выполняемый запросом, регистрируется на уровне DEBUG в этом регистраторе.

Сообщения этого регистратора имеют следующий дополнительный контекст:

  • duration: Время, затраченное на выполнение оператора SQL.
  • sql: Оператор SQL, который был выполнен.
  • params: Параметры, которые были использованы в вызове SQL.

По соображениям производительности, протоколирование SQL включается только при установке settings.DEBUG в значение True, независимо от уровня протоколирования или установленных обработчиков.

Это протоколирование не включает инициализацию на уровне фреймворка (например, SET TIMEZONE) или запросы управления транзакциями (например, BEGIN, COMMIT и ROLLBACK). Включите протоколирование запросов в вашей базе данных, если вы хотите просмотреть все запросы к базе данных.

django.security.*

Регистраторы безопасности будут получать сообщения о любом появлении SuspiciousOperation и других ошибок, связанных с безопасностью. Для каждого подтипа ошибки безопасности, включая все SuspiciousOperations, существует свой поджурнал. Уровень события журнала зависит от того, как обрабатывается исключение. Большинство случаев регистрируется как предупреждение, в то время как любая SuspiciousOperation>, которая достигает обработчика WSGI, регистрируется как ошибка. Например, если в запрос от клиента включен заголовок HTTP Host, который не соответствует ALLOWED_HOSTS, Django вернет ответ 400, а сообщение об ошибке будет записано в журнал django.security.DisallowedHost.

По умолчанию эти события журнала будут поступать в регистратор django, который отправляет администраторам сообщения об ошибках при DEBUG=False. Запросы, приводящие к ответу 400 из-за ошибки SuspiciousOperation, не будут регистрироваться в журнале django.request, а только в журнале django.security.

Чтобы заглушить определенный тип SuspiciousOperation, вы можете переопределить этот конкретный регистратор, следуя этому примеру:

'handlers': {
    'null': {
        'class': 'logging.NullHandler',
    },
},
'loggers': {
    'django.security.DisallowedHost': {
        'handlers': ['null'],
        'propagate': False,
    },
},

Другими регистраторами django.security, не основанными на SuspiciousOperation, являются:

django.db.backends.schema

Регистрирует SQL-запросы, которые выполняются во время изменения схемы базы данных командой migrations framework. Обратите внимание, что он не будет регистрировать запросы, выполняемые RunPython. Сообщения этого логгера имеют params и sql в дополнительном контексте (но, в отличие от django.db.backends, не продолжительность). Значения имеют тот же смысл, который объясняется в django.db.backends.

Обработчики

Django предоставляет один обработчик журналов в дополнение к тем, которые предоставляются модулем протоколирования Python.

class AdminEmailHandler(include_html=False, email_backend=None)[исходный код]

Этот обработчик отправляет письмо на сайт ADMINS для каждого полученного им сообщения журнала.

Если запись журнала содержит атрибут request, в письмо будет включена полная информация о запросе. Тема письма будет включать фразу «internal IP», если IP-адрес клиента находится в настройках INTERNAL_IPS; если нет, она будет включать «EXTERNAL IP».

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

Аргумент include_html в AdminEmailHandler используется для управления тем, включает ли письмо с обратным отслеживанием HTML-вложение, содержащее полное содержимое отладочной веб-страницы, которая была бы создана, если бы DEBUG было True. Чтобы задать это значение в вашей конфигурации, включите его в определение обработчика для django.utils.log.AdminEmailHandler, например, так:

'handlers': {
    'mail_admins': {
        'level': 'ERROR',
        'class': 'django.utils.log.AdminEmailHandler',
        'include_html': True,
    }
},

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

Установив аргумент email_backend в AdminEmailHandler, можно переопределить email backend, используемый обработчиком, следующим образом:

'handlers': {
    'mail_admins': {
        'level': 'ERROR',
        'class': 'django.utils.log.AdminEmailHandler',
        'email_backend': 'django.core.mail.backends.filebased.EmailBackend',
    }
},

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

send_mail(subject, message, *args, **kwargs)[исходный код]

Отправляет электронные письма пользователям-администраторам. Чтобы настроить это поведение, вы можете использовать подкласс класса AdminEmailHandler и переопределить этот метод.

Фильтры

Django предоставляет некоторые фильтры журналов в дополнение к тем, которые предоставляет модуль протоколирования Python.

class CallbackFilter(callback)[исходный код]

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

Например, чтобы отфильтровать UnreadablePostError (появляется, когда пользователь отменяет загрузку) из писем администратора, вы создадите функцию фильтрации:

from django.http import UnreadablePostError

def skip_unreadable_post(record):
    if record.exc_info:
        exc_type, exc_value = record.exc_info[:2]
        if isinstance(exc_value, UnreadablePostError):
            return False
    return True

а затем добавьте его в конфигурацию протоколирования:

'filters': {
    'skip_unreadable_posts': {
        '()': 'django.utils.log.CallbackFilter',
        'callback': skip_unreadable_post,
    }
},
'handlers': {
    'mail_admins': {
        'level': 'ERROR',
        'filters': ['skip_unreadable_posts'],
        'class': 'django.utils.log.AdminEmailHandler'
    }
},
class RequireDebugFalse[исходный код]

Этот фильтр будет пропускать записи только в том случае, если settings.DEBUG имеет значение False.

Этот фильтр используется следующим образом в конфигурации LOGGING по умолчанию, чтобы гарантировать, что AdminEmailHandler отправляет письма с ошибками администраторам только тогда, когда DEBUG становится False:

'filters': {
    'require_debug_false': {
        '()': 'django.utils.log.RequireDebugFalse',
    }
},
'handlers': {
    'mail_admins': {
        'level': 'ERROR',
        'filters': ['require_debug_false'],
        'class': 'django.utils.log.AdminEmailHandler'
    }
},
class RequireDebugTrue[исходный код]

Этот фильтр аналогичен RequireDebugFalse, за исключением того, что записи передаются только тогда, когда DEBUG и True.

Конфигурация протоколирования по умолчанию в Django

По умолчанию Django настраивает следующее протоколирование:

Когда DEBUG становится True:

  • Регистратор django отправляет сообщения в иерархии django (кроме django.server) на уровне INFO или выше на консоль.

Когда DEBUG становится False:

  • Регистратор django отправляет сообщения в иерархии django (кроме django.server) с уровнем ERROR или CRITICAL в AdminEmailHandler.

Не зависит от значения DEBUG:

  • Регистратор django.server отправляет сообщения уровня INFO или выше на консоль.

Все логгеры, кроме django.server, распространяют логирование на своих родителей, вплоть до корневого логгера django. Обработчики console и mail_admins прикрепляются к корневому логгеру для обеспечения описанного выше поведения.

Смотрите также Configuring logging, чтобы узнать, как вы можете дополнить или заменить эту конфигурацию протоколирования по умолчанию, определенную в django/utils/log.py.

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