81 votes

Évaluation de la chaîne de messages de l'enregistreur paresseux

Je suis en utilisant le standard de python module de journalisation dans mon application en python:

l'importation de journalisation
la journalisation.basicConfig(level=logging.INFO)
logger = logging.getLogger("log")
while True:
 enregistreur.debug('Stupide message du journal" + ' '.join([str(i) for i in range(20)]) )
 # Faire quelque chose

Le problème est que, bien que le niveau de debug n'est pas activer, ce stupide message de log est évalué en fonction de chaque itération de boucle, qui nuit à la performance de mal.

Est-il une solution pour cela?

En C++, nous avons log4cxx package qui fournit des macros comme ceci:
LOG4CXX_DEBUG(logger, messasage)
C'est effectivement évaluée à

si (log4cxx::debugEnabled(logger)) {
 log4cxx.journal(logger,log4cxx::LOG4CXX_DEBUG, message)
}

Mais depuis il n'y a pas de macros en Python (autant que je sache), si il y a un moyen efficace de faire de l'exploitation forestière?

104voto

Shane Hathaway Points 823

Le module de journalisation dispose déjà d'une prise en charge partielle de ce que vous voulez faire. Faire cela:

log.debug("Some message: a=%s b=%s", a, b)

... au lieu de cela:

log.debug("Some message: a=%s b=%s" % (a, b))

Le module de journalisation est assez intelligent pour ne pas produire le journal complet de message si le message est enregistré quelque part.

Pour appliquer cette fonction à votre demande spécifique, vous pouvez créer un lazyjoin classe.

class lazyjoin:
    def __init__(self, s, items):
        self.s = s
        self.items = items
    def __str__(self):
        return self.s.join(self.items)

L'utiliser comme ceci (notez l'utilisation d'un générateur d'expression, en ajoutant à la paresse):

logger.info('Stupid log message %s', lazyjoin(' ', (str(i) for i in range(20))))

Voici une démo qui montre que cela fonctionne.

>>> import logging
>>> logging.basicConfig(level=logging.INFO)
>>> logger = logging.getLogger("log")
>>> class DoNotStr:
...     def __str__(self):
...         raise AssertionError("the code should not have called this")
... 
>>> logger.info('Message %s', DoNotStr())
Traceback (most recent call last):
...
AssertionError: the code should not have called this
>>> logger.debug('Message %s', DoNotStr())
>>>

Dans la démo, Le logger.info() l'appel a frappé l'erreur d'assertion, alors que l'enregistreur.debug() n'était pas loin.

40voto

schnittstabil Points 73

Bien entendu, ce qui suit n’est pas aussi efficace qu’une macro:

 if logger.isEnabledFor(logging.DEBUG):
    logger.debug(
        'Stupid log message ' + ' '.join([str(i) for i in range(20)])
    )
 

mais simple et 4 fois plus rapide que cela:

 class lazyjoin:
    def __init__(self, s, items):
        self.s = s
        self.items = items

    def __str__(self):
        return self.s.join(self.items)

logger.debug(
    'Stupid log message %s', lazyjoin(' ', (str(i) for i in range(20)))
)
 

Voir benchmark-src pour ma configuration.

30voto

unutbu Points 222216
 import logging
import time

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("log")

class Lazy(object):
    def __init__(self,func):
        self.func=func
    def __str__(self):
        return self.func()

logger.debug(Lazy(lambda: time.sleep(20)))

logger.info(Lazy(lambda: "Stupid log message " + ' '.join([str(i) for i in range(20)])))
# INFO:log:Stupid log message 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 

Si vous exécutez le script, vous remarquerez que l'exécution de la première commande logger.debug ne prend pas 20 secondes. Cela montre que l'argument n'est pas évalué lorsque le niveau de consignation est inférieur au niveau défini.

13voto

Pierre-Antoine Points 116

Comme Shane points, en utilisant

log.debug("Some message: a=%s b=%s", a, b)

... au lieu de cela:

log.debug("Some message: a=%s b=%s" % (a, b))

permet de gagner du temps que de l'exécution de la mise en forme de chaîne si le message est effectivement connecté.

Ce n'est pas de résoudre complètement le problème, même si, comme vous pourriez avoir à pré-traiter les valeurs de format dans la chaîne de caractères, tels que:

log.debug("Some message: a=%s b=%s", foo.get_a(), foo.get_b())

Dans ce cas, obj.get_a() et obj.get_b() seront calculés de la même si aucun enregistrement n'arrive.

Une solution serait d'utiliser des fonctions lambda, mais cela nécessite quelques machines supplémentaire:

class lazy_log_debug(object):
    def __init__(self, func):
        self.func = func
        logging.debug("%s", self)
    def __str__(self):
        return self.func()

... alors vous pouvez vous connecter avec ce qui suit:

lazy_log_debug(lambda: "Some message: a=%s b=%s" % (foo.get_a(), foo.get_b()))

Dans ce cas, la fonction lambda va seulement être appelée que si log.debug décide d'effectuer la mise en forme, d'où l'appel de la __str__ méthode.

Rappelez-vous: la surcharge, la solution peut très bien dépasser le bénéfice :-) Mais au moins en théorie, il est possible de le faire parfaitement paresseux de l'exploitation forestière.

1voto

Wolph Points 28062

Si vous voulez vraiment désactiver la journalisation du débogage partout, vous pouvez faire quelque chose comme ceci:

 logger.debug = lambda *a, **kw: None
 

C’est aussi rapide que vous pouvez l’obtenir sans supprimer réellement les déclarations.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X