Лучшая практика лесозаготовки в Django Celery

Я пытаюсь запустить ведение сельдерей с Django . У меня есть регистрация настроек в settings.py чтобы перейти на консоль (это отлично работает, поскольку я размещаю на Heroku ). В верхней части каждого модуля у меня есть:

 import logging logger = logging.getLogger(__name__) 

И в моих задачах.py у меня есть:

 from celery.utils.log import get_task_logger logger = get_task_logger(__name__) 

Это отлично работает для записи вызовов из задачи, и я получаю вывод следующим образом:

 2012-11-13T18:05:38+00:00 app[worker.1]: [2012-11-13 18:05:38,527: INFO/PoolWorker-2] Syc feed is starting 

Но если эта задача затем вызывает метод в другом модуле, например метод queryset , я получаю дубликаты записей журнала, например

 2012-11-13T18:00:51+00:00 app[worker.1]: [INFO] utils.generic_importers.ftp_processor process(): File xxx.csv already imported. Not downloaded 2012-11-13T18:00:51+00:00 app[worker.1]: [2012-11-13 18:00:51,736: INFO/PoolWorker-6] File xxx.csv already imported. Not downloaded 

Я думаю, что могу использовать

 CELERY_HIJACK_ROOT_LOGGER = False 

просто использовать Django в Django но это не сработало, когда я его попробовал, и даже если бы мне это удалось, я потерял бы "PoolWorker-6" который я действительно хочу. (Кстати, я не могу понять, как получить имя задачи для отображения в записи журнала из Celery, поскольку документы, похоже, указывают, что это должно).

Я подозреваю, что мне здесь что-то не хватает.

    Когда ваш логин инициализируется в начале «другого модуля», он связывается с другим регистратором. Которые обрабатывают ваши сообщения. Это может быть корневой журнал, или обычно я вижу в проектах Django – logger с именем '' .

    Лучший способ здесь – переопределить конфигурацию ведения журнала:

     LOGGING = { 'version': 1, 'disable_existing_loggers': True, 'formatters': { 'simple': { 'format': '%(levelname)s %(message)s', 'datefmt': '%y %b %d, %H:%M:%S', }, }, 'handlers': { 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', 'formatter': 'simple' }, 'celery': { 'level': 'DEBUG', 'class': 'logging.handlers.RotatingFileHandler', 'filename': 'celery.log', 'formatter': 'simple', 'maxBytes': 1024 * 1024 * 100, # 100 mb }, }, 'loggers': { 'celery': { 'handlers': ['celery', 'console'], 'level': 'DEBUG', }, } } from logging.config import dictConfig dictConfig(LOGGING) 

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

    PS dictConfig добавлен в Python2.7 +.

    Вызывает беспокойство то, что сельдерей мешает корневому журналу (который не является лучшей практикой и не может полностью контролироваться), но он не отключает пользовательские регистраторы вашего приложения каким-либо образом, поэтому используйте собственные имена обработчиков и определите свое собственное поведение, а чем пытаться решить эту проблему с помощью сельдерея. [Мне нравится, чтобы мой журнал приложений оставался отдельным). Вы можете использовать отдельные обработчики или то же самое для Django-кода и задач Celery, вам просто нужно определить их в конфигурации Django LOGGING. Добавьте аргументы форматирования для модуля, имени файла и имени процесса в ваш форматтер для здравомыслия, чтобы помочь вам отличить, откуда происходят сообщения.

    [предполагается, что вы установили обработчик для «yourapp» в значение параметра LOGGING, указывающее на Appender, – похоже, что вы знаете об этом, хотя].

    views.py

     log = logging.getLogger('yourapp') def view_fun(): log.info('about to call a task') yourtask.delay() 

    tasks.py

     log = logging.getLogger('yourapp') @task def yourtask(): log.info('doing task') 

    Для ведения журнала, который генерирует сельдерей, используйте флаги celeryd –logfile, чтобы отправить исходный продукт Celery (например, рабочий init, запущенная задача, задача не удалась) в отдельное место, если это необходимо. Или используйте другой ответ здесь, который отправляет регистратор «сельдерей» в файл по вашему выбору.

    Примечание. Я бы не использовал RotatingFileHandlers – они не поддерживаются для приложений с несколькими процессами. Вращение журнала с другого инструмента, такого как logrotate, является более безопасным, то же самое происходит с протоколированием из Django, предполагая, что у вас есть несколько процессов или одни и те же файлы журналов используются совместно с работниками сельдерея. Если вы используете многосерверное решение, вы, вероятно, захотите регистрироваться где-то централизованным в любом случае.

    Чтобы устранить проблему с дублированием журнала, то, что сработало для меня, заключается в том, чтобы установить для параметра распространения значение false при объявлении моих настроек. REGGING dict

     LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', 'formatter': 'verbose' }, }, 'formatters': { 'verbose': { 'format': '%(asctime)s %(levelname)s module=%(module)s, ' 'process_id=%(process)d, %(message)s' } }, 'loggers': { 'my_app1': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': False #this will do the trick }, 'celery': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': True }, } } 

    скажем, ваш макет проекта django выглядит так:
    мой проект/
    – tasks.py
    – email.py

    и позволяет сказать, что одна из ваших задач вызывает вызов некоторой функции в email.py; ведение журнала будет происходить по электронной почте.py, а затем регистрация будет распространена на «родительский», что в этом случае будет вашей задачей сельдерея. Таким образом, двойной каротаж. Но настройка распространения на False для конкретного регистратора означает, что для этого журнала / приложения его журналы не будут распространяться на родителя, поэтому их не будет «двойным» протоколированием. По умолчанию для параметра «распространять» установлено значение «Истина»

    Вот ссылка на раздел django docs о том, что родительский / детский журнал