Подтвердить что ты не робот

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

Я пытаюсь заставить журнал 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

Но если эта задача затем вызывает метод в другом модуле, например. a 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, но это не сработало, когда я его попробовал, и даже если бы я его заработал, я потерял бы бит "PoolWorker-6", который я действительно хочу. (Кстати, я не могу понять, как получить имя задачи для отображения в записи журнала из Celery, поскольку docs, кажется, указывает, что это должно быть).

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

4b9b3361

Ответ 1

Когда ваш логгер инициализируется в начале "другого модуля", он связывается с другим регистратором. Которые обрабатывают ваши сообщения. Это может быть корневой регистратор, или обычно я вижу в проектах 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)

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

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

Ответ 2

Это вызывает беспокойство, что сельдерей мешает корневому журналу (который не является лучшей практикой и не может полностью контролироваться), но он никак не отключает ваши пользовательские регистраторы приложений, поэтому используйте собственные имена обработчиков и определяйте свои а не пытаться решить эту проблему с помощью сельдерея. [Мне нравится, чтобы мой журнал приложений оставался отдельным). Вы можете использовать отдельные обработчики или то же самое для 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, предполагая, что у вас есть несколько процессов или одни и те же файлы журналов используются совместно с работниками сельдерея. Если вы используете многосерверное решение, вы, вероятно, захотите регистрироваться где-то централизованным в любом случае.

Ответ 3

Чтобы устранить проблему дублирования журнала, для меня работала установка значения параметра распространения при объявлении моих настроек. LOGGING 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 выглядит следующим образом:
my_project/
- tasks.py
- email.py

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

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