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


101

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

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

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

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


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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.