web-dev-qa-db-fra.com

l'enregistreur django.request n'est pas propagé à la racine?

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

Si je ne commente pas les lignes commentées et que j'appelle une vue comportant le code 1/0, la trace est imprimée sur la console:

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

Mais si les lignes restent commentées, aucun retour en arrière n'est imprimé sur la console, juste:

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

Je pensais que si le consignateur Django.request n'était pas configuré, il se propagerait au consignateur racine, qui afficherait tout dans la console.

Je n'ai trouvé aucune information indiquant que Django.request est spécial.

Pourquoi ça ne marche pas?

Ici je lis:

Avant Django 1.5, le paramètre LOGGING remplaçait toujours la configuration de journalisation Django par défaut. À partir de Django 1.5, il est possible de fusionner la configuration de journalisation du projet avec les valeurs par défaut de Django. Vous pouvez donc décider d’ajouter ou de remplacer la configuration existante.

Si la clé disable_existing_loggers dans le journal LOGGING dictConfig est définie sur True (valeur par défaut), la configuration par défaut est complètement remplacée. Vous pouvez également redéfinir certains ou tous les enregistreurs en définissant disable_existing_loggers sur False.

Dans 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'],
        },
    }
}

Donc, par défaut, Django.request a propagate = False. Mais dans mon cas, j'ai 'disable_existing_loggers': True.

31
warvariuc

La solution consiste à empêcher Django de configurer la journalisation et de la gérer nous-mêmes. Heureusement c'est facile. Dans settings.py:

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

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

UPDATE ~ March 2015 : Django a clarifié leur documentation :

Si la clé disable_existing_loggers du journal LOGGING dictConfig est définie sur Sur True, tous les enregistreurs de la configuration par défaut Sont désactivés. Les enregistreurs désactivés ne sont pas les mêmes que Supprimés; l'enregistreur existera toujours, mais il éliminera en silence tout ce qui lui a été consigné, sans même propager les entrées vers un enregistreur parent. Vous devez donc faire très attention en utilisant 'Disable_existing_loggers': True; ce n’est probablement pas ce que vous voulez. À la place, vous pouvez définir disable_existing_loggers sur False et redéfinir tout ou partie des enregistreurs par défaut; ou vous pouvez définir LOGGING_CONFIG sur et gérer vous-même la configuration de la journalisation.

Pour la postérité et le détail: L'explication? La plupart de la confusion, je pense, provient de la mauvaise explication sur disable_existing_loggers de Django, qui dit que lorsque True est défini, "la configuration par défaut est complètement remplacée". Dans votre propre réponse, vous avez découvert que ce n'est pas correct. Ce qui se passe, c'est que les enregistreurs existants, déjà configurés par Django, sont disabled non remplacés.

La journalisation Python documentation l'explique mieux (accentuation ajoutée):

disable_existing_loggers - Si la valeur est False, les enregistreurs qui existent lorsque cet appel est effectué sont laissés seuls. La valeur par défaut est True, car Ceci active l'ancien comportement de manière rétrocompatible. Ce comportement Consiste à désactiver tous les enregistreurs existants, à moins qu'eux-mêmes ou leurs ancêtres Ne soient explicitement nommés dans la configuration de la journalisation.

Sur la base des documents Django, nous pensons: "écrasez les valeurs par défaut avec ma propre configuration de journalisation et tout ce que je ne spécifie pas sera bulle". J'ai également trébuché sur cette attente. Le comportement attendu est similaire à replace_existing_loggers (ce qui n’est pas une réalité). Au lieu de cela, les enregistreurs Django sont fermés pas éclatés.

En premier lieu, nous devons empêcher l’installation de ces enregistreurs Django. Ici, les Django docs sont plus utiles:

Si vous ne voulez pas du tout configurer la journalisation (ou si vous voulez manuellement Configurer la journalisation en utilisant votre propre approche), vous pouvez définir LOGGING_CONFIG Sur Aucun. Cela désactivera le processus de configuration.

Remarque: Définir LOGGING_CONFIG sur Aucun signifie uniquement que le processus de configuration Est désactivé et ne se connecte pas lui-même. Si vous désactivez le processus de configuration , Django continuera à effectuer des appels de journalisation, ramenant À son comportement de journalisation par défaut.

Django utilisera toujours ses enregistreurs, mais comme ils ne sont pas gérés (puis désactivés) par la configuration, ces enregistreurs vont bouillonner comme prévu. Un test simple avec les paramètres ci-dessus:

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)
70
JCotton

Ok, donc le comportement est "correct", mais pas prévu. 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)

En réalité, la configuration de journalisation par défaut est appliquée et le consignateur Django.request est créé. Ensuite, ma configuration LOGGING personnalisée est appliquée avec disable_existing_loggers = True, mais Python ne supprime pas le consignateur existant Django.request, mais le désactive uniquement.

Je dois donc reconfigurer manuellement l'enregistreur Django.request dans ma configuration. :(

1
warvariuc

Pour Django-2.1, la configuration de la journalisation est plus concise:

$ ./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

Comme cité dans la documentation :

Tous les enregistreurs, à l'exception de Django.server, propagent la journalisation à leurs parents, jusqu'au enregistreur racine Django. Les gestionnaires de la console et mail_admins sont attachés au consignateur racine pour fournir le comportement décrit ci-dessus.

Ceci est conforme à la documentation de propagate qui stipule que: 

Remarque

Si vous associez un gestionnaire à un enregistreur et à un ou plusieurs de ses ancêtres, Peut émettre le même enregistrement plusieurs fois. En général, vous ne devriez pas Attacher un gestionnaire à plus d’un enregistreur - si vous attachez simplement À l’enregistreur approprié qui se trouve le plus haut dans la hiérarchie de l’enregistreur, il verra tous les événements consignés par tous les enregistreurs descendants, à condition que leur paramètre de propagation soit laissé à True. Un scénario courant consiste à N'attacher des gestionnaires qu'au logger racine et à laisser la propagation S'occuper du reste.

J'ai donc décidé de ne pas empêcher Django de configurer la journalisation. Je voulais arrêter d'envoyer des courriers électroniques aux administrateurs parce que j'utilise sentry , et je viens de configurer le consignateur racine pour qu'il utilise les gestionnaires console et file, conformément aux documents de Django examples :

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

Ce qui résulte en:

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

Pas encore testé en production, mais il semble que cela fonctionnera comme prévu.

0
raratiru