为什么需要自定义 Celery 日志
默认的 Celery 日志配置虽然满足基本需求,但在以下情况下可能需要进行自定义:
- 支持日志滚动:原生celery不支持日志滚动。
- 更详细的日志信息:需要包含更多上下文信息,以便更好地理解任务的执行过程。
- 日志格式调整:适应现有的日志管理系统,如 ELK(Elasticsearch, Logstash, Kibana)堆栈。
- 多日志目标:将日志同时输出到多个地方,例如控制台、文件和远程日志服务器。
- 日志级别控制:根据不同的模块或任务设定不同的日志级别。
Celery 日志为什么复杂
Celery的日志记录看起来不符合Python的风格,并且显得复杂。这不是Celery的问题,而是因为Python的日志系统默认不支持Celery所需的各种并发环境,如eventlets、greenlets、prefork和线程等。因此,Celery需要自定义日志记录。
Celery提供了一个特殊的函数get_task_logger,它在celery.utils.log中用于获取日志记录器。这个记录器会自动包含任务名称和唯一ID。Celery的文档建议使用get_task_logger。
在worker中应用日志,我更喜欢使用标准的logging.getLogger方法,因为这样可以保持代码库的清晰,并与Celery应用的上下文分离。
策略1:增强Celery日志记录器
Celery提供了一个after_setup_logger信号,该信号在Celery设置日志记录器后触发
(http://docs.celeryproject.org/en/latest/userguide/signals.html#after-setup-logger)。该信号传递了日志记录器对象(以及其他一些参数),你可以方便地向其添加自定义日志处理程序。
Celery 使用 Python 的标准 logging
模块进行日志记录。首先,需要获取 Celery 默认的日志记录器。
python
import os
import logging
from celery import Celery
from celery.signals import after_setup_logger
@after_setup_logger.connect
def setup_loggers(logger, *args, **kwargs):
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# FileHandler
fh = logging.FileHandler('logs.log')
fh.setFormatter(formatter)
logger.addHandler(fh)
# SysLogHandler
slh = logging.handlers.SysLogHandler(address=('logsN.papertrailapp.com', '...'))
slh.setFormatter(formatter)
logger.addHandler(slh)
策略2:重写Celery根记录器
您可以通过连接setup_logging信号来阻止Celery配置任何记录器。这使您可以完全用自己的配置重写日志配置。
python
import logging
import logging.config
from celery.signals import setup_logging
@setup_logging.connect
def setup_logging(**kwargs):
logfile = kwargs.get("logfile")
loglevel = kwargs.get("loglevel")
loglevel_str = logging.getLevelName(loglevel)
config = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'default': {
'format': ' %(asctime)s - %(name)s - %(levelname)s - %(message)s',
'datefmt': "%Y/%m/%d %H:%M:%S"
}
},
'handlers': {
'rotatingHandler': {
'class': 'logging.handlers.RotatingFileHandler',
'filename': logfile,
'maxBytes': 2*1024*1024,
'backupCount': 3,
'formatter': 'default',
'encoding': 'utf-8'
},
'timedHandler': {
'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': logfile,
'when': 'midnight',
'interval': 1,
'backupCount': 7,
'formatter': 'default',
'encoding': 'utf-8'
},
},
'loggers': {
'celery': {
'handlers': ['timedHandler'],
'level': loglevel_str,
'propagate': False
},
},
'root': {
'handlers': ['rotatingHandler'],
'level': loglevel_str,
'propagate': False
},
}
logging.config.dictConfig(config)
策略3:禁用Celery logger配置
另一种解决方案是让Celery设置其日志记录器但不使用它,并防止其劫持根日志记录器。
默认情况下,Celery会移除根日志记录器上任何先前配置的处理程序。如果你想自定义自己的日志处理程序而不被Celery干扰,可以通过设置worker_hijack_root_logger=False来禁用此行为。
这允许你重新控制根日志记录器并回退到标准的Python日志设置。
使用这种方法时要小心,因为你需要完全负责确保你的Python日志记录与Celery设置(eventlets、greenlets、prefork(子进程)、线程等)兼容。
python
import os
import logging
from celery import Celery
from celery.signals import setup_logging
app = Celery('app')
app.conf.update({
'worker_hijack_root_logger': False})
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger()
# StreamHandler
sh = logging.StreamHandler()
sh.setFormatter(formatter)
logger.addHandler(sh)
# FileHandler
fh = logging.FileHandler('logs.log')
fh.setFormatter(formatter)
logger.addHandler(fh)
# SysLogHandler
slh = logging.handlers.SysLogHandler(address=('logsN.papertrailapp.com', '...'))
slh.setFormatter(formatter)
logger.addHandler(slh)