web-dev-qa-db-fra.com

Connexion Python à Django

Je développe une application Django et j'essaie d'utiliser le module de journalisation de Python pour la journalisation des erreurs/traces. Idéalement, j'aimerais que différents enregistreurs soient configurés pour différentes zones du site. Jusqu'à présent, tout cela fonctionne, mais il y a une chose qui me fait me gratter la tête.

L'enregistreur racine va sur sys.stderr et j'ai configuré un autre enregistreur pour écrire dans un fichier. Ceci est dans mon fichier settings.py:

sviewlog = logging.getLogger('MyApp.views.scans')
view_log_handler = logging.FileHandler('C:\\MyApp\\logs\\scan_log.log')
view_log_handler.setLevel(logging.INFO)
view_log_handler.setFormatter(logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s'))
sviewlog.addHandler(view_log_handler)

Cela semble assez simple. Voici cependant le problème: tout ce que j'écris dans sviewlog est écrit deux fois dans le fichier journal. L'enregistreur racine ne l'imprime qu'une fois. C'est comme si addHandler () était appelé deux fois. Et lorsque je mets mon code dans un débogueur, c'est exactement ce que je vois. Le code dans settings.py est exécuté deux fois. Deux FileHandlers sont donc créés et ajoutés à la même instance de journal. Mais pourquoi? Et comment puis-je contourner cela?

Quelqu'un peut-il me dire ce qui se passe ici? J'ai essayé de déplacer le code d'instanciation du gestionnaire/enregistreur sviewlog vers le fichier où il est utilisé (car cela me semble l'endroit approprié), mais j'ai le même problème. La plupart des exemples que j'ai vus en ligne utilisent uniquement l'enregistreur racine et je préférerais disposer de plusieurs enregistreurs.

40
Jeff

Permettez-moi de répondre à ma propre question. Le problème sous-jacent est que settings.py est importé deux fois, voire plus (Voir ici ). (Je ne comprends toujours pas pourquoi. Peut-être qu'un expert de Django pourrait me l'expliquer.) Cela semble être vrai de certains autres modules également. À ce stade, je ne pense pas qu'il soit judicieux de faire des hypothèses sur le nombre de fois que settings.py sera importé. D'ailleurs, de telles hypothèses ne sont pas sûres en général. J'ai eu ce code dans des endroits autres que settings.py, et les résultats sont similaires.

Vous devez coder autour de cela. En d'autres termes, vous devez rechercher dans votre journal les gestionnaires existants avant de lui ajouter des gestionnaires supplémentaires. C'est un peu moche parce qu'il est parfaitement raisonnable d'avoir plusieurs gestionnaires - même du même type - attachés à un seul enregistreur. Il existe quelques solutions pour y remédier. L'une consiste à vérifier la propriété handlers de votre objet enregistreur. Si vous ne voulez qu'un seul gestionnaire et que votre longueur> 0, ne l'ajoutez pas. Personnellement, je n'aime pas cette solution car elle est compliquée par davantage de gestionnaires.

Je préfère quelque chose comme ça (merci à Thomas Guettler):

# file logconfig.py
if not hasattr(logging, "set_up_done"):
    logging.set_up_done=False

def set_up(myhome):
    if logging.set_up_done:
        return
    # set up your logging here
    # ...
    logging.set_up_done=True

Je dois dire que je souhaite que Django importe plusieurs fois que les importations de settings.py soient mieux documentées. Et j'imagine que ma configuration est en quelque sorte à l'origine de cette importation multiple, mais j'ai du mal à comprendre ce qui cause le problème et pourquoi. Peut-être que je ne pouvais tout simplement pas trouver cela dans leurs documents, mais je penserais que c'est le genre de chose pour laquelle vous devez avertir vos utilisateurs.

30
Jeff

Depuis la version 1.3, Django utilise la journalisation Python standard, configurée avec le paramètre LOGGING (documenté ici: 1.3 , dev ).

Référence de journalisation Django: 1.3 , dev .

24
Tobu

Difficile de commenter votre cas particulier. Si settings.py est exécuté deux fois, il est normal que vous obteniez deux lignes pour chaque journal envoyé.

Nous avions le même problème et nous avons donc prévu dans nos projets un module dédié à la journalisation. Ce module a un motif "module singleton", de sorte que nous n'exécutons le code intéressant qu'une seule fois.

Cela ressemble à ceci:

def init_logging():
    stdoutHandler = logging.StreamHandler( sys.stdout )
    stdoutHandler.setLevel( DEBUG )
    stdoutHandler.setFormatter( logging.Formatter( LOG_FORMAT_WITH_TIME ) )
    logging.getLogger( LOG_AREA1 ).addHandler( stdoutHandler )

logInitDone=False #global variable controlling the singleton.
if not logInitDone:
    logInitDone = True
    init_logging()

Importer le fichier log.py pour la première fois configurera correctement la journalisation.

14
Philippe F

Réactiver un ancien thread, mais je rencontrais des messages en double lorsque j'utilisais la journalisation Django 1.3 Python au format dictConfig

Le disable_existing_loggers supprime le problème de gestionnaire/de journalisation en double avec plusieurs chargements de settings.py, mais vous pouvez toujours obtenir des messages de journal en double si vous ne spécifiez pas le booléen propagate de façon appropriée sur la logger spécifique. À savoir, assurez-vous de définir propagate=False pour les enregistreurs enfants. Par exemple.,

'loggers': {
    'Django': {
        'handlers':['null'],
        'propagate': True,
        'level':'INFO',
    },
    'Django.request': {
        'handlers': ['console'],
        'level': 'ERROR',
        'propagate': False,
    },
    'project': {
        'handlers': ['console', 'project-log-file'],
        'level': 'DEBUG',
        'propagate': True,
    },
    'project.customapp': {
        'handlers': ['console', 'customapp-log-file'],
        'level': 'DEBUG',
        'propagate': False,
    },
}

Ici, project.customapp définit propagate=False de sorte qu'il ne soit pas attrapé par le logger project. Les Django Logging Docs sont excellents, comme toujours.

11
A Lee

Pour répondre à la question sur "Pourquoi Django importe-t-il plusieurs fois les paramètres.py"?.

En fait, il est importé deux fois (passez le premier bloc de code pour entrer directement dans le fichier mais donnez-en une bonne lecture si vous avez le temps):

http://blog.dscpl.com.au/2010/03/improved-wsgi-script-for-use-with.html

PS - Désolé de faire revivre un vieux fil.

6
Matt Briançon

Vous pouvez contourner votre problème en vérifiant le nombre de gestionnaires lorsque vous effectuez votre init.

def init_logging():
    stdoutHandler = logging.StreamHandler( sys.stdout )
    stdoutHandler.setLevel( DEBUG )
    stdoutHandler.setFormatter( logging.Formatter( LOG_FORMAT_WITH_TIME ) )
    logger = logging.getLogger( LOG_AREA1 )
    if len(logger.handlers) < 1:
        logger.addHandler( stdoutHandler )

Je ne pense pas que ce soit un excellent moyen de le gérer. Personnellement, pour vous connecter à Django avec le module de journalisation python, je crée un consignateur dans views.py pour chaque application qui m'intéresse, puis je récupère le consignateur dans chaque fonction d'affichage.

from Django.http import HttpResponse
from magic import makeLogger
from magic import getLogger

makeLogger('myLogName', '/path/to/myLogName.log')
def testLogger(request):
    logger = getLogger('myLogName')
    logger.debug('this worked')
    return HttpResponse('TEXT, HTML or WHATEVER')

Ceci est un très bon article sur le débogage de Django et couvre une partie de la journalisation: http://simonwillison.net/2008/May/22/debugging/

4
tedshroyer

Pour répondre à la question sur "Pourquoi Django importe-t-il plusieurs fois les paramètres.py"? 

Vous exécutez probablement un serveur Web multiprocessus/multithread qui crée plusieurs sous-interprètes python, chacun de ceux-ci important le code de votre application Django.

Testez cela sur le serveur de test Django et vous devriez voir que les paramètres ne sont pas importés plusieurs fois.

Il y a quelque temps, j'avais conçu mon singleton Nice (version plus précise de l'idiome de python borg) avec ma première application Django/Apache.

3
rodrigue

Vous pouvez également utiliser un middleware à exécution unique pour obtenir un effet similaire, sans les variables privées. Notez que cela ne configurera que la journalisation pour les requêtes Web - vous devrez trouver une solution différente si vous souhaitez vous connecter à votre shell ou à une commande.

from Django.conf import settings
from Django.core.exceptions import MiddlewareNotUsed
import logging
import logging.handlers
import logging.config

__all__ = ('LoggingConfigMiddleware',)


class LoggingConfigMiddleware:
    def __init__(self):
        '''Initialise the logging setup from settings, called on first request.'''
        if hasattr(settings, 'LOGGING'):
            logging.config.dictConfig(settings.LOGGING)
        Elif getattr(settings, 'DEBUG', False):
            print 'No logging configured.'
        raise MiddlewareNotUsed('Logging setup only.')
3
Brad Shuttleworth

Pourquoi utiliser Python Logger au lieu de Django-Logging? Faites un essai, cela pourrait simplement résoudre votre problème. 

http://code.google.com/p/Django-logging/wiki/Overview

Pour le moment, il ne permet que de visualiser le consignateur racine, mais vous pouvez certainement écrire dans plusieurs enregistreurs.

1
Sergey Golovchenko

Pour ajouter à A Lee post, la documentation de journalisation python dit ceci à propos de propagate:

Logger.propagate

Si la valeur est false, les messages de journalisation ne sont pas transmis par cet enregistreur ou ses enregistreurs enfants aux gestionnaires des enregistreurs de niveau supérieur (ancêtres). Le constructeur définit cet attribut sur 1.

Cela signifie que si propagate == False, alors enregistreur enfant ne transmettra PAS le message de journalisation à son enregistreur parent

0
aisbaa

Une manière hack, mais vous pouvez essayer de mettre le code de journalisation dans un fichier admin.py. Il est censé être importé une seule fois.

Alternativement pouvez-vous d'abord vérifier si le journal MyApp.views.scans existe? S'il existe (peut-être qu'une erreur est générée), vous pouvez simplement ignorer la création (et donc ne pas ajouter le gestionnaire à nouveau). Une façon plus propre mais je n'ai pas encore essayé.

De plus, il doit y avoir un endroit plus approprié pour mettre ce code (__init__.py?). settings.py est pour les paramètres.

0
muhuk