Verwenden der Anmeldung in mehreren Modulen

257

Ich habe ein kleines Python-Projekt mit der folgenden Struktur:

Project 
 -- pkg01
   -- test01.py
 -- pkg02
   -- test02.py
 -- logging.conf

Ich plane, das Standardprotokollierungsmodul zu verwenden, um Nachrichten an stdout und eine Protokolldatei zu drucken. Um das Protokollierungsmodul verwenden zu können, ist eine Initialisierung erforderlich.

import logging.config

logging.config.fileConfig('logging.conf')
logger = logging.getLogger('pyApp')

logger.info('testing')

Derzeit führe ich diese Initialisierung in jedem Modul durch, bevor ich mit der Protokollierung von Nachrichten beginne. Ist es möglich, diese Initialisierung nur einmal an einem Ort durchzuführen, sodass dieselben Einstellungen durch Protokollierung im gesamten Projekt wiederverwendet werden?

Quest Monger
quelle
3
Als Antwort auf Ihren Kommentar zu meiner Antwort: Sie müssen nicht fileConfigjedes Modul aufrufen , das die Protokollierung durchführt, es sei denn, Sie haben if __name__ == '__main__'in allen Modulen Logik. Die Antwort von prost ist keine gute Vorgehensweise, wenn es sich bei dem Paket um eine Bibliothek handelt, obwohl dies möglicherweise für Sie funktioniert. Sie sollten die Anmeldung in Bibliothekspaketen nicht konfigurieren, außer eine hinzuzufügen NullHandler.
Vinay Sajip
1
prost implizierte, dass wir die Import- und Logger-Stmts in jedem Modul aufrufen müssen und nur die Fileconfig-Stmt im Hauptmodul aufrufen müssen. Ist das nicht ähnlich wie das, was du sagst?
Quest Monger
6
prost sagt, dass Sie den Konfigurationscode für die Protokollierung eingeben sollten package/__init__.py. Dies ist normalerweise nicht der Ort, an dem Sie if __name__ == '__main__'Code eingeben. Außerdem sieht das Beispiel von prost so aus, als würde es den Konfigurationscode beim Import bedingungslos aufrufen, was für mich nicht richtig aussieht. Im Allgemeinen sollte die Protokollierung des Konfigurationscodes an einem Ort erfolgen und nicht als Nebeneffekt des Imports auftreten, außer wenn Sie __main__ importieren.
Vinay Sajip
Sie haben Recht, ich habe die Zeile '# package / __ init__.py' in seinem Codebeispiel völlig verpasst. Vielen Dank für den Hinweis und Ihre Geduld.
Quest Monger
1
Was passiert also, wenn Sie mehrere haben if __name__ == '__main__'? (es wird nicht explizit in Frage gestellt, ob dies der Fall ist)
kon psych

Antworten:

293

Es wird empfohlen, in jedem Modul einen Logger wie folgt zu definieren:

import logging
logger = logging.getLogger(__name__)

in der Nähe der Oberseite des Moduls und dann in anderem Code im Modul tun Sie z

logger.debug('My message with %s', 'variable data')

Wenn Sie die Protokollierungsaktivität innerhalb eines Moduls unterteilen müssen, verwenden Sie z

loggerA = logging.getLogger(__name__ + '.A')
loggerB = logging.getLogger(__name__ + '.B')

und melden Sie sich bei loggerAund loggerBnach Bedarf an.

Führen Sie in Ihrem Hauptprogramm oder Ihren Hauptprogrammen z.

def main():
    "your program code"

if __name__ == '__main__':
    import logging.config
    logging.config.fileConfig('/path/to/logging.conf')
    main()

oder

def main():
    import logging.config
    logging.config.fileConfig('/path/to/logging.conf')
    # your program code

if __name__ == '__main__':
    main()

Siehe hier für aus mehreren Modulen Anmeldung und hier für die Protokollierung Konfiguration für Code, der durch einen anderen Code als Bibliothek - Modul verwendet wird.

Update: Beim Aufrufen fileConfig()möchten Sie möglicherweise angeben, disable_existing_loggers=Falseob Sie Python 2.6 oder höher verwenden ( weitere Informationen finden Sie in den Dokumenten ). Der Standardwert ist die TrueAbwärtskompatibilität. Dadurch werden alle vorhandenen Protokollierer deaktiviert, es fileConfig()sei denn, sie oder ihre Vorfahren werden in der Konfiguration explizit benannt. Wenn der Wert auf gesetzt ist False, werden vorhandene Logger in Ruhe gelassen. Wenn Sie Python 2.7 / Python 3.2 oder höher verwenden, sollten Sie die dictConfig()API in Betracht ziehen, die besser ist als die bessere fileConfig()Kontrolle über die Konfiguration.

Vinay Sajip
quelle
21
Wenn Sie sich mein Beispiel ansehen, mache ich bereits das, was Sie oben vorgeschlagen haben. Meine Frage war, wie ich diese Protokollierungsinitialisierung so zentralisieren kann, dass ich diese 3 Anweisungen nicht wiederholen muss. Außerdem haben Sie in Ihrem Beispiel die Datei 'logging.config.fileConfig (' logging.conf ')' verpasst. Dieses stmt ist eigentlich die Hauptursache meiner Besorgnis. Sie sehen, wenn ich den Logger in jedem Modul initiiert habe, müsste ich dieses stmt in jedes Modul eingeben. Das würde bedeuten, den Pfad der Conf-Datei in jedem Modul zu verfolgen, was für mich nicht als Best Practice erscheint (stellen Sie sich das Chaos beim Ändern der Modul- / Paketpositionen vor).
Quest Monger
4
Wenn Sie fileConfig nach dem Erstellen des Loggers aufrufen, funktioniert dies nicht, egal ob im selben oder in einem anderen Modul (z. B. wenn Sie den Logger oben in der Datei erstellen). Die Protokollierungskonfiguration gilt nur für nachträglich erstellte Protokollierer. Dieser Ansatz funktioniert also nicht oder ist für mehrere Module keine praktikable Option. @Quest Monger: Sie können jederzeit eine andere Datei erstellen, die den Speicherort der Konfigurationsdatei enthält ..;)
Vincent Ketelaars
2
@Oxidator: Nicht unbedingt - siehe das disable_existing_loggersFlag, das Truestandardmäßig aktiviert ist, aber auf gesetzt werden kann False.
Vinay Sajip
1
@ Vinay Sajip, danke. Haben Sie Empfehlungen für Logger, die in Modulen, aber auch außerhalb von Klassen arbeiten? Da die Importe vor dem Aufruf der Hauptfunktion durchgeführt werden, wurden diese Protokolle bereits protokolliert. Ich denke, das Einrichten Ihres Loggers vor allen Importen im Hauptmodul ist der einzige Weg? Dieser Logger kann dann auf Wunsch in main überschrieben werden.
Vincent Ketelaars
1
Wenn ich möchte, dass alle meine modulspezifischen Logger eine andere Protokollierungsstufe als die Standard-WARNUNG haben, muss ich diese Einstellung für jedes Modul vornehmen? Angenommen, ich möchte, dass alle meine Module bei INFO protokolliert werden.
Raj
127

Tatsächlich ist jeder Logger ein untergeordnetes Element des Paketloggers des Elternteils (dh package.subpackage.moduleerbt die Konfiguration von package.subpackage). Sie müssen also nur den Root-Logger konfigurieren. Dies kann erreicht werden durch logging.config.fileConfig(Ihre eigene Konfiguration für Logger) oder logging.basicConfig(legt den Root-Logger fest). . Setup - Protokollierung im Eingabemodul ( __main__.pyoder was auch immer Sie zum Beispiel ausführen will main_script.py. __init__.pyfunktioniert auch)

using basicConfig:

# package/__main__.py
import logging
import sys

logging.basicConfig(stream=sys.stdout, level=logging.INFO)

using fileConfig:

# package/__main__.py
import logging
import logging.config

logging.config.fileConfig('logging.conf')

und erstellen Sie dann jeden Logger mit:

# package/submodule.py
# or
# package/subpackage/submodule.py
import logging
log = logging.getLogger(__name__)

log.info("Hello logging!")

Weitere Informationen finden Sie im Advanced Logging Tutorial .

Stan Prokop
quelle
15
Dies ist bei weitem die einfachste Lösung für das Problem, ganz zu schweigen davon, dass es die Eltern-Kind-Beziehung zwischen Modulen aufdeckt und nutzt, was mir als Noob nicht bewusst war. danke.
Quest Monger
Du hast recht. und wie vinay in seinem Beitrag betonte, ist Ihre Lösung richtig, solange sie nicht im init .py-Modul enthalten ist. Ihre Lösung hat funktioniert, als ich sie auf das Hauptmodul (Einstiegspunkt) angewendet habe.
Quest Monger
2
eigentlich viel relevantere Antwort, da es sich bei der Frage um separate Module handelt.
Jan Sila
1
Dumme Frage vielleicht: Wenn kein Logger angemeldet ist __main__.py(z. B. wenn ich das Modul in einem Skript verwenden möchte, das keinen Logger hat), wird logging.getLogger(__name__)immer noch eine Art Logging im Modul durchgeführt oder wird eine Ausnahme ausgelöst?
Bill
1
Schließlich. Ich hatte einen funktionierenden Logger, der jedoch in Windows for Parallel mit joblib fehlgeschlagen ist. Ich denke, dies ist eine manuelle Optimierung des Systems - etwas anderes stimmt mit Parallel nicht. Aber es funktioniert sicherlich! Danke
B Furtado
17

Ich mache es immer wie unten.

Verwenden Sie eine einzelne Python-Datei, um mein Protokoll als Singleton-Muster mit dem Namen ' log_conf.py' zu konfigurieren.

#-*-coding:utf-8-*-

import logging.config

def singleton(cls):
    instances = {}
    def get_instance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return get_instance()

@singleton
class Logger():
    def __init__(self):
        logging.config.fileConfig('logging.conf')
        self.logr = logging.getLogger('root')

Importieren Sie in einem anderen Modul einfach die Konfiguration.

from log_conf import Logger

Logger.logr.info("Hello World")

Dies ist ein Singleton-Muster, das einfach und effizient protokolliert werden kann.

Yarkee
quelle
1
Vielen Dank für die Detaillierung des Singleton-Musters. Ich hatte vor, dies zu implementieren, aber dann ist die @ prost-Lösung viel einfacher und passt perfekt zu meinen Anforderungen. Ich sehe jedoch, dass Ihre Lösung bei größeren Projekten mit mehreren Einstiegspunkten (außer Haupt) nützlich ist. danke.
Quest Monger
46
Das ist nutzlos. Der Root-Logger ist bereits ein Singleton. Verwenden Sie einfach logging.info anstelle von Logger.logr.info.
Pod
9

Einige dieser Antworten deuten darauf hin, dass Sie sich oben in einem Modul befinden

import logging
logger = logging.getLogger(__name__)

Nach meinem Verständnis wird dies als sehr schlechte Praxis angesehen . Der Grund dafür ist, dass die Dateikonfiguration standardmäßig alle vorhandenen Protokollierer deaktiviert. Z.B

#my_module
import logging

logger = logging.getLogger(__name__)

def foo():
    logger.info('Hi, foo')

class Bar(object):
    def bar(self):
        logger.info('Hi, bar')

Und in Ihrem Hauptmodul:

#main
import logging

# load my module - this now configures the logger
import my_module

# This will now disable the logger in my module by default, [see the docs][1] 
logging.config.fileConfig('logging.ini')

my_module.foo()
bar = my_module.Bar()
bar.bar()

Jetzt ist das in logging.ini angegebene Protokoll leer, da der vorhandene Protokollierer durch den Aufruf von fileconfig deaktiviert wurde.

Es ist zwar möglich, dies zu umgehen (disable_existing_Loggers = False), aber realistischerweise wissen viele Clients Ihrer Bibliothek nichts über dieses Verhalten und erhalten Ihre Protokolle nicht. Machen Sie es Ihren Kunden einfach, indem Sie logging.getLogger immer lokal aufrufen. Hut Tipp: Ich habe von diesem Verhalten auf der Website von Victor Lin erfahren .

Es empfiehlt sich daher, logging.getLogger immer lokal aufzurufen. Z.B

#my_module
import logging

logger = logging.getLogger(__name__)

def foo():
    logging.getLogger(__name__).info('Hi, foo')

class Bar(object):
    def bar(self):
        logging.getLogger(__name__).info('Hi, bar')    

Wenn Sie in Ihrer Hauptdatei fileconfig verwenden, setzen Sie disable_existing_loggers = False, nur für den Fall, dass Ihre Bibliotheksdesigner Logger-Instanzen auf Modulebene verwenden.

phil_20686
quelle
Kannst du logging.config.fileConfig('logging.ini')vorher nicht rennen import my_module? Wie in dieser Antwort vorgeschlagen .
lucid_dreamer
Ich bin mir nicht sicher - aber es wäre definitiv auch eine schlechte Praxis, Importe und ausführbaren Code auf diese Weise zu mischen. Sie möchten auch nicht, dass Ihre Clients vor dem Import prüfen müssen, ob sie die Protokollierung konfigurieren müssen, insbesondere wenn es eine triviale Alternative gibt! Stellen Sie sich vor, eine weit verbreitete Bibliothek wie Anfragen hätte das getan ...!
phil_20686
"Ich bin mir nicht sicher - aber es wäre definitiv auch eine schlechte Praxis, Importe und ausführbaren Code auf diese Weise zu mischen." - Warum?
lucid_dreamer
Mir ist nicht klar, warum das schlecht ist. Und ich verstehe Ihr Beispiel nicht ganz. Können Sie Ihre Konfiguration für dieses Beispiel veröffentlichen und eine Verwendung zeigen?
lucid_dreamer
1
Sie scheinen den offiziellen Dokumenten zu widersprechen : "Eine gute Konvention beim Benennen von Loggern ist die Verwendung eines Loggers auf Modulebene in jedem Modul, das Protokollierung verwendet, mit folgendem Namen: logger = logging.getLogger(__name__)"
iron9
8

Eine einfache Möglichkeit, eine Instanz der Protokollierungsbibliothek in mehreren Modulen zu verwenden, war für mich die folgende Lösung:

base_logger.py

import logging

logger = logging
logger.basicConfig(format='%(asctime)s - %(message)s', level=logging.INFO)

Andere Dateien

from base_logger import logger

if __name__ == '__main__':
    logger.info("This is an info message")
Alex Jolig
quelle
7

Eine andere Lösung einwerfen.

In der init .py meines Moduls habe ich so etwas wie:

# mymodule/__init__.py
import logging

def get_module_logger(mod_name):
  logger = logging.getLogger(mod_name)
  handler = logging.StreamHandler()
  formatter = logging.Formatter(
        '%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
  handler.setFormatter(formatter)
  logger.addHandler(handler)
  logger.setLevel(logging.DEBUG)
  return logger

Dann brauche ich in jedem Modul einen Logger:

# mymodule/foo.py
from [modname] import get_module_logger
logger = get_module_logger(__name__)

Wenn die Protokolle fehlen, können Sie ihre Quelle nach dem Modul unterscheiden, von dem sie stammen.

Tommy
quelle
Was bedeutet "Hauptinit meines Moduls"? Und "Dann brauche ich in jeder Klasse einen Logger:"? Können Sie ein Beispiel mit dem Namen_module.py und ein Beispiel für dessen Verwendung als Import aus dem Modul caller_module.py bereitstellen? In dieser Antwort finden Sie eine Inspiration für das Format, nach dem ich frage. Ich versuche nicht zu bevormunden. Ich versuche Ihre Antwort zu verstehen und ich weiß, ich würde es tun, wenn Sie es so schreiben würden.
lucid_dreamer
1
@lucid_dreamer habe ich geklärt.
Tommy
4

Sie könnten sich auch so etwas einfallen lassen!

def get_logger(name=None):
    default = "__app__"
    formatter = logging.Formatter('%(levelname)s: %(asctime)s %(funcName)s(%(lineno)d) -- %(message)s',
                              datefmt='%Y-%m-%d %H:%M:%S')
    log_map = {"__app__": "app.log", "__basic_log__": "file1.log", "__advance_log__": "file2.log"}
    if name:
        logger = logging.getLogger(name)
    else:
        logger = logging.getLogger(default)
    fh = logging.FileHandler(log_map[name])
    fh.setFormatter(formatter)
    logger.addHandler(fh)
    logger.setLevel(logging.DEBUG)
    return logger

Jetzt können Sie mehrere Logger in demselben Modul und im gesamten Projekt verwenden, wenn das oben Gesagte in einem separaten Modul definiert und in andere Module importiert wird, in denen eine Protokollierung erforderlich ist.

a=get_logger("__app___")
b=get_logger("__basic_log__")
a.info("Starting logging!")
b.debug("Debug Mode")
Deeshank
quelle
4

@ Yarkees Lösung schien besser. Ich möchte noch etwas hinzufügen -

class Singleton(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances.keys():
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]


class LoggerManager(object):
    __metaclass__ = Singleton

    _loggers = {}

    def __init__(self, *args, **kwargs):
        pass

    @staticmethod
    def getLogger(name=None):
        if not name:
            logging.basicConfig()
            return logging.getLogger()
        elif name not in LoggerManager._loggers.keys():
            logging.basicConfig()
            LoggerManager._loggers[name] = logging.getLogger(str(name))
        return LoggerManager._loggers[name]    


log=LoggerManager().getLogger("Hello")
log.setLevel(level=logging.DEBUG)

LoggerManager kann also für die gesamte Anwendung steckbar sein. Hoffe, es macht Sinn und Wert.

Deeshank
quelle
11
Das Protokollierungsmodul befasst sich bereits mit Singletons. logging.getLogger ("Hallo") erhält für alle Module den gleichen Logger.
Pod
2

Es gibt mehrere Antworten. Am Ende hatte ich eine ähnliche, aber andere Lösung, die für mich Sinn macht. Vielleicht macht sie auch für Sie Sinn. Mein Hauptziel war es, Protokolle nach ihrer Ebene an Handler übergeben zu können (Protokolle auf Debug-Ebene an die Konsole, Warnungen und höher an Dateien):

from flask import Flask
import logging
from logging.handlers import RotatingFileHandler

app = Flask(__name__)

# make default logger output everything to the console
logging.basicConfig(level=logging.DEBUG)

rotating_file_handler = RotatingFileHandler(filename="logs.log")
rotating_file_handler.setLevel(logging.INFO)

app.logger.addHandler(rotating_file_handler)

hat eine nette util-Datei namens logger.py erstellt:

import logging

def get_logger(name):
    return logging.getLogger("flask.app." + name)

Die flask.app ist ein fest codierter Wert in der Flasche. Der Anwendungslogger beginnt immer mit flask.app als Modulnamen.

Jetzt kann ich es in jedem Modul im folgenden Modus verwenden:

from logger import get_logger
logger = get_logger(__name__)

logger.info("new log")

Dadurch wird mit minimalem Aufwand ein neues Protokoll für "app.flask.MODULE_NAME" erstellt.

Ben Yitzhaki
quelle
2

Die beste Vorgehensweise wäre, ein Modul separat zu erstellen, das nur eine Methode enthält, deren Aufgabe es ist, der aufrufenden Methode einen Logger-Handler zuzuweisen. Speichern Sie diese Datei als m_logger.py

import logger, logging

def getlogger():
    # logger
    logger = logging.getLogger(__name__)
    logger.setLevel(logging.DEBUG)
    # create console handler and set level to debug
    #ch = logging.StreamHandler()
    ch = logging.FileHandler(r'log.txt')
    ch.setLevel(logging.DEBUG)
    # create formatter
    formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
    # add formatter to ch
    ch.setFormatter(formatter)
    # add ch to logger
    logger.addHandler(ch)
    return logger

Rufen Sie jetzt die Methode getlogger () auf, wenn ein Logger-Handler benötigt wird.

from m_logger import getlogger
logger = getlogger()
logger.info('My mssg')
Mousam Singh
quelle
1
Dies ist gut, wenn Sie keine zusätzlichen Parameter haben. Aber wenn Sie zum Beispiel eine --debugOption in der App haben und die Protokollierungsstufe in allen Loggern in Ihrer App basierend auf diesem Parameter festlegen möchten ...
Der Pate
@ TheGodfather Ja, das ist mit dieser Methode schwer zu erreichen. In dieser Situation können wir eine Klasse erstellen, für die zum Zeitpunkt der Objekterstellung der Formatierer als Parameter verwendet wird und die ähnliche Funktion zum Zurückgeben des Logger-Handlers hat. Wie sehen Sie dies?
Mousam Singh
Ja, ich habe etwas Ähnliches getan, get_logger(level=logging.INFO)um eine Art Singleton zurückzugeben. Wenn es also zum ersten Mal von der Haupt-App aufgerufen wird, initialisiert es den Logger und die Handler mit der richtigen Ebene und gibt dann dasselbe loggerObjekt an alle anderen Methoden zurück.
Der Pate
0

Neu in Python, daher weiß ich nicht, ob dies ratsam ist, aber es funktioniert hervorragend, wenn Boilerplate nicht neu geschrieben wird.

Ihr Projekt muss eine init .py haben, damit es als Modul geladen werden kann

# Put this in your module's __init__.py
import logging.config
import sys

# I used this dictionary test, you would put:
# logging.config.fileConfig('logging.conf')
# The "" entry in loggers is the root logger, tutorials always 
# use "root" but I can't get that to work
logging.config.dictConfig({
    "version": 1,
    "formatters": {
        "default": {
            "format": "%(asctime)s %(levelname)s %(name)s %(message)s"
        },
    },
    "handlers": {
        "console": {
            "level": 'DEBUG',
            "class": "logging.StreamHandler",
            "stream": "ext://sys.stdout"
        }
    },
    "loggers": {
        "": {
            "level": "DEBUG",
            "handlers": ["console"]
        }
    }
})

def logger():
    # Get the name from the caller of this function
    return logging.getLogger(sys._getframe(1).f_globals['__name__'])

sys._getframe(1)Vorschlag kommt von hier

So verwenden Sie Ihren Logger in einer anderen Datei:

from [your module name here] import logger

logger().debug("FOOOOOOOOO!!!")

Vorsichtsmaßnahmen:

  1. Sie müssen Ihre Dateien als Module ausführen, sonst import [your module]funktioniert es nicht:
    • python -m [your module name].[your filename without .py]
  2. Der Name des Loggers für den Einstiegspunkt Ihres Programms lautet __main__, aber bei jeder verwendeten Lösung tritt __name__dieses Problem auf.
npjohns
quelle