web-dev-qa-db-de.com

Python: Protokollierungsmodul - global

Ich habe mich gefragt, wie ein globaler Logger implementiert werden kann, der mit Ihren eigenen Einstellungen überall verwendet werden kann:

Ich habe derzeit eine benutzerdefinierte Logger-Klasse:

class customLogger(logging.Logger):
   ...

Die Klasse befindet sich in einer separaten Datei mit einigen Formatierern und anderem Material. Der Logger arbeitet perfekt für sich.

Ich importiere dieses Modul in meine Hauptdatei python und erstelle ein Objekt wie das folgende:

self.log = logModule.customLogger(arguments)

Aber natürlich kann ich nicht von anderen Teilen meines Codes auf dieses Objekt zugreifen. Wende ich einen falschen Ansatz an? Gibt es einen besseren Weg, dies zu tun?

49
cwoebker

Verwenden Sie logging.getLogger(name) , um einen benannten globalen Logger zu erstellen.

main.py

import log
logger = log.setup_custom_logger('root')
logger.debug('main message')

import submodule

log.py

import logging

def setup_custom_logger(name):
    formatter = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(module)s - %(message)s')

    handler = logging.StreamHandler()
    handler.setFormatter(formatter)

    logger = logging.getLogger(name)
    logger.setLevel(logging.DEBUG)
    logger.addHandler(handler)
    return logger

submodule.py

import logging

logger = logging.getLogger('root')
logger.debug('submodule message')

Ausgabe

2011-10-01 20:08:40,049 - DEBUG - main - main message
2011-10-01 20:08:40,050 - DEBUG - submodule - submodule message
105
koehlma

Da ich keine zufriedenstellende Antwort gefunden habe, möchte ich kurz auf die Antwort auf die Frage eingehen, um einen Einblick in die Funktionsweise und Absichten der Bibliothek logging zu erhalten, die mit Pythons Standard geliefert wird Bibliothek.

Im Gegensatz zum Ansatz des OP (Originalposter) trennt die Bibliothek die Schnittstelle zum Logger und die Konfiguration des Loggers selbst klar voneinander.

Die Konfiguration der Handler ist das Vorrecht des Anwendungsentwicklers, der Ihre Bibliothek verwendet.

Das heißt, Sie sollten nicht eine benutzerdefinierte Logger-Klasse erstellen und den Logger in dieser Klasse konfigurieren, indem Sie eine beliebige Konfiguration hinzufügen oder was auch immer.

Die Bibliothek logging enthält vier Komponenten: Logger, Handler, Filter und Formatierer.

  • Protokollierer legen die Schnittstelle offen, die der Anwendungscode direkt verwendet.
  • Handler senden die von den Protokollierern erstellten Protokollsätze an das entsprechende Ziel.
  • Filter bieten eine differenziertere Möglichkeit, um zu bestimmen, welche Protokollsätze ausgegeben werden sollen.
  • Formatierer geben das Layout der Protokollsätze in der endgültigen Ausgabe an.

Eine allgemeine Projektstruktur sieht folgendermaßen aus:

Project/
|-- .../
|   |-- ...
|
|-- project/
|   |-- package/
|   |   |-- __init__.py
|   |   |-- module.py
|   |   
|   |-- __init__.py
|   |-- project.py
|
|-- ...
|-- ...

In Ihrem Code (wie in module.py ) verweisen Sie auf die Protokollierungsinstanz Ihres Moduls, um die Ereignisse auf ihren spezifischen Ebenen zu protokollieren.

Eine gute Konvention für die Benennung von Loggern ist die Verwendung eines Loggers auf Modulebene in jedem Modul, das die Protokollierung verwendet, mit den folgenden Namen:

logger = logging.getLogger(__name__)

Die spezielle Variable __name__ Verweist auf den Namen Ihres Moduls und sieht je nach Codestruktur Ihrer Anwendung wie project.package.module Aus.

module.py (und jede andere Klasse) könnte im Wesentlichen so aussehen:

import logging
...
log = logging.getLogger(__name__)

class ModuleClass:
    def do_something(self):
        log.debug('do_something() has been called!')

Der Logger in jedem Modul gibt jedes Ereignis an den übergeordneten Logger weiter, der im Gegenzug die Informationen an den angehängten Handler weiterleitet. Analog zur python package/module-Struktur wird der übergeordnete Logger durch den Namespace unter Verwendung von "gepunkteten Modulnamen" bestimmt. Deshalb ist es sinnvoll, den Logger mit dem speziellen __name__ Variable (im obigen Beispiel Name entspricht der Zeichenfolge "project.package.module").

Es gibt zwei Möglichkeiten, den Logger global zu konfigurieren:

  • Instanziiere einen Logger in project.py mit dem Namen __package__, Der "project" entspricht Beispiel und ist damit der übergeordnete Logger der Logger aller Submodule. Es ist nur erforderlich, einen geeigneten Handler und Formatierer für this logger hinzuzufügen.

  • Richten Sie einen Logger mit einem Handler und einem Formatierer im ausführenden Skript (wie main.py ) mit dem Namen des obersten Pakets ein.

Bei der Entwicklung einer Bibliothek, die die Protokollierung verwendet, sollten Sie sorgfältig dokumentieren, wie die Bibliothek die Protokollierung verwendet, z. B. die Namen der verwendeten Protokollierer.

Das ausführende Skript, wie zum Beispiel main.py , könnte nun ungefähr so ​​aussehen:

import logging
from project import App

def setup_logger():
    # create logger
    logger = logging.getLogger('project')
    logger.setLevel(logging.DEBUG)

    # create console handler and set level to debug
    ch = logging.StreamHandler()
    ch.setLevel(level)

    # create formatter
    formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(name)s: %(message)s')

    # add formatter to ch
    ch.setFormatter(formatter)

    # add ch to logger
    logger.addHandler(ch)

if __== '__main__' and __package__ is None:
     setup_logger()
     app = App()
     app.do_some_funny_stuff()

Der Methodenaufruf log.setLevel(...) gibt die Protokollnachricht mit dem niedrigsten Schweregrad an, die ein Protokollierer handle ausgibt, aber nicht unbedingt! Dies bedeutet lediglich, dass die Nachricht an den Handler übergeben wird, solange der Schweregrad der Nachricht höher als der eingestellte ist (oder diesem entspricht). Der Handler ist jedoch für die Behandlung der Protokollmeldung verantwortlich (zum Beispiel durch Drucken oder Speichern).

Daher bietet die Bibliothek logging einen strukturierten und modularen Ansatz, der nur entsprechend den eigenen Bedürfnissen genutzt werden muss.

Protokolldokumentation

41
Dennis

Erstellen Sie eine Instanz von customLogger in Ihrem Protokollmodul und verwenden Sie es als Singleton. Verwenden Sie nur die importierte Instanz und nicht die Klasse.

9
Amber

Sie können ihm einfach vor dem ersten Punkt eine Zeichenfolge mit einer gemeinsamen Unterzeichenfolge übergeben. Die durch Punkt (".") Getrennten Teile der Zeichenfolge können für verschiedene Klassen/Module/Dateien/usw. verwendet werden. So (insbesondere der Teil logger = logging.getLogger(loggerName)):

def getLogger(name, logdir=LOGDIR_DEFAULT, level=logging.DEBUG, logformat=FORMAT):
    base = os.path.basename(__file__)
    loggerName = "%s.%s" % (base, name)
    logFileName = os.path.join(logdir, "%s.log" % loggerName)
    logger = logging.getLogger(loggerName)
    logger.setLevel(level)
    i = 0
    while os.path.exists(logFileName) and not os.access(logFileName, os.R_OK | os.W_OK):
        i += 1
        logFileName = "%s.%s.log" % (logFileName.replace(".log", ""), str(i).zfill((len(str(i)) + 1)))
    try:
        #fh = logging.FileHandler(logFileName)
        fh = RotatingFileHandler(filename=logFileName, mode="a", maxBytes=1310720, backupCount=50)
    except IOError, exc:
        errOut = "Unable to create/open log file \"%s\"." % logFileName
        if exc.errno is 13: # Permission denied exception
            errOut = "ERROR ** Permission Denied ** - %s" % errOut
        Elif exc.errno is 2: # No such directory
            errOut = "ERROR ** No such directory \"%s\"** - %s" % (os.path.split(logFileName)[0], errOut)
        Elif exc.errno is 24: # Too many open files
            errOut = "ERROR ** Too many open files ** - Check open file descriptors in /proc/<PID>/fd/ (PID: %s)" % os.getpid()
        else:
            errOut = "Unhandled Exception ** %s ** - %s" % (str(exc), errOut)
        raise LogException(errOut)
    else:
        formatter = logging.Formatter(logformat)
        fh.setLevel(level)
        fh.setFormatter(formatter)
        logger.addHandler(fh)
    return logger

class MainThread:
    def __init__(self, cfgdefaults, configdir, pidfile, logdir, test=False):
        self.logdir = logdir
        logLevel = logging.DEBUG
        logPrefix = "MainThread_TEST" if self.test else "MainThread"
        try:
            self.logger = getLogger(logPrefix, self.logdir, logLevel, FORMAT)
        except LogException, exc:
            sys.stderr.write("%s\n" % exc)
            sys.stderr.flush()
            os._exit(0)
        else:
            self.logger.debug("-------------------- MainThread created.  Starting __init__() --------------------")

    def run(self):
        self.logger.debug("Initializing ReportThreads..")
        for (group, cfg) in self.config.items():
            self.logger.debug(" ------------------------------ GROUP '%s' CONFIG ------------------------------     " % group)
            for k2, v2 in cfg.items():
                self.logger.debug("%s <==> %s: %s" % (group, k2, v2))
            try:
                rt = ReportThread(self, group, cfg, self.logdir, self.test)
            except LogException, exc:
                sys.stderr.write("%s\n" % exc)
                sys.stderr.flush()
                self.logger.exception("Exception when creating ReportThread (%s)" % group)
                logging.shutdown()
                os._exit(1)
            else:
                self.threads.append(rt)
        self.logger.debug("Threads initialized.. \"%s\"" % ", ".join([t.name for t in self.threads]))
        for t in self.threads:
            t.Start()
        if not self.test:
            self.loop()


class ReportThread:
    def __init__(self, mainThread, name, config, logdir, test):
        self.mainThread = mainThread
        self.name = name
        logLevel = logging.DEBUG
        self.logger = getLogger("MainThread%s.ReportThread_%s" % ("_TEST" if self.test else "", self.name), logdir, logLevel, FORMAT)
        self.logger.info("init database...")
        self.initDB()
        # etc....

if __== "__main__":
    # .....
    MainThread(cfgdefaults=options.cfgdefaults, configdir=options.configdir, pidfile=options.pidfile, logdir=options.logdir, test=options.test)
3
chown