Django Celery采伐最佳实践

时间:2012-11-13 18:14:11

标签: python django logging celery django-celery

我正在尝试让Celery日志记录与Django一起使用。我在settings.py中进行了日志记录设置以进入控制台(在我Heroku上托管时工作正常)。在每个模块的顶部,我有:

import logging
logger = logging.getLogger(__name__)

在我的tasks.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日志记录,但是当我尝试它时这不起作用,即使我确实让它工作,我也会丢失我想要的"PoolWorker-6"位。 (顺便说一句,我无法弄清楚如何在Celery的日志条目中显示任务名称,因为the docs似乎表明它应该)。

我怀疑我在这里缺少一些简单的东西。

4 个答案:

答案 0 :(得分:69)

当您的记录器在“另一个模块”的开头初始化时,它会链接到另一个记录器。哪个处理你的消息。它可以是root logger,或者通常我在Django项目中看到 - 名为''的记录器。

这里最好的方法是覆盖你的日志配置:

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 +中添加。

答案 1 :(得分:8)

令人不安的是,Celery干扰了根记录器(这不是最佳做法,无法完全控制),但它不会以任何方式禁用应用程序的自定义记录器,因此请使用您自己的处理程序名称并定义自己的行为,而不是试图解决Celery的这个问题。 [无论如何,我希望保持我的应用程序日志分离)。你可以使用单独的处理程序或相同的Django代码和Celery任务,你只需要在Django LOGGING配置中定义它们。将模块,文件名和processName的格式化args添加到格式化程序中,以帮助您区分消息的来源。

[这假设你已经在LOGGING设置值中为'yourapp'设置了一个指向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')

对于Celery生成的日志记录 - 如果需要,使用celeryd flags --logfile将Celery输出(例如,worker init,启动任务,任务失败)发送到单独的位置。或者,使用另一个答案,将'celery'记录器发送到您选择的文件。

注意:我不会使用RotatingFileHandlers - 多进程应用程序不支持它们。来自另一个工具(如logrotate)的日志轮换更安全,与Django的日志记录相同,假设您有多个进程,或者与芹菜工作者共享相同的日志文件。如果您使用的是多服务器解决方案,那么您可能希望将其集中在某个集中的位置。

答案 2 :(得分:6)

要修复重复的日志记录问题,对我来说有用的是在声明我的设置时将传播设置设置为false.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中的某个函数;日志记录将在email.py中发生,然后该日志记录将传播到“父母”#39;在这种情况下恰好是你的芹菜任务。因此双重记录。但是对于特定的记录器设置传播为False意味着对于该记录器/应用程序,其日志不会传播到父级,因此它们将不会被“双”'日志记录。 默认情况下'传播'设置为True

这里有link to the django docs部分关于父母/子女记录器的内容

答案 3 :(得分:1)

也许会帮助某人,我的问题是将所有芹菜原木发送到Graylog。这是解决方法。

celery.py:

app.config_from_object('django.conf:settings', namespace='CELERY')


# ====== Magic starts
from celery.signals import setup_logging

@setup_logging.connect
def config_loggers(*args, **kwargs):
    from logging.config import dictConfig
    from django.conf import settings
    dictConfig(settings.LOGGING)
# ===== Magic ends


# Load task modules from all registered Django app configs.
app.autodiscover_tasks()

settings.py:

LOGGING = {
    'version': 1,
    'handlers': {
        'graypy': {
            'class': 'graypy.GELFTCPHandler',
            'host': GRAYLOG_HOST,
            'port': GRAYLOG_PORT,
        }
    },
    'loggers': {
        'my_project': {
            'handlers': ['graypy'],
            'level': 'INFO',
        },
        # ====== Magic starts
        'celery': {
            'handlers': ['graypy'],
            'level': 'INFO',
        }
        # ===== Magic ends
    }
}