it-swarm.com.de

Lazy Logger Message String Auswertung

Ich verwende das Standard-Protokollierungsmodul python in meiner Anwendung python:

 Import Logging 
 Logging.basicConfig (Level = Logging.INFO) 
 Logger = Logging.getLogger ("Log") 
 während True: 
 logger.debug ('Dumme Logmeldung "+' '.join ([str (i) für i in range (20)])) 
 # Tu etwas 

Das Problem ist, dass, obwohl die Debugstufe nicht aktiviert ist, diese dumme Protokollnachricht bei jeder Schleifeniteration ausgewertet wird, was die Leistung stark beeinträchtigt.

Gibt es dafür eine Lösung?

In C++ haben wir das Paket log4cxx, Das Makros wie diese bereitstellt:
LOG4CXX_DEBUG(logger, messasage)
Das bewertet effektiv zu

 if (log4cxx :: debugEnabled (logger)) {
 log4cxx.log (logger, log4cxx :: LOG4CXX_DEBUG, message) 
} 

Aber da es in Python (AFAIK) keine Makros gibt, ob es eine effiziente Möglichkeit gibt, die Protokollierung durchzuführen?

59
Zaar Hai

Das Protokollierungsmodul bietet bereits teilweise Unterstützung für das, was Sie tun möchten. Mach das:

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

... an Stelle von:

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

Das Protokollierungsmodul ist intelligent genug, um nicht die vollständige Protokollnachricht zu erzeugen, es sei denn, die Nachricht wird tatsächlich irgendwo protokolliert.

Um diese Funktion auf Ihre spezifische Anfrage anzuwenden, können Sie eine Lazyjoin-Klasse erstellen.

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

Verwenden Sie es so (beachten Sie die Verwendung eines Generatorausdrucks, der zur Faulheit beiträgt):

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

Hier ist eine Demo, die zeigt, dass dies funktioniert.

>>> 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())
>>>

In der Demo schlug der Aufruf von logger.info () den Assertionsfehler, während logger.debug () nicht so weit gekommen ist.

67
Shane Hathaway

Natürlich ist Folgendes nicht so effizient wie ein Makro:

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

aber einfach, wertet faul aus und ist 4-mal schneller als die akzeptierte Antwort :

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

Siehe benchmark-src für mein Setup.

34
schnittstabil
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

Wenn Sie das Skript ausführen, werden Sie feststellen, dass der erste logger.debug-Befehl keine 20 Sekunden für die Ausführung benötigt. Dies zeigt, dass das Argument nicht ausgewertet wird, wenn die Protokollierungsstufe unterhalb der festgelegten Stufe liegt.

24
unutbu

Wie Shane darauf hinweist

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

... an Stelle von:

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

spart Zeit, indem Sie nur die String-Formatierung durchführen, wenn die Nachricht tatsächlich protokolliert wird.

Dies löst das Problem jedoch nicht vollständig, da Sie die Werte möglicherweise vorbereiten müssen, um sie in die Zeichenfolge zu formatieren, z.

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

In diesem Fall werden obj.get_a() und obj.get_b() berechnet even , falls keine Protokollierung erfolgt.

Eine Lösung dafür wäre die Verwendung von Lambda-Funktionen. Dies erfordert jedoch einige zusätzliche Maschinen:

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

... dann können Sie sich mit folgendem Protokoll einloggen:

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

In diesem Fall wird die Lambda-Funktion only aufgerufen, wenn log.debug sich für die Formatierung entscheidet und daher die __str__-Methode aufruft.

Doch der Overhead dieser Lösung kann den Nutzen durchaus übertreffen :-) Aber zumindest theoretisch ist es möglich, faul zu protokollieren.

13
Pierre-Antoine

Ich präsentiere, Lazyfy:

class Lazyfy(object):
    __slots__ = 'action', 'value'

    def __init__(self, action, *value):
        self.action = action
        self.value = value

    def __str__(self):
        return self.action(*self.value)

Verwendungszweck:

from pprint import pformat
log.debug("big_result: %s", Lazyfy(pformat, big_result))
log.debug( "x y z: %s", Lazyfy( lambda x, y, z: ' ,'.join( [x, y, z] ), '1', '2', '3' ) )

Das ursprüngliche Beispiel:

logger.info('Stupid log message %s', Lazyfy(lambda: ' '.join((str(i) for i in range(20)))))

Wie Sie sehen, deckt dies auch die andere Antwort ab, die die Lambda-Funktion verwendet, benötigt jedoch mehr Speicherplatz mit der Variablen value und der Erweiterung. Es spart jedoch mehr Speicher mit: Verwendung von __slots__?

Schließlich ist die effizienteste Lösung immer noch die folgende, wie eine andere Antwort vorschlägt:

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

Wenn Sie nur vom Zugriff auf globale Statusattribute abhängig sind, können Sie eine python -Klasse instanziieren und mithilfe der __str__ -Methode inaktivieren:

class get_lazy_debug(object):
    def __repr__(self):
        return ' '.join(
                str(i) for i in range(20)
            )

# Allows to pass get_lazy_debug as a function parameter without 
# evaluating/creating its string!
get_lazy_debug = get_lazy_debug()

logger.debug( 'Stupid log message', get_lazy_debug )

Verbunden:

  1. Bedingt bewertete Debug-Anweisungen in Python
  2. Was sind Metaklassen in Python?
0
user