web-dev-qa-db-ja.com

Django Celery Logging Best Practice

Djangoで動作するCeleryのログを取得しようとしています。コンソールに移動するために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からログエントリに表示するタスク名を取得する方法がわかりません)。

ここで何か簡単なものが欠けているのではないかと思います。

63
alan

ロガーが「別のモジュール」の先頭で初期化されると、別のロガーにリンクします。あなたのメッセージを処理します。ルートロガー、または通常Django projects-名前''

ここでの最善の方法は、ロギング設定を上書きすることです:

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)

この場合、想定どおりに動作するはずです。

追伸Python2.7 +でdictConfigが追加されました。

69
Rustem

Celeryがルートロガーに干渉するのは厄介です(ベストプラクティスではなく、完全に制御することはできません)が、アプリのカスタムロガーを無効にすることはないため、独自のハンドラー名を使用して、むしろ独自の動作を定義してくださいこの問題をCeleryで修正しようとするよりも。 [とにかく、アプリケーションのログを個別に保持するのが好きです)。 DjangoコードとCeleryタスクに別々のハンドラーまたは同じハンドラーを使用できます。それらを定義する必要があるのは、Django LOGGING構成です。モジュールのフォーマット引数を追加します。 、ファイル名、およびprocessNameを使用して、メッセージの発信元を区別しやすくするために、フォーマッタに健全性を持たせます。

[これは、アペンダーを指すLOGGING設定値で 'yourapp'のハンドラーをセットアップしていることを前提としています。

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フラグ--logfileを使用して、必要に応じてCeleryの出力(ワーカーの初期化、開始タスク、タスクの失敗など)を別の場所に送信します。または、「セロリ」ロガーを選択したファイルに送信する他の回答をここで使用します。

注:RotatingFileHandlersは使用しません。マルチプロセスアプリではサポートされていません。 logrotateのような別のツールからのログローテーションの方が安全です。複数のプロセスがある場合、または同じログファイルがセロリワーカーと共有されている場合は、Djangoとにかくどこかで集中的にログを取りたいサーバーソリューション。

8
Lincoln B

重複したロギングの問題を修正するには、設定を宣言するときに伝搬設定をfalseに設定することが効果的でした。LOGGINGdict

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プロジェクトのレイアウトは次のようになります。
私のプロジェクト/
-tasks.py
-email.py

そして、タスクの1つがemail.pyのいくつかの関数を呼び出すと言ってみましょう。ロギングはemail.pyで行われ、そのロギングは「親」に伝播されます。この場合は、たまたまセロリのタスクです。したがって、二重ロギング。ただし、特定のロガーのpropagateをFalseに設定すると、そのロガー/アプリのログは親に伝播されないため、「ダブル」ロギングは行われません。デフォルトでは、 'propagate'はTrueに設定されています

Django docs へのリンク)その親/子ロガーのものに関するセクション

6
Komu