web-dev-qa-db-ja.com

django.requestロガーがルートに伝播されませんか?

Django 1.5.1:

DEBUG = False

LOGGING = {
    'version': 1,
    'disable_existing_loggers': True,
    'formatters': {
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(module)s %(message)s'
        },
    },
    'handlers': {
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'verbose',
        },
    },
    'loggers': {
        # root logger
        '': {
            'handlers': ['console'],
        },
        #'Django.request': {
        #    'handlers': ['console'],
        #    'level': 'DEBUG',
        #    'propagate': False,
        #},
    }
}

コメント付きの行のコメントを外して、1/0、トレースバックがコンソールに出力されます:

ERROR 2013-11-29 13:33:23,102 base Internal Server Error: /comment/*******/
Traceback (most recent call last):
  ...
  File "*****/comments/views.py", line 10, in post
    1/0
ZeroDivisionError: integer division or modulo by zero
WARNING 2013-11-29 13:33:23,103 csrf Forbidden (CSRF cookie not set.): /comment/******/
[29/Nov/2013 13:33:23] "POST /comment/******/ HTTP/1.0" 500 27

ただし、行がコメントのままの場合、トレースバックはコンソールに出力されません。

[29/Nov/2013 13:33:23] "POST /comment/******/ HTTP/1.0" 500 27

Django.requestロガーは設定されていません。すべてをコンソールに出力するルートロガーに伝播します。

Django.requestは特別です。

なぜ動作しないのですか?

ここ 私は読んだ:

Django 1.5より前は、LOGGING設定は常にデフォルトのDjangoロギング構成を上書きしました。Django 1.5以降、プロジェクトのロギング構成をDjangoのデフォルトとマージして取得できるため、既存の構成に追加するか、既存の構成を置き換えるかを決定できます。

LOGGING dictConfigのdisable_existing_loggersキーがTrue(デフォルト)に設定されている場合、デフォルト設定は完全に上書きされます。または、disable_existing_loggersをFalseに設定して、ロガーの一部またはすべてを再定義できます。

Django/utils/log.py

# Default logging for Django. This sends an email to the site admins on every
# HTTP 500 error. Depending on DEBUG, all other log records are either sent to
# the console (DEBUG=True) or discarded by mean of the NullHandler (DEBUG=False).
DEFAULT_LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'filters': {
        'require_debug_false': {
            '()': 'Django.utils.log.RequireDebugFalse',
        },
        'require_debug_true': {
            '()': 'Django.utils.log.RequireDebugTrue',
        },
    },
    'handlers': {
        'console':{
            'level': 'INFO',
            'filters': ['require_debug_true'],
            'class': 'logging.StreamHandler',
        },
        'null': {
            'class': 'Django.utils.log.NullHandler',
        },
        'mail_admins': {
            'level': 'ERROR',
            'filters': ['require_debug_false'],
            'class': 'Django.utils.log.AdminEmailHandler'
        }
    },
    'loggers': {
        'Django': {
            'handlers': ['console'],
        },
        'Django.request': {
            'handlers': ['mail_admins'],
            'level': 'ERROR',
            'propagate': False,
        },
        'py.warnings': {
            'handlers': ['console'],
        },
    }
}

したがって、デフォルトではDjango.requestにはpropagate = False。しかし、私の場合、私は'disable_existing_loggers': True

33
warvariuc

解決策は、Djangoがログを構成して自分で処理しないようにすることです。幸い、これは簡単です。settings.pyで:

LOGGING_CONFIG = None
LOGGING = {...}  # whatever you want, as you already have

import logging.config
logging.config.dictConfig(LOGGING)

UPDATE〜2015年3月:Django has clarified their documentation =:

LOGGING dictConfigのdisable_existing_loggersキーがTrueに設定されている場合、デフォルト構成のすべてのロガーが無効になります。無効化されたロガーは削除されたものと同じではありません。ロガーは引き続き存在しますが、ログに記録されたものは何も表示せずに破棄し、エントリを親ロガーに伝達することもしません。したがって、 'disable_existing_loggers'を使用する場合は十分に注意する必要があります。それはおそらくあなたが望むものではありません。代わりに、disable_existing_loggersをFalseに設定して、デフォルトロガーの一部またはすべてを再定義できます。または、LOGGING_CONFIGをNoneに設定して、ロギング構成を自分で処理することもできます。

後世と詳細について:説明?混乱のほとんどは、Djangoの貧弱な explanation of disable_existing_loggersに起因すると考えられます。これは、Trueの場合、「デフォルトの構成は完全に上書きされる」と述べています。あなた自身の答えで、あなたはそれが正しくないことを発見しました。 Djangoがすでに構成されている)既存のロガーがdisabled置き換えられないことが起こっています。

Python logging documentation はそれをよりよく説明しています(強調を追加):

disable_existing_loggers – Falseと指定した場合、この呼び出しが行われたときに存在するロガーはそのまま残されます。これは下位互換性のある方法で古い動作を可能にするため、デフォルトはTrueです。この動作は、既存のロガーまたはその祖先がロギング構成で明示的に指定されていない限り、既存のロガーをdisableすることです。

Django docsに基づいて、「自分のLOGGING構成でデフォルトを上書きすると、指定していないものはbubble up」になります。 'この期待も上回っています。期待される動作は、replace_existing_loggers(これは現実のものではありません)の行に沿ったものです。代わりに= Djangoロガーはシャットダウンされませんバブリングされます

最初にこれらのDjangoロガーのセットアップを防止する必要があります。ここではDjango docs がより役立ちます:

ロギングをまったく構成しない場合(または独自の方法でロギングを手動で構成する場合)、LOGGING_CONFIGをNoneに設定できます。これにより、構成プロセスが無効になります。

注:LOGGING_CONFIGをNoneに設定すると、設定プロセスが無効になり、それ自体はログに記録されません。設定プロセスを無効にした場合、Djangoは引き続きログを呼び出し、定義されているデフォルトのログ動作にフォールバックします。

Djangoは引き続きロガーを使用しますが、ロガーは構成によって処理されないため(無効にされるため)、これらのロガーは期待どおりにバブルアップします。上記の設定を使用した簡単なテスト:

manage.py Shell
>>> import logging
>>> logging.warning('root logger')
WARNING 2014-03-11 13:35:08,832 root root logger
>>> l = logging.getLogger('Django.request')
>>> l.warning('request logger')
WARNING 2014-03-11 13:38:22,000 Django.request request logger
>>> l.propagate, l.disabled
(1, 0)
72
JCotton

わかりましたので、動作は「正しい」ですが、予期されていません。 Django/conf/__init__.py:65

def _configure_logging(self):
    ...
    if self.LOGGING_CONFIG:
        from Django.utils.log import DEFAULT_LOGGING
        # First find the logging configuration function ...
        logging_config_path, logging_config_func_name = self.LOGGING_CONFIG.rsplit('.', 1)
        logging_config_module = importlib.import_module(logging_config_path)
        logging_config_func = getattr(logging_config_module, logging_config_func_name)

        logging_config_func(DEFAULT_LOGGING)

        if self.LOGGING:
            # Backwards-compatibility shim for #16288 fix
            compat_patch_logging_config(self.LOGGING)

            # ... then invoke it with the logging settings
            logging_config_func(self.LOGGING)

何が起こっているかというと、デフォルトのロギング設定が適用され、Django.requestロガーが作成されます。次に、私のカスタムLOGGING構成がdisable_existing_loggers = True、ただしPythonは既存のロガーを削除しませんDjango.request、しかしそれを無効にするだけです。

だから私は手動で再構成する必要がありますDjango.requestロガーを構成します。 :(

1
warvariuc

Django-2.1の場合、ロギング構成がより簡潔であることがわかりました。

$ ./manage.py Shell

>>> import logging
>>> # Grub all Django loggers
>>> loggers = [
        name for name in logging.root.manager.loggerDict 
        if 'Django' in name
    ]
>>> for each in loggers:
        logger = logging.getLogger(each)
        print(
            'Logger Name: {0}\nLogger Handlers: {1}\n'
            'Logger Propagates: {2}\n\n'.format(
                each, 
                logger.handlers, 
                logger.propagate
            )
        )

Logger Name: Django.db
Logger Handlers: []
Logger Propagates: True


Logger Name: Django.request
Logger Handlers: []
Logger Propagates: True


Logger Name: Django.template
Logger Handlers: []
Logger Propagates: True


Logger Name: Django.db.backends
Logger Handlers: []
Logger Propagates: True


Logger Name: Django.db.backends.schema
Logger Handlers: []
Logger Propagates: True


Logger Name: Django.security.csrf
Logger Handlers: []
Logger Propagates: True


Logger Name: Django
Logger Handlers: [<logging.StreamHandler object at 0x7f706d5dd780>, <Django.utils.log.AdminEmailHandler object at 0x7f706d740cf8>]
Logger Propagates: True


Logger Name: Django.contrib.gis
Logger Handlers: []
Logger Propagates: True


Logger Name: Django.contrib
Logger Handlers: []
Logger Propagates: True


Logger Name: Django.security
Logger Handlers: []
Logger Propagates: True


Logger Name: Django.server
Logger Handlers: [<logging.StreamHandler object at 0x7f706d59eba8>]
Logger Propagates: False

引用通り ドキュメント内

Django.serverを除くすべてのロガーは、ロギングを親に伝達し、ルートまで記録しますDjangoロガー。コンソールとmail_adminsハンドラーは、上記の動作を提供するためにルートロガーに接続されます。

これは propagate のドキュメントに従い、次のように述べています:

注意

ハンドラーをロガーとその祖先の1つ以上にアタッチすると、同じレコードが複数回出力される場合があります。一般に、ハンドラーを複数のロガーにアタッチする必要はありません。ロガー階層の最上位にある適切なロガーにハンドラーをアタッチするだけであれば、すべての子孫ロガーによってログに記録されたすべてのイベントが表示されます。設定はTrueのままにしておきます。一般的なシナリオは、ルートロガーにのみハンドラーを接続し、残りの部分は伝播に任せることです。

したがって、私はDjangoがログを設定しないようにしません。- sentry を使用しているため、管理者への電子メールの送信を停止したいと思い、ルートロガーを使用するように設定しましたDjango docs examples によると、consoleおよびfileハンドラ

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
            'style': '{',
        },
        'simple': {
            'format': '{levelname} {message}',
            'style': '{',
        },
    },
    'filters': {
        'require_debug_false': {
            '()': 'Django.utils.log.RequireDebugFalse',
        },
        'require_debug_true': {
            '()': 'Django.utils.log.RequireDebugTrue',
        },
    },
    'handlers': {
        'console': {
            'level': 'INFO',
            'filters': ['require_debug_true'],
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        'file': {
            'level': 'INFO',
            'filters': ['require_debug_false'],
            'class': 'logging.FileHandler',
            'filename': os.path.join(LOGGING_DIR, 'Django.log'),
            'formatter': 'verbose'
        },
    },
    'loggers': {
        'Django': {
            'handlers': ['file', 'console'],
            'level': 'INFO',
            'propagate': True,
        },
    }
}

その結果:

Logger Name: Django
Logger Handlers: [<logging.FileHandler object at 0x7f5aa0fd1cc0>, <logging.StreamHandler object at 0x7f5aa0fd1ef0>]
Logger Propagates: True

まだ本番環境ではテストされていませんが、期待どおりに動作するようです。

0
raratiru