Django+Celery自定义日志handler主进程生效但Worker进程失效
问题根因
核心是Celery启动时的日志初始化和Django配置加载顺序冲突,没有其他玄学问题:
- Celery worker启动的最早阶段就会默认初始化自身日志系统,这时候Django的settings还没完成加载,你写在
LOGGING里的根logger规则根本没被应用到worker进程的logging实例上 - 你之前试的
after_setup_task_logger信号没触发很正常:Celery 5.x版本之后这个信号只有在使用Celery默认日志配置、且没有禁用根logger劫持的时候才会触发,你手动接了setup_logging信号之后默认日志流程被截断,这个信号自然不会触发 - 你看到root logger上挂了自定义handler但日志打不出来,是因为Celery初始化时默认给所有任务相关logger设了
propagate = False,日志根本不会向上传递到root logger,自然走不到你挂在root上的handler。
可直接落地的解决方案
不需要给每个文件的logger单独挂handler,按下面步骤修改即可:
- 修正Celery实例初始化配置,强制关闭Celery自带的日志劫持逻辑
不要只在settings里设置CELERYD_HIJACK_ROOT_LOGGER,新版本Celery中该配置不会直接同步到app实例,需要在初始化app时显式声明:# 项目celery.py中初始化app的位置 app = Celery("your_project_name") app.config_from_object("django.conf:settings", namespace="CELERY") # 强制关闭Celery对日志系统的所有默认篡改 app.conf.worker_hijack_root_logger = False # 日志格式和Django配置保持一致即可 app.conf.worker_log_format = "[%(asctime)s: %(levelname)s/%(processName)s] [%(name)s] %(message)s" app.conf.worker_task_log_format = "[%(asctime)s: %(levelname)s/%(processName)s] [%(name)s] %(message)s" - 精简Django LOGGING配置,移除冗余规则
原配置中单独给celery写logger规则是多余的,容易和根logger继承逻辑冲突,调整为如下配置即可,注意给自定义handler补上formatter避免日志格式错乱:LOGGING = { "version": 1, "disable_existing_loggers": False, "formatters": { "default": { "format": "[%(asctime)s: %(levelname)s/%(processName)s] [%(name)s] %(message)s" } }, "filters": {"require_debug_false": {"()": "django.utils.log.RequireDebugFalse"}}, "handlers": { "console": { "level": "INFO", "class": "logging.StreamHandler", "formatter": "default", }, "mail_admins": { "level": "ERROR", "filters": ["require_debug_false"], "class": "django.utils.log.AdminEmailHandler", }, "my_custom_handler": { "level": "INFO", "class": "my_custom_handler_class", "formatter": "default", }, }, "loggers": { "django.request": { "handlers": ["mail_admins"], "level": "ERROR", "propagate": True, }, # 根logger配置,所有未单独配置的logger都会自动继承该规则 "": { "handlers": ["console", "my_custom_handler"], "level": "INFO", "propagate": True, }, }, } - 绑定日志初始化信号,保证Django日志配置加载时机正确
不要在setup_logging信号里手动给root logger追加handler,直接在信号中加载Django完整的LOGGING配置,同时清理Celery默认挂载的冗余handler、修正传播属性:from celery.signals import setup_logging from django.conf import settings from logging.config import dictConfig import logging @setup_logging.connect def config_celery_logging(loglevel, logfile, format, colorize, **kwargs): # 直接加载Django中定义的完整日志配置,保证和Django服务日志逻辑统一 dictConfig(settings.LOGGING) # 清理Celery偷偷给celery系列logger挂的默认handler,强制开启日志向上传播 for logger_name in logging.root.manager.loggerDict: if logger_name.startswith("celery"): target_logger = logging.getLogger(logger_name) target_logger.handlers.clear() target_logger.propagate = True - 清理冗余代码
删掉之前写的worker_process_init信号中手动获取logger、挂载handler的逻辑,所有业务代码、任务文件中统一用标准库方式获取logger即可:import logging logger = logging.getLogger(__name__)
注意:不需要使用Celery的
get_task_logger,该方法是老版本兼容逻辑,现在直接用标准库logging即可和Django生态完全打通。
验证标准
改完重启Celery worker后,任务中打印的INFO及以上级别日志会同时输出到控制台和你的自定义handler,不需要在每个文件单独配置handler,日志传播逻辑完全符合标准logging规则。
内容的提问来源于stack exchange,提问作者Nonos
相关产品推荐
相关产品推荐

