Protokollnachrichten, die bei Python-Protokollierung zweimal angezeigt werden

100

Ich verwende die Python-Protokollierung und aus irgendeinem Grund werden alle meine Nachrichten zweimal angezeigt.

Ich habe ein Modul zum Konfigurieren der Protokollierung:

# BUG: It's outputting logging messages twice - not sure why - it's not the propagate setting.
def configure_logging(self, logging_file):
    self.logger = logging.getLogger("my_logger")
    self.logger.setLevel(logging.DEBUG)
    self.logger.propagate = 0
    # Format for our loglines
    formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
    # Setup console logging
    ch = logging.StreamHandler()
    ch.setLevel(logging.DEBUG)
    ch.setFormatter(formatter)
    self.logger.addHandler(ch)
    # Setup file logging as well
    fh = logging.FileHandler(LOG_FILENAME)
    fh.setLevel(logging.DEBUG)
    fh.setFormatter(formatter)
    self.logger.addHandler(fh)

Später rufe ich diese Methode auf, um die Protokollierung zu konfigurieren:

if __name__ == '__main__':
    tom = Boy()
    tom.configure_logging(LOG_FILENAME)
    tom.buy_ham()

Und dann würde ich innerhalb des buy_ham-Moduls Folgendes nennen:

self.logger.info('Successfully able to write to %s' % path)

Aus irgendeinem Grund werden alle Nachrichten zweimal angezeigt. Ich habe einen der Stream-Handler auskommentiert, immer noch dasselbe. Etwas seltsam, nicht sicher, warum das passiert ... lol. Vorausgesetzt, ich habe etwas Offensichtliches verpasst.

Prost, Victor

victorhooi
quelle
1
Sind Sie sicher, dass configure_logging()nicht zweimal aufgerufen wird (zB auch vom Konstruktor)? Wird nur eine Instanz von Boy () erstellt?
Jacek Konieczny
1
Die Verwendung von self.logger.handlers = [ch]stattdessen würde dieses Problem lösen, obwohl es am besten ist, nur sicherzustellen, dass Sie diesen Code nicht zweimal ausführen, indem Sie ihn beispielsweise if not self.loggerzu Beginn verwenden.
Ninjakannon

Antworten:

133

Sie rufen configure_loggingzweimal auf (möglicherweise in der __init__Methode von Boy): getLoggerGibt dasselbe Objekt zurück, addHandlerprüft jedoch nicht, ob dem Logger bereits ein ähnlicher Handler hinzugefügt wurde.

Versuchen Sie, Aufrufe dieser Methode zu verfolgen und eine davon zu entfernen. Oder richten Sie ein Flag ein logging_initialized, das Falsein der __init__Methode von initialisiert wurde, Boyund ändern Sie es configure_logging, um nichts zu tun, wenn dies der Fall logging_initializedist True, und setzen Sie es auf, Truenachdem Sie den Logger initialisiert haben.

Wenn Ihr Programm mehrere BoyInstanzen erstellt, müssen Sie die Vorgehensweise mit einer globalen configure_loggingFunktion ändern, die die Handler hinzufügt, und die Boy.configure_loggingMethode, die nur das self.loggerAttribut initialisiert .

Eine andere Möglichkeit, dies zu lösen, besteht darin, das Handler-Attribut Ihres Loggers zu überprüfen:

logger = logging.getLogger('my_logger')
if not logger.handlers:
    # create the handlers and call logger.addHandler(logging_handler)
Trage Alex
quelle
1
Ja, du hattest Recht - dumm mich. Ich habe es sowohl in init als auch explizit anderswo genannt. Lol. Danke =).
Victorhooi
Vielen Dank. Ihre Lösung hat mich heute gerettet.
ForeverLearner
1
In meinem Fall erschienen sie 6 Mal. Ich hatte das vermutet, weil ich in 6 oop-Klassen den gleichen Logger-Typ deklariert habe
deklariert habe
5
Ich möchte hier meine Erfahrungen mitteilen: Für eine von mir entwickelte Flask-Anwendung wurden die Protokollmeldungen MEHR ALS ZWEIMAL angezeigt. Ich würde sagen, dass sie in der Protokolldatei inkrementiert wurden, da beim Laden der Anwendung und der Module die verwendete loggerVariable nicht die aus einer meiner Klassen instanziierte war, sondern die loggerim Python3-Cache vorhandene Variable und der Handler wurde alle 60 Sekunden von einem von mir konfigurierten AppScheduler hinzugefügt. Dies if not logger.handlersist also ein ziemlich kluger Weg, um diese Art von Phänomen zu vermeiden. Danke für die Lösung, Genosse :)!
ivanleoncz
2
Ich sehe dieses Problem in meiner Flask-App. Diese Lösung hat das Problem für Protokollnachrichten behoben, die in der Hauptflaschen-App generiert wurden, aber meine App funktioniert in einem Bibliotheksmodul, und diese Nachrichten aus dieser Bibliothek werden immer noch mehrmals protokolliert. Ich weiß nicht, wie ich das beheben soll.
Cas
24

Wenn Sie dieses Problem sehen und Sie hinzufügen nicht den Handler zweimal dann sehen abarnert Antwort hier

Aus den Dokumenten :

Hinweis: Wenn Sie einen Handler an einen Logger und einen oder mehrere seiner Vorfahren anhängen, wird möglicherweise derselbe Datensatz mehrmals ausgegeben. Im Allgemeinen sollten Sie keinen Handler an mehr als einen Logger anhängen müssen. Wenn Sie ihn nur an den entsprechenden Logger anhängen, der in der Loggerhierarchie am höchsten ist, werden alle von allen untergeordneten Loggern protokollierten Ereignisse angezeigt, sofern sich diese verbreiten Die Einstellung bleibt auf True gesetzt. Ein häufiges Szenario besteht darin, Handler nur an den Root-Logger anzuhängen und die Weitergabe für den Rest zu übernehmen.

Wenn Sie also einen benutzerdefinierten Handler für "Test" möchten und nicht möchten, dass seine Nachrichten auch an den Root-Handler gesendet werden, ist die Antwort einfach: Deaktivieren Sie das Propagierungsflag:

logger.propagate = False

wahnsinniger Igel
quelle
1
Das ist die beste Antwort. Es passte nicht zum Zweck des Posters (logischer Fehler bei der Codierung), aber meistens sollte dies der Fall sein.
Artem
Bravo. Dies ist die eigentliche Ursache für die Duplikate (für die allgemeinsten Fälle).
Herr Duhart
Dies war auch das Problem mit meinem Code. Vielen Dank.
Hardit
Beste Antwort aller Zeiten. Danke dir!
Foivos Ts
8

Der Handler wird jedes Mal hinzugefügt, wenn Sie von außerhalb anrufen. Versuchen Sie, den Handler zu entfernen, nachdem Sie Ihre Arbeit beendet haben:

self.logger.removeHandler(ch)
Mayukh Roy
quelle
1
Ich habe logger.handlers.pop() in Python 2.7 verwendet, macht den Trick
Radtek
6

Ich bin ein Python-Neuling, aber das schien für mich zu funktionieren (Python 2.7)

while logger.handlers:
     logger.handlers.pop()
Gregory Ponto
quelle
4

In meinem Fall würde ich festlegen logger.propagate = False, um Doppeldruck zu verhindern.

Wenn Sie im folgenden Code entfernen logger.propagate = False, wird der Doppeldruck angezeigt.

import logging
from typing import Optional

_logger: Optional[logging.Logger] = None

def get_logger() -> logging.Logger:
    global _logger
    if _logger is None:
        raise RuntimeError('get_logger call made before logger was setup!')
    return _logger

def set_logger(name:str, level=logging.DEBUG) -> None:
    global _logger
    if _logger is not None:
        raise RuntimeError('_logger is already setup!')
    _logger = logging.getLogger(name)
    _logger.handlers.clear()
    _logger.setLevel(level)
    ch = logging.StreamHandler()
    ch.setLevel(level)
    # warnings.filterwarnings("ignore", "(Possibly )?corrupt EXIF data", UserWarning)
    ch.setFormatter(_get_formatter())
    _logger.addHandler(ch)
    _logger.propagate = False # otherwise root logger prints things again


def _get_formatter() -> logging.Formatter:
    return logging.Formatter(
        '[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s')
Shital Shah
quelle
Dies ist das Problem, das ich habe. Vielen Dank
q0987
Gute Antwort; Das Hinzufügen logger.propagate = Falsewar die Lösung, um eine doppelte Protokollierung in einer von Waitress gehosteten Flask-Anwendung zu verhindern, wenn Sie sich bei der Flask- app.loggerInstanz anmelden .
Bluebinary
1

Ein Aufruf zum logging.debug()Aufrufen, logging.basicConfig()wenn keine Root-Handler installiert sind. Das geschah für mich in einem Test-Framework, in dem ich die Reihenfolge, in der Testfälle ausgelöst wurden, nicht kontrollieren konnte. Mein Initialisierungscode war die Installation des zweiten. Die Standardeinstellung verwendet logging.BASIC_FORMAT, die ich nicht wollte.

JimB
quelle
Ich denke, das ist es, was für mich los ist. Wie verhindern Sie die automatische Erstellung von Konsolenloggern?
Robert
@Robert Es geht darum, vor dem ersten Protokollierungsaufruf sicherzustellen, dass Sie mit dem gewünschten Logger initialisiert sind. Das Testen von Frameworks kann dies verschleiern, aber es sollte einen Weg geben, dies zu tun. Wenn Sie mehrere Prozesse ausführen, müssen Sie bei jedem Prozess dasselbe tun.
JimB
1

Es scheint, dass es zu spät ist, wenn Sie etwas (versehentlich) an den Logger ausgeben und dann konfigurieren. Zum Beispiel in meinem Code hatte ich

logging.warning("look out)"

...
ch = logging.StreamHandler(sys.stdout)
root = logging.getLogger()
root.addHandler(ch)

root.info("hello")

Ich würde so etwas bekommen (die Formatoptionen ignorieren)

look out
hello
hello

und alles wurde zweimal auf stdout geschrieben. Ich glaube, das liegt daran, dass beim ersten Aufruf logging.warningautomatisch ein neuer Handler erstellt wird und ich dann explizit einen weiteren Handler hinzugefügt habe. Das Problem verschwand, als ich den versehentlichen ersten logging.warningAnruf entfernte.

Mark Lakata
quelle
0

Ich hatte eine seltsame Situation, in der Konsolenprotokolle verdoppelt wurden, meine Dateiprotokolle jedoch nicht. Nach einer Menge Graben habe ich es herausgefunden.

Bitte beachten Sie, dass Pakete von Drittanbietern Logger registrieren können. Dies ist etwas, auf das Sie achten müssen (und das in einigen Fällen nicht verhindert werden kann). In vielen Fällen überprüft der Code von Drittanbietern, ob Root- Logger-Handler vorhanden sind. und wenn nicht, registrieren sie einen neuen Konsolenhandler.

Meine Lösung bestand darin, meinen Konsolenlogger auf der Stammebene zu registrieren:

rootLogger = logging.getLogger()  # note no text passed in--that's how we grab the root logger
if not rootLogger.handlers:
        ch = logging.StreamHandler()
        ch.setLevel(logging.INFO)
        ch.setFormatter(logging.Formatter('%(process)s|%(levelname)s] %(message)s'))
        rootLogger.addHandler(ch)
Robert
quelle