web-dev-qa-db-fra.com

Pourquoi le céleri n'émet-il PAS une exception quand la tâche sous-jacente en lance une

Le céleri ne semble pas gérer correctement les exceptions. 

Si j'ai tâche:

def errorTest():
    raise Exception()

et puis j'appelle

r = errorTest.delay()
In [8]: r.result

In [9]: r.state
Out[9]: 'PENDING'

Et ça va pendre comme ça indéfiniment. 

Aller vérifier les journaux montre que l'erreur IS est renvoyée dans la tâche (et si vous voulez le message, demandez), et je sais que le backend et tout sont correctement configurés, car d'autres tâches fonctionnent et donnent des résultats correctement. 

Dois-je faire quelque chose de génial pour attraper des exceptions dans le céleri?

/ Celery version est 3.0.13, le courtier est RabbitMQ s'exécutant sur ma machine locale

31
Kevin Meyer

Vous pouvez définir une fonction on_failure dans votre sous-classe Task pour les gérer correctement. Si vous cherchez seulement à savoir ce qui s'est passé, vous pouvez configurer les notifications par courrier électronique d'erreur qui vous enverra la trace de pile dans votre configuration de céleri.

Remarque: à compter de la v4, Celery n'est plus prend en charge l'envoi d'e-mails .

13
primalpython

Si vous exécutez Celery avec CELERY_ALWAYS_EAGER défini sur True, assurez-vous d'inclure également cette ligne dans vos paramètres:

CELERY_EAGER_PROPAGATES_EXCEPTIONS = True

http://docs.celeryproject.org/en/latest/configuration.html#celery-eager-propagates-exceptions

28
seddonym

Rendre la réponse de @ primalpython plus explicite. 

Cela va échouer:

@task
def error():
    raise Exception

Entrée sortie:

In [7]: r = error.delay()

In [8]: print r.state
Out[8]: 'PENDING'

In [9]: print r.result
Out[9]: None

Cela va réussir:

@task
def error():
    raise Exception

    def on_failure(self, *args, **kwargs):
        pass

Entrée sortie:

In [7]: r = error.delay()

In [8]: print r.state
Out[8]: 'FAILURE'

In [9]: print r.result
Out[9]: Exception()
3
Kevin Meyer

Pour ce faire, le moyen le plus simple consiste à transmettre une référence à une classe de tâches à utiliser lors de la création de votre nouvelle application Celery.

Dans un module, définissez la classe de tâches à utiliser par défaut:

from celery.app.task import Task
import logging

logger=logging.getLogger(__name__)

class LoggingTask(Task):
  def on_failure(self, exc, task_id, args, kwargs, einfo):
      kwargs={}
      if logger.isEnabledFor(logging.DEBUG):
         kwargs['exc_info']=exc
      logger.error('Task % failed to execute', task_id, **kwargs)
      super().on_failure(exc, task_id, args, kwargs, einfo)

Lorsque vous définissez votre application, référencez le module (remarque: il s'agit d'une référence de chaîne que vous fournissez ..):

from celery import Celery

app=Celery('my_project_name', task_cls='task_package.module_name:LoggingTask')

À partir de ce moment, si aucune classe de tâches n'est spécifiquement fournie, la tâche LoggingTask sera utilisée, vous permettant ainsi d'effectuer les tâchesall existantes (qui utilisent la valeur par défaut) plutôt que de les modifier. Cela signifie également que vous pouvez utiliser le décorateur @shared_task normalement.

1
chander