Doppelte Protokollausgabe bei Verwendung des Python-Protokollierungsmoduls


105

Ich benutze Python Logger. Folgendes ist mein Code:

import os
import time
import datetime
import logging
class Logger :
   def myLogger(self):
      logger = logging.getLogger('ProvisioningPython')
      logger.setLevel(logging.DEBUG)
      now = datetime.datetime.now()
      handler=logging.FileHandler('/root/credentials/Logs/ProvisioningPython'+ now.strftime("%Y-%m-%d") +'.log')
      formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
      handler.setFormatter(formatter)
      logger.addHandler(handler)
      return logger

Das Problem ist, dass ich für jeden logger.infoAnruf mehrere Einträge in der Protokolldatei erhalte . Wie kann ich das lösen?


Funktioniert bei mir. Python 3.2 und Windows XP.
Zuljin

2
Sind Sie sicher, dass Sie nicht mehrere Logger-Instanzen erstellen?
Gandi

Ja. In einer anderen Datei nehme ich eine neue Instanz, wie wir es in Java-Projekten getan haben. Bitte geben Sie an, ob dies zu Problemen führt oder nicht.
user865438

Antworten:


94

Das logging.getLogger()ist schon ein Singleton. ( Dokumentation )

Das Problem ist, dass bei jedem Aufruf myLogger()der Instanz ein weiterer Handler hinzugefügt wird, der zu doppelten Protokollen führt.

Vielleicht so etwas?

import os
import time
import datetime
import logging

loggers = {}

def myLogger(name):
    global loggers

    if loggers.get(name):
        return loggers.get(name)
    else:
        logger = logging.getLogger(name)
        logger.setLevel(logging.DEBUG)
        now = datetime.datetime.now()
        handler = logging.FileHandler(
            '/root/credentials/Logs/ProvisioningPython' 
            + now.strftime("%Y-%m-%d") 
            + '.log')
        formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        loggers[name] = logger

        return logger

3
Ich denke, Sie sollten stattdessen loggers.update (dict ((name, logger))) haben.
Akrophobie

warum loggers.update(dict(name=logger))? ist nicht loggers[name] = loggereinfacher?
Ryan J McCall

@ RyanJMcCall Zu der Zeit war es die Codierungskonvention, die ich verwendet habe. Aber wenn ich den Code so überprüfe, wie er jetzt ist, sehe ich, dass er kaputt ist. loggers.update(dict(name=logger))erstellt ein Wörterbuch mit einem einzelnen Schlüssel nameund aktualisiert diesen Schlüssel kontinuierlich. Ich bin überrascht, dass dies niemand zuvor erwähnt hat, da dieser Code ziemlich kaputt ist :) Wird die erforderlichen Änderungen vornehmen.
Werner Smit

Ich sehe, dass @acrophobia sich vor langer Zeit diesem entzogen hat. Vielen Dank.
Werner Smit

Ist das globale loggersWörterbuch nicht redundant logging.getLogger? Da Sie wirklich nur vermeiden möchten, zusätzliche Handler hinzuzufügen, scheinen Sie die Antworten unten zu bevorzugen, die direkt nach
Handlern suchen

60

Seit Python 3.2 können Sie einfach überprüfen, ob bereits Handler vorhanden sind, und diese in diesem Fall löschen, bevor Sie neue Handler hinzufügen. Dies ist beim Debuggen sehr praktisch und der Code enthält Ihre Logger-Initialisierung

if (logger.hasHandlers()):
    logger.handlers.clear()

logger.addHandler(handler)

Gute Antwort, Thx :))
Gavriel Cohen

2
Beachten Sie, dass hasHandlers () in pytest true zurückgibt, wenn dem Root-Logger ein Handler hinzugefügt wurde, auch wenn Ihre lokalen / benutzerdefinierten Handler noch nicht hinzugefügt wurden. Das len (logger.handlers) (gemäß Guillaumes Antwort) gibt in diesem Fall 0 zurück, ist also möglicherweise eine bessere Option.
Grant

Dies ist eine echte Lösung, nach der ich gesucht habe.
XCanG

45
import datetime
import logging
class Logger :
    def myLogger(self):
       logger=logging.getLogger('ProvisioningPython')
       if not len(logger.handlers):
          logger.setLevel(logging.DEBUG)
          now = datetime.datetime.now()
          handler=logging.FileHandler('/root/credentials/Logs/ProvisioningPython'+ now.strftime("%Y-%m-%d") +'.log')
          formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
          handler.setFormatter(formatter)
          logger.addHandler(handler)
        return logger

machte den Trick für mich

mit Python 2.7


1
Dies funktioniert auch, wenn das Modul neu geladen wird (was bei den anderen Antworten nicht der Fall ist)
yco

3
Vielen Dank für den Tipp, BTW zu überprüfen, ob eine Liste leer ist oder nicht, müssen Sie nicht den Operator "len" verwenden, den Sie direkt verwenden können, wenn my_list: ..
rkachach

26

Ich habe das bereits loggerals Singleton verwendet und überprüft if not len(logger.handlers), aber immer noch Duplikate erhalten : Es war die formatierte Ausgabe, gefolgt von der unformatierten.

Lösung in meinem Fall: logger.propagate = False

Dank an diese Antwort und die Dokumente .


1
Ich hatte herausgefunden, dass die doppelte Protokollierung von RootLogger und meinem StreamHandler stammt, konnte das Problem jedoch erst beheben (während mein Formatierer im StreamHandler beibehalten wurde).
Xander YzWich

10

Sie rufen Logger.myLogger()mehr als einmal an. Speichern Sie die Logger - Instanz es gibt irgendwo und Wiederverwendung , dass .

Beachten Sie außerdem, dass ein Standardwert erstellt StreamHandler(sys.stderr)wird , wenn Sie sich vor dem Hinzufügen eines Handlers anmelden .


Eigentlich versuche ich, auf die Logger-Instanz zuzugreifen, wie wir sie in Java verwenden. Aber ich weiß nicht, ob es notwendig ist, eine Instanz nur einmal für ein ganzes Projekt zu erstellen oder nicht.
user865438

1
@ user865483: Nur einmal. Alle Standard-Bibliothekslogger sind Singletons.
Matt Joiner

5

Dies ist eine Ergänzung zu @ rm957377s Antwort, aber mit einer Erklärung, warum dies geschieht . Wenn Sie eine Lambda-Funktion in AWS ausführen, rufen sie Ihre Funktion innerhalb einer Wrapping-Instanz auf, die für mehrere Aufrufe am Leben bleibt. Das heißt, wenn Sie addHandler()den Code Ihrer Funktion aufrufen , werden dem Protokollierungs-Singleton bei jeder Ausführung der Funktion weiterhin doppelte Handler hinzugefügt. Der Protokollierungs-Singleton bleibt durch mehrere Aufrufe Ihrer Lambda-Funktion bestehen.

Um dies zu lösen, können Sie Ihre Handler löschen, bevor Sie sie einstellen über:

logging.getLogger().handlers.clear()
logging.getLogger().addHandler(...)

Irgendwie werden in meinem Fall die Logger-Handler bei dem Ereignis auf .info()Abruf hinzugefügt , was ich nicht verstehe.
Evgeny

4

Ihr Logger sollte als Singleton arbeiten. Sie sollten es nicht mehr als einmal erstellen. Hier ist ein Beispiel, wie es aussehen könnte:

import os
import time
import datetime
import logging
class Logger :
    logger = None
    def myLogger(self):
        if None == self.logger:
            self.logger=logging.getLogger('ProvisioningPython')
            self.logger.setLevel(logging.DEBUG)
            now = datetime.datetime.now()
            handler=logging.FileHandler('ProvisioningPython'+ now.strftime("%Y-%m-%d") +'.log')
            formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
            handler.setFormatter(formatter)
            self.logger.addHandler(handler)
        return self.logger

s = Logger()
m = s.myLogger()
m2 = s.myLogger()
m.info("Info1")
m2.info("info2")

dann wieder, wenn ich die andere Instanz in einer anderen Datei nehmen werde. Angenommen, in Datei 1 s = Logger () m = s.myLogger () und in Datei 2 s = Logger () wird es funktionieren oder nicht m2 = s.myLogger ()
user865438

Trotzdem bekomme ich mehrmals eine Kopie desselben Protokolls. Ich habe hier Zweifel, ob im Thread-Protokoll mehr als einer gedruckt wird oder nicht. Bitte helfen Sie mir dabei.
user865438

1
@ user865438, wir müssen uns nicht darum kümmern, die Implementierung zu einem Singleton zu machen (das ist es bereits). Folgen Sie für die Anmeldung in Submodulen dem offiziellen Link zum Protokollierungskochbuch . Grundsätzlich müssen Sie beim Benennen der Logger der Namenshierarchie folgen, und der Rest wird erledigt.
Narayan

4

Die Implementierung von Logger ist bereits ein Singleton.

Mehrere Aufrufe von logging.getLogger ('someLogger') geben einen Verweis auf dasselbe Logger-Objekt zurück. Dies gilt nicht nur innerhalb desselben Moduls, sondern auch modulübergreifend, solange es sich im selben Python-Interpreter-Prozess befindet. Dies gilt für Verweise auf dasselbe Objekt. Darüber hinaus kann der Anwendungscode einen übergeordneten Logger in einem Modul definieren und konfigurieren und einen untergeordneten Logger in einem separaten Modul erstellen (aber nicht konfigurieren). Alle Logger-Aufrufe an das untergeordnete Protokoll werden an das übergeordnete Protokoll weitergeleitet. Hier ist ein Hauptmodul

Quelle - Verwenden der Anmeldung in mehreren Modulen

Die Art und Weise, wie Sie dies nutzen sollten, ist -

Nehmen wir an, wir haben im Hauptmodul einen Logger namens 'main_logger' erstellt und konfiguriert (der den Logger einfach konfiguriert und nichts zurückgibt ).

# get the logger instance
logger = logging.getLogger("main_logger")
# configuration follows
...

Wenn wir jetzt in einem Untermodul einen untergeordneten Logger erstellen , der der Namenshierarchie 'main_logger.sub_module_logger' folgt, müssen wir ihn nicht im Untermodul konfigurieren. Es reicht aus, nur den Logger nach der Namenshierarchie zu erstellen.

# get the logger instance
logger = logging.getLogger("main_logger.sub_module_logger")
# no configuration needed
# it inherits the configuration from the parent logger
...

Außerdem wird kein doppelter Handler hinzugefügt.

In dieser Frage finden Sie eine ausführlichere Antwort.


1
Die Neudefinition von Handlern nach getLogger scheint für mich zu funktionieren: logger = logging.getLogger('my_logger') ; logger.handlers = [logger.handlers[0], ]
Radtek

2

Eine doppelte (oder dreifache oder ..- basierend auf der Anzahl der Neuladungen) Logger-Ausgabe kann auch auftreten, wenn Sie Ihr Modul über neu laden importlib.reload(aus dem gleichen Grund, wie in der akzeptierten Antwort erläutert). Ich füge diese Antwort nur als zukünftige Referenz hinzu, da ich eine Weile gebraucht habe, um herauszufinden, warum meine Ausgabe doppelt (dreifach) ist.


1

Eine einfache Problemumgehung ist wie

logger.handlers[:] = [handler]

Auf diese Weise vermeiden Sie, dass ein neuer Handler an die zugrunde liegende Liste "Handler" angehängt wird.


1

Fazit: In den meisten Fällen muss logger.getLogger () nur einmal pro Modul aufgerufen werden. Wenn Sie wie ich mehrere Klassen haben, könnte ich das so nennen:

LOGGER = logger.getLogger(__name__)

class MyClass1:
    log = LOGGER
    def __init__(self):
        self.log.debug('class 1 initialized')

class MyClass2:
    log = LOGGER
    def __init__(self):
        self.log.debug('class 2 initialized')

Beide haben dann ihren eigenen vollständigen Paketnamen und ihre eigene Methode, wo sie protokolliert werden.


0

Sie können eine Liste aller Handler für den jeweiligen Logger abrufen, um so etwas zu tun

logger = logging.getLogger(logger_name)
handler_installed = False
for handler in logger:
    # Here your condition to check for handler presence
    if isinstance(handler, logging.FileHandler) and handler.baseFilename == log_filename:
        handler_installed = True
        break

if not handler_installed:
    logger.addHandler(your_handler)

Im obigen Beispiel prüfen wir, ob der Handler für eine angegebene Datei bereits mit dem Logger verknüpft ist. Wenn Sie jedoch auf die Liste aller Handler zugreifen können, können Sie entscheiden, nach welchen Kriterien Sie einen weiteren Handler hinzufügen möchten oder nicht.


0

Hatte heute dieses Problem. Da meine Funktionen @staticmethod waren, wurden die obigen Vorschläge mit random () aufgelöst.

Sieht ungefähr so ​​aus:

import random

logger = logging.getLogger('ProvisioningPython.{}'.format(random.random()))

-1
from logging.handlers import RotatingFileHandler
import logging
import datetime

# stores all the existing loggers
loggers = {}

def get_logger(name):

    # if a logger exists, return that logger, else create a new one
    global loggers
    if name in loggers.keys():
        return loggers[name]
    else:
        logger = logging.getLogger(name)
        logger.setLevel(logging.DEBUG)
        now = datetime.datetime.now()
        handler = logging.FileHandler(
            'path_of_your_log_file' 
            + now.strftime("%Y-%m-%d") 
            + '.log')
        formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        loggers.update(dict(name=logger))
        return logger

Bitte fügen Sie eine Erklärung hinzu, um diese Antwort für den Langzeitgebrauch wertvoller zu machen.
Aminah Nuraini
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.