web-dev-qa-db-fra.com

Django Meilleures pratiques de journalisation du céleri

J'essaie de faire fonctionner la journalisation Celery avec Django. J'ai une configuration de connexion dans settings.py pour accéder à la console (cela fonctionne très bien car j'héberge sur Heroku). En haut de chaque module, j'ai:

import logging
logger = logging.getLogger(__name__)

Et dans mes tâches.py, j'ai:

from celery.utils.log import get_task_logger
logger = get_task_logger(__name__)

Cela fonctionne bien pour la journalisation des appels d'une tâche et j'obtiens une sortie comme celle-ci:

2012-11-13T18:05:38+00:00 app[worker.1]: [2012-11-13 18:05:38,527: INFO/PoolWorker-2] Syc feed is starting

Mais si cette tâche appelle alors une méthode dans un autre module, par exemple une méthode queryset, j'obtiens des entrées de journal en double, par exemple.

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

Je pense que je pourrais utiliser

CELERY_Hijack_ROOT_LOGGER = False

pour utiliser simplement la journalisation Django mais cela n'a pas fonctionné quand je l'ai essayé et même si je le faisais fonctionner, je perdrais le "PoolWorker-6" peu que je veux. (Par ailleurs, je ne peux pas comprendre comment obtenir le nom de la tâche à afficher dans l'entrée de journal de Celery, car les documents semble indiquer qu'il devrait).

Je soupçonne que je manque quelque chose de simple ici.

63
alan

Lorsque votre enregistreur s'est initialisé au début d'un "autre module", il est lié à un autre enregistreur. Qui gèrent vos messages. Il peut s'agir de l'enregistreur racine, ou généralement je vois dans Django - enregistreur avec nom ''.

Le meilleur moyen ici, est de remplacer votre configuration de journalisation:

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)

Dans ce cas, je suppose que cela devrait fonctionner comme vous le supposez.

P.S. dictConfig ajouté en Python2.7 +.

69
Rustem

Il est troublant que Celery interfère avec l'enregistreur racine (ce qui n'est pas la meilleure pratique et ne peut pas être complètement contrôlé), mais cela ne désactive pas les enregistreurs personnalisés de votre application en aucune façon, alors utilisez vos propres noms de gestionnaire et définissez plutôt votre propre comportement que d'essayer de résoudre ce problème avec Celery. [J'aime quand même garder la journalisation de mon application séparée). Vous pouvez utiliser des gestionnaires distincts ou les mêmes pour Django et tâches Celery, il vous suffit de les définir dans votre configuration Django LOGGING. Ajoutez des arguments de mise en forme pour le module , filename et processName à votre formateur pour raison, pour vous aider à distinguer d'où proviennent les messages.

[cela suppose que vous avez configuré un gestionnaire pour 'yourapp' dans la valeur des paramètres LOGGING qui pointe vers un Appender - semble que vous le sachiez cependant].

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')

Pour la journalisation générée par Celery - utilisez les indicateurs celeryd --logfile pour envoyer la sortie de Celery (par exemple, init de travailleur, tâche démarrée, tâche échouée) à un endroit séparé si vous le souhaitez. Ou utilisez l'autre réponse ici qui envoie l'enregistreur "céleri" vers un fichier de votre choix.

Remarque: je n'utiliserais pas RotatingFileHandlers - ils ne sont pas pris en charge pour les applications multi-processus. La rotation des journaux à partir d'un autre outil comme logrotate est plus sûre, il en va de même avec la journalisation de Django en supposant que vous avez plusieurs processus là-bas, ou les mêmes fichiers journaux sont partagés avec les travailleurs céleri. Si vous utilisez un multi- solution de serveur que vous souhaitez probablement vous connecter quelque part centralisée de toute façon.

8
Lincoln B

Pour résoudre le problème de journalisation des doublons, ce qui a fonctionné pour moi est de définir le paramètre de propagation sur false lors de la déclaration de mes paramètres.

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
        },
    }
}

disons que votre disposition de projet Django ressemble à:
mon projet/
- tasks.py
- email.py

et disons que l'une de vos tâches appelle une fonction dans email.py; la journalisation aura lieu dans email.py, puis cette journalisation sera propagée au "parent", qui dans ce cas se trouve être votre tâche de céleri. Ainsi double enregistrement. Mais définir la propagation sur False pour un enregistreur particulier signifie que pour cet enregistreur/application, ses journaux ne seront pas propagés au parent, d'où leur absence de journalisation "double". Par défaut, "propagate" est défini sur True

Voici un lien vers la section Django docs sur ce truc des enregistreurs parents/enfants

6
Komu