Configuration élégante de la journalisation Python dans Django

101

Je n'ai pas encore trouvé un moyen de configurer la journalisation Python avec Django qui me satisfait. Mes exigences sont assez simples:

  • Différents gestionnaires de journaux pour différents événements - c'est-à-dire que je veux pouvoir me connecter à différents fichiers
  • Accès facile aux enregistreurs dans mes modules. Le module devrait pouvoir trouver son enregistreur avec peu d'effort.
  • Devrait être facilement applicable aux modules de ligne de commande. Certaines parties du système sont des processus de ligne de commande ou de démon autonomes. La journalisation doit être facilement utilisable avec ces modules.

Ma configuration actuelle consiste à utiliser un logging.conffichier et une journalisation de configuration dans chaque module à partir duquel je me connecte. Ça ne va pas.

Avez-vous une configuration de journalisation que vous aimez? Veuillez le détailler: comment configurez-vous la configuration (utilisez-vous logging.confou configurez-la dans le code), où / quand lancez-vous les enregistreurs, et comment y avez-vous accès dans vos modules, etc.

Parand
la source
1
Vous trouverez peut-être utile le screencast suivant - ericholscher.com/blog/2008/aug/29/… . De plus, un meilleur support de la journalisation dans Django a été proposé par Simon Willison (voir simonwillison.net/2009/Sep/28/ponies ).
Dominic Rodger
@Dominic Rodger - Vous pouvez déjà effectuer une journalisation flexible des applications dans Django, la proposition de Simon principalement pour faciliter la journalisation dans les internes de Django. Des travaux sont en cours en Python pour ajouter une configuration basée sur un dictionnaire à la journalisation Python, dont Django peut bénéficier.
Vinay Sajip

Réponses:

57

Le meilleur moyen que j'ai trouvé jusqu'à présent est d'initialiser la configuration de la journalisation dans settings.py - nulle part ailleurs. Vous pouvez utiliser un fichier de configuration ou le faire par programmation étape par étape - cela dépend uniquement de vos besoins. L'essentiel est que j'ajoute généralement les gestionnaires que je veux à l'enregistreur racine, en utilisant des niveaux et parfois en journalisant.Filtres pour obtenir les événements que je veux dans les fichiers appropriés, la console, les syslogs, etc. aussi, mais ce n'est généralement pas nécessaire dans mon expérience.

Dans chaque module, je définis un enregistreur en utilisant

logger = logging.getLogger(__name__)

et utilisez-le pour enregistrer les événements dans le module (et, si je veux différencier davantage), utilisez un enregistreur qui est un enfant de l'enregistreur créé ci-dessus.

Si mon application va être potentiellement utilisée dans un site qui ne configure pas la journalisation dans settings.py, je définis un NullHandler quelque part comme suit:

#someutils.py

class NullHandler(logging.Handler):
    def emit(self, record):
        pass

null_handler = NullHandler()

et assurez-vous qu'une instance de celui-ci est ajoutée à tous les enregistreurs créés dans les modules de mes applications qui utilisent la journalisation. (Remarque: NullHandler est déjà dans le package de journalisation pour Python 3.1, et sera dans Python 2.7.) Donc:

logger = logging.getLogger(__name__)
logger.addHandler(someutils.null_handler)

Ceci est fait pour garantir que vos modules jouent bien dans un site qui ne configure pas la journalisation dans settings.py, et que vous n'obtenez aucun message ennuyeux "Aucun gestionnaire n'a pu être trouvé pour le logger XYZ" (qui sont des avertissements sur des journalisation mal configurée).

Cela répond à vos exigences déclarées:

  • Vous pouvez configurer différents gestionnaires de journaux pour différents événements, comme vous le faites actuellement.
  • Accès facile aux enregistreurs dans vos modules - utilisation getLogger(__name__).
  • Facilement applicable aux modules de ligne de commande - ils importent également settings.py.

Mise à jour: Notez qu'à partir de la version 1.3, Django intègre désormais la prise en charge de la journalisation .

Vinay Sajip
la source
Cela ne nécessitera-t-il pas que chaque module ait un gestionnaire défini dans la configuration (vous ne pouvez pas utiliser un gestionnaire pour que foo gère foo.bar)? Consultez la conversation que nous avons eue il y a des années sur groups.google.com/group/comp.lang.python/browse_thread/thread/…
andrew cooke
1
@andrew cooke: vous pouvez utiliser un gestionnaire pour foogérer les événements enregistrés foo.bar. Ré. ce thread - fileConfig et dictConfig ont maintenant des options pour empêcher la désactivation des anciens enregistreurs. Voir ce numéro: bugs.python.org/issue3136 , qui est arrivé quelques mois après votre problème bugs.python.org/issue2697 - de toute façon, il a été réglé depuis juin 2008.
Vinay Sajip
ne serait-il pas préférable de faire d' logger = someutils.getLogger(__name__)someutils.getLoggerretourne le logger logging.getLoggeravec un null_handler déjà ajouté?
7yl4r
1
@ 7yl4r Vous n'avez pas besoin que chaque enregistreur ait un NullHandlerajout - généralement juste l'enregistreur de niveau supérieur pour votre hiérarchie de paquets. Ce serait donc exagéré, OMI.
Vinay Sajip
122

Je sais que c'est déjà une réponse résolue, mais selon django> = 1.3, il y a un nouveau paramètre de journalisation.

Passer de l'ancien au nouveau n'est pas automatique, alors j'ai pensé l'écrire ici.

Et bien sûr, consultez la documentation django pour en savoir plus.

Ceci est la configuration de base, créée par défaut avec django-admin createproject v1.3 - le kilométrage peut changer avec les dernières versions de django:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'mail_admins': {
            'level': 'ERROR',
            'class': 'django.utils.log.AdminEmailHandler',
        }
    },
    'loggers': {
        'django.request': {
            'handlers': ['mail_admins'],
            'level': 'ERROR',
            'propagate': True,
        }
    }
}

Cette structure est basée sur la journalisation Python standard dictConfig , qui dicte les blocs suivants:

  • formatters - la valeur correspondante sera un dict dans lequel chaque clé est un identifiant de formateur et chaque valeur est un dict décrivant comment configurer l'instance de formateur correspondante.
  • filters - la valeur correspondante sera un dict dans lequel chaque clé est un identifiant de filtre et chaque valeur est un dict décrivant comment configurer l'instance de filtre correspondante.
  • handlers- la valeur correspondante sera un dict dans lequel chaque clé est un identifiant de gestionnaire et chaque valeur est un dict décrivant comment configurer l'instance de gestionnaire correspondante. Chaque gestionnaire possède les clés suivantes:

    • class(obligatoire). Il s'agit du nom qualifié complet de la classe de gestionnaire.
    • level(optionnel). Le niveau du gestionnaire.
    • formatter(optionnel). L'identifiant du formateur pour ce gestionnaire.
    • filters(optionnel). Une liste des identifiants des filtres pour ce gestionnaire.

Je fais généralement au moins ceci:

  • ajouter un fichier .log
  • configurer mes applications pour écrire dans ce journal

Ce qui se traduit par:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
        },
        'simple': {
            'format': '%(levelname)s %(message)s'
        },
    },
    'filters': {
        'require_debug_false': {
            '()': 'django.utils.log.RequireDebugFalse'
        }
    },
    'handlers': {
        'null': {
            'level':'DEBUG',
            'class':'django.utils.log.NullHandler',
        },
        'console':{
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        # I always add this handler to facilitate separating loggings
        'log_file':{
            'level': 'DEBUG',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': os.path.join(VAR_ROOT, 'logs/django.log'),
            'maxBytes': '16777216', # 16megabytes
            'formatter': 'verbose'
        },
        'mail_admins': {
            'level': 'ERROR',
            'filters': ['require_debug_false'],
            'class': 'django.utils.log.AdminEmailHandler',
            'include_html': True,
        }
    },
    'loggers': {
        'django.request': {
            'handlers': ['mail_admins'],
            'level': 'ERROR',
            'propagate': True,
        },
        'apps': { # I keep all my of apps under 'apps' folder, but you can also add them one by one, and this depends on how your virtualenv/paths are set
            'handlers': ['log_file'],
            'level': 'INFO',
            'propagate': True,
        },
    },
    # you can also shortcut 'loggers' and just configure logging for EVERYTHING at once
    'root': {
        'handlers': ['console', 'mail_admins'],
        'level': 'INFO'
    },
}

Éditer

Les exceptions de demande sont désormais toujours enregistrées et le ticket n ° 16288 :

J'ai mis à jour l'exemple de configuration ci-dessus pour inclure explicitement le filtre correct pour mail_admins afin que, par défaut, les e-mails ne soient pas envoyés lorsque le débogage a la valeur True.

Vous devez ajouter un filtre:

'filters': {
    'require_debug_false': {
        '()': 'django.utils.log.RequireDebugFalse'
    }
},

et appliquez-le au gestionnaire mail_admins:

    'mail_admins': {
        'level': 'ERROR',
        'filters': ['require_debug_false'],
        'class': 'django.utils.log.AdminEmailHandler',
        'include_html': True,
    }

Sinon, django.core.handers.base.handle_uncaught_exceptionne transmet pas les erreurs à l'enregistreur 'django.request' si settings.DEBUG est True.

Si vous ne le faites pas dans Django 1.5, vous obtiendrez un

DeprecationAvertissement: Vous n'avez pas de filtre défini sur le gestionnaire de journalisation 'mail_admins': ajout d'un filtre implicite debug-false-only

mais les choses fonctionneront toujours correctement LES DEUX dans django 1.4 et django 1.5.

** fin de modification **

Cette conf est fortement inspirée de l'exemple de conf dans la doc django, mais en ajoutant la partie du fichier journal.

Je fais souvent aussi ce qui suit:

LOG_LEVEL = 'DEBUG' if DEBUG else 'INFO'

...
    'level': LOG_LEVEL
...

Ensuite, dans mon code python, j'ajoute toujours un NullHandler au cas où aucune configuration de journalisation n'est définie. Cela évite les avertissements pour aucun gestionnaire spécifié. Particulièrement utile pour les bibliothèques qui ne sont pas nécessairement appelées uniquement dans Django ( ref )

import logging
# Get an instance of a logger
logger = logging.getLogger(__name__)
class NullHandler(logging.Handler): #exists in python 3.1
    def emit(self, record):
        pass
nullhandler = logger.addHandler(NullHandler())

# here you can also add some local logger should you want: to stdout with streamhandler, or to a local file...

[...]

logger.warning('etc.etc.')

J'espère que cela t'aides!

Stefano
la source
Stefano, merci beaucoup pour la réponse détaillée, très utile. Cela pourrait valoir la peine de passer à la version 1.3.
Parand
Parand, cela vaut vraiment la peine (à mon humble avis!) De passer à django 1.3, bien qu'il y ait quelques points à prendre en compte pour une transition en douceur - ouvrez une nouvelle question SO si vous avez des ennuis ;-)
Stefano
au fait: j'utilise toujours ce genre de paramètres et le journal des fichiers, mais je suis passé à la sentinelle pour la production!
Stefano
@clime bien j'ai essayé de l'expliquer dans la réponse elle-même: au cas où aucune configuration de journalisation n'est définie. Cela évite les avertissements pour aucun gestionnaire spécifié. Particulièrement utile pour les bibliothèques qui ne sont pas nécessairement appelées uniquement dans Django (ref)
Stefano
Je ne vois pas comment vous utilisez cette définition: 'null': {'level': 'DEBUG', 'class': 'django.utils.log.NullHandler',}
clime
9

Nous initialisons la journalisation au niveau supérieur urls.pyà l'aide d'un logging.inifichier.

L'emplacement du logging.iniest fourni dans settings.py, mais c'est tout.

Chaque module fait alors

logger = logging.getLogger(__name__)

Pour distinguer les instances de test, de développement et de production, nous avons différents fichiers logging.ini. Pour la plupart, nous avons un "journal de console" qui va à stderr avec des erreurs uniquement. Nous avons un "journal d'application" qui utilise un fichier journal de roulement régulier qui va dans un répertoire de journaux.

S.Lott
la source
J'ai fini par utiliser ceci, sauf l'initialisation dans settings.py au lieu de urls.py
Parand
Comment utilisez-vous les paramètres de settings.py dans votre fichier logging.ini? Par exemple, j'ai besoin du paramètre BASE_DIR pour pouvoir lui indiquer où stocker mes fichiers journaux.
slypete
@slypete: Nous n'utilisons pas de paramètres dans le logging.ini. La journalisation étant largement indépendante, nous n'utilisons aucun des paramètres Django. Oui, il est possible de répéter quelque chose. Non, cela ne fait pas beaucoup de différence pratique.
S.Lott
Dans ce cas, je voudrais un fichier logging.ini séparé dans chaque installation de mon application.
slypete
@slypete: Vous disposez d'un settings.py pour chaque installation. Vous disposez également d'un logging.ini pour chaque installation. De plus, vous avez probablement un fichier de configuration Apache pour chaque installation. Plus un fichier d'interface wsgi. Je ne suis pas sûr de votre argument.
S.Lott
6

J'utilise actuellement un système de journalisation, que j'ai créé moi-même. Il utilise le format CSV pour la journalisation.

django-csvlog

Ce projet n'a toujours pas de documentation complète, mais je travaille dessus.

Oduvan
la source