我正在尝试让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似乎表明它应该)。
我怀疑我在这里缺少一些简单的东西。
答案 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
}
}