Django Celery日志记录最佳实践



我正在尝试让Celery日志记录与Django一起工作。我在settings.py中设置了日志以转到控制台(这在我托管Heroku时很好)。在每个模块的顶部,我有:

import logging
logger = logging.getLogger(__name__)

在我的任务中,我有:

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的日志条目中,因为文档似乎表明它应该显示)。

我怀疑我错过了一些简单的东西。

当您的记录器在"另一个模块"的开头初始化时,它会链接到另一个记录器。处理您的消息。它可以是根记录器,或者我通常在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)

在这种情况下,我想它应该像你想象的那样工作。

第页。在Python2.7+.中添加了S.dictConfig

要解决重复日志记录问题,对我有效的方法是在声明设置时将传播设置设置为false。记录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中进行,然后日志记录将被传播到"父",在本例中,它恰好是您的芹菜任务。因此双重记录。但是,对于特定的记录器,将propagation设置为False意味着对于该记录器/应用程序,其日志不会传播到父级,因此不会进行"双重"日志记录。默认情况下,"传播"设置为True

这里有一个链接到django文档部分,关于父/子记录器填充

也许这会对某人有所帮助,我的问题是将所有芹菜日志发送到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
}
}

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

[这假设你已经在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标志-日志文件将Celery输出(例如,worker init、started task、task failed)发送到单独的位置。或者,在这里使用另一个答案,将"芹菜"记录器发送到您选择的文件。

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

最新更新