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?

user865438
quelle
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
Werner Smit
quelle
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)
rm957377
quelle
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

Guillaume Cisco
quelle
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 .

Herr B.
quelle
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 .

Matt Joiner
quelle
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(...)
Chad Befus
quelle
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")
Zuljin
quelle
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.

Narayan
quelle
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.

rkuska
quelle
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.

aihex
quelle
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.

Harlin
quelle
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.

Meistgesucht
quelle
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()))
Pacman
quelle
-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
Avinash Kumar
quelle
Bitte fügen Sie eine Erklärung hinzu, um diese Antwort für den Langzeitgebrauch wertvoller zu machen.
Aminah Nuraini