Элегантная настройка логирования Python в Django

101

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

  • Различные обработчики журналов для разных событий - то есть я хочу иметь возможность вести журнал в разных файлах.
  • Легкий доступ к логгерам в моих модулях. Модуль должен иметь возможность без особых усилий найти свой регистратор.
  • Должен быть легко применим к модулям командной строки. Части системы являются автономными процессами командной строки или демонами. С этими модулями должно быть легко вести журнал.

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

У вас есть настройки ведения журнала, которые вам нравятся? Пожалуйста, опишите это: как вы настраиваете конфигурацию (используете ли вы logging.confили настраиваете в коде), где / когда вы запускаете регистраторы, и как вы получаете доступ к ним в своих модулях и т. Д.

Parand
источник
1
Вам может быть полезен следующий скринкаст - ericholscher.com/blog/2008/aug/29/… . Кроме того, лучшая поддержка регистрации в Django была предложена Саймоном Уиллисоном (см. Simonwillison.net/2009/Sep/28/ponies ).
Доминик Роджер,
@Dominic Rodger - вы уже можете выполнять гибкое ведение журнала приложений в Django, предложение Саймона в основном для облегчения ведения журнала во внутреннем устройстве Django. В Python ведется работа по добавлению конфигурации на основе словаря в ведение журнала Python, от чего Django может извлечь выгоду.
Винай Саджип,

Ответы:

58

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

В каждом модуле я определяю регистратор, используя

logger = logging.getLogger(__name__)

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

Если мое приложение будет потенциально использоваться на сайте, который не настраивает ведение журнала в settings.py, я определяю NullHandler где-нибудь следующим образом:

#someutils.py

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

null_handler = NullHandler()

и убедитесь, что его экземпляр добавлен во все регистраторы, созданные в модулях моих приложений, которые используют ведение журнала. (Примечание: NullHandler уже находится в пакете журналов для Python 3.1 и будет в Python 2.7.) Итак:

logger = logging.getLogger(__name__)
logger.addHandler(someutils.null_handler)

Это сделано для того, чтобы ваши модули работали нормально на сайте, который не настраивает ведение журнала в settings.py, и чтобы вы не получали раздражающих сообщений «Не найдены обработчики для регистратора XYZ» (которые являются предупреждениями о потенциально неправильно настроенное ведение журнала).

Это соответствует вашим заявленным требованиям:

  • Вы можете настроить разные обработчики журналов для разных событий, как вы это делаете сейчас.
  • Легкий доступ к логгерам в ваших модулях - пользуйтесь getLogger(__name__).
  • Легко применимо к модулям командной строки - они также импортируют settings.py.

Обновление: обратите внимание, что с версии 1.3 Django теперь поддерживает ведение журнала .

Винай Саджип
источник
Разве для этого не потребуется, чтобы каждый модуль имел обработчик, определенный в конфигурации (вы не можете использовать обработчик для foo для обработки foo.bar)? Посмотрите наш разговор несколько лет назад на groups.google.com/group/comp.lang.python/browse_thread/thread/…
Эндрю Кук,
1
@andrew cooke: вы можете использовать обработчик для fooобработки событий, в которые ведется журнал foo.bar. Re. этот поток - и fileConfig, и dictConfig теперь имеют параметры, предотвращающие отключение старых регистраторов. См. Эту проблему: bugs.python.org/issue3136 , которая появилась через пару месяцев после вашей проблемы bugs.python.org/issue2697 - во всяком случае, она решена с июня 2008 года.
Виней Саджип
не лучше ли было бы сделать, logger = someutils.getLogger(__name__)откуда someutils.getLoggerвозвращается регистратор logging.getLoggerс уже добавленным null_handler?
7yl4r
1
@ 7yl4r Вам не нужно, чтобы каждый регистратор имел NullHandlerдобавленный - обычно это был регистратор верхнего уровня для вашей иерархии пакетов. Так что это было бы излишним, ИМО.
Винай Саджип,
123

Я знаю, что это уже решенный ответ, но согласно django> = 1.3 есть новый параметр ведения журнала.

Переход от старого к новому не происходит автоматически, поэтому я подумал, что запишу его здесь.

И, конечно же, посмотрите документацию django, чтобы узнать больше.

Это базовая конфигурация, созданная по умолчанию с помощью django-admin createproject v1.3 - пробег может измениться с последними версиями django:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'mail_admins': {
            'level': 'ERROR',
            'class': 'django.utils.log.AdminEmailHandler',
        }
    },
    'loggers': {
        'django.request': {
            'handlers': ['mail_admins'],
            'level': 'ERROR',
            'propagate': True,
        }
    }
}

Эта структура основана на стандартном протоколе Python dictConfig , который определяет следующие блоки:

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

    • class(обязательное). Это полное имя класса обработчика.
    • level(по желанию). Уровень обработчика.
    • formatter(по желанию). Идентификатор средства форматирования для этого обработчика.
    • filters(по желанию). Список идентификаторов фильтров для этого обработчика.

Я обычно делаю по крайней мере так:

  • добавить файл .log
  • настроить мои приложения для записи в этот журнал

Что означает:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
        },
        'simple': {
            'format': '%(levelname)s %(message)s'
        },
    },
    'filters': {
        'require_debug_false': {
            '()': 'django.utils.log.RequireDebugFalse'
        }
    },
    'handlers': {
        'null': {
            'level':'DEBUG',
            'class':'django.utils.log.NullHandler',
        },
        'console':{
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        # I always add this handler to facilitate separating loggings
        'log_file':{
            'level': 'DEBUG',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': os.path.join(VAR_ROOT, 'logs/django.log'),
            'maxBytes': '16777216', # 16megabytes
            'formatter': 'verbose'
        },
        'mail_admins': {
            'level': 'ERROR',
            'filters': ['require_debug_false'],
            'class': 'django.utils.log.AdminEmailHandler',
            'include_html': True,
        }
    },
    'loggers': {
        'django.request': {
            'handlers': ['mail_admins'],
            'level': 'ERROR',
            'propagate': True,
        },
        'apps': { # I keep all my of apps under 'apps' folder, but you can also add them one by one, and this depends on how your virtualenv/paths are set
            'handlers': ['log_file'],
            'level': 'INFO',
            'propagate': True,
        },
    },
    # you can also shortcut 'loggers' and just configure logging for EVERYTHING at once
    'root': {
        'handlers': ['console', 'mail_admins'],
        'level': 'INFO'
    },
}

редактировать

См. Исключения запросов теперь всегда регистрируются, и билет № 16288 :

Я обновил приведенный выше образец conf, чтобы явно включить правильный фильтр для mail_admins, чтобы по умолчанию электронные письма не отправлялись, когда отладка имеет значение True.

Вам следует добавить фильтр:

'filters': {
    'require_debug_false': {
        '()': 'django.utils.log.RequireDebugFalse'
    }
},

и примените его к обработчику mail_admins:

    'mail_admins': {
        'level': 'ERROR',
        'filters': ['require_debug_false'],
        'class': 'django.utils.log.AdminEmailHandler',
        'include_html': True,
    }

В противном случае django.core.handers.base.handle_uncaught_exceptionне передает ошибки в регистратор django.request, если settings.DEBUG имеет значение True.

Если вы этого не сделаете в Django 1.5, вы получите

DeprecationWarning: у вас нет фильтров, определенных в обработчике ведения журнала mail_admins: добавление неявного фильтра debug-false-only

но все будет работать правильно ОБЕИХ в django 1.4 и django 1.5.

** конец редактирования **

Этот conf сильно вдохновлен примером conf в django doc, но с добавлением части файла журнала.

Я также часто делаю следующее:

LOG_LEVEL = 'DEBUG' if DEBUG else 'INFO'

...
    'level': LOG_LEVEL
...

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

import logging
# Get an instance of a logger
logger = logging.getLogger(__name__)
class NullHandler(logging.Handler): #exists in python 3.1
    def emit(self, record):
        pass
nullhandler = logger.addHandler(NullHandler())

# here you can also add some local logger should you want: to stdout with streamhandler, or to a local file...

[...]

logger.warning('etc.etc.')

Надеюсь это поможет!

Стефано
источник
Стефано, большое спасибо за подробный ответ, очень полезно. Это может сделать целесообразным обновление до 1.3.
Parand
Паранд, определенно (ИМХО!) Стоит перейти на django 1.3, хотя есть несколько моментов, о которых нужно позаботиться для плавного перехода - откройте новый вопрос SO, если у вас возникнут проблемы ;-)
Стефано
кстати: я до сих пор использую такие настройки и файловый журнал, но я перешел на караул для производства!
Стефано
@clime ну я попытался объяснить это в самом ответе: в случае, если не определено никакое конфигурирование логирования. Это позволяет избежать предупреждений о том, что обработчик не указан. Особенно полезно для библиотек, которые не обязательно вызываются только в Django (ссылка)
Стефано
Я не понимаю, как вы используете это определение: 'null': {'level': 'DEBUG', 'class': 'django.utils.log.NullHandler',}
clime
9

Мы инициализируем ведение журнала на верхнем уровне urls.pyс помощью logging.iniфайла.

Местоположение logging.iniуказано в settings.py, но это все.

Затем каждый модуль выполняет

logger = logging.getLogger(__name__)

Чтобы различать экземпляры тестирования, разработки и производства, у нас есть разные файлы logging.ini. По большей части у нас есть «консольный журнал», который отправляется на stderr только с ошибками. У нас есть «журнал приложений», в котором используется обычный файл журнала, который переходит в каталог журналов.

С.Лотт
источник
В итоге я использовал это, за исключением инициализации в settings.py вместо urls.py
Parand
Как вы используете настройки из settings.py в вашем файле logging.ini? Например, мне нужен параметр BASE_DIR, чтобы я мог указать ему, где хранить файлы журнала.
slypete 03
@slypete: Мы не используем настройки в logging.ini. Поскольку ведение журнала в значительной степени независимое, мы не используем никаких настроек Django. Да, есть возможность что-то повторить. Нет, особой практической разницы это не имеет.
S.Lott
В этом случае я бы использовал отдельный файл logging.ini при каждой установке моего приложения.
slypete 03
@slypete: у вас есть файл settings.py для каждой установки. У вас также есть logging.ini для каждой установки. Кроме того, у вас, вероятно, также есть файл конфигурации Apache для каждой установки. Плюс файл интерфейса wsgi. Я не уверен, о чем вы говорите.
S.Lott
6

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

django-csvlog

У этого проекта до сих пор нет полной документации, но я работаю над ним.

Одуван
источник