Python-Protokollierung (Funktionsname, Dateiname, Zeilennummer) mit einer einzelnen Datei

108

Ich versuche zu lernen, wie eine Anwendung funktioniert. Und dafür füge ich Debug-Befehle als erste Zeile des Funktionskörpers ein, mit dem Ziel, den Namen der Funktion sowie die Zeilennummer (innerhalb des Codes) zu protokollieren, unter der ich eine Nachricht an die Protokollausgabe sende. Da diese Anwendung aus vielen Dateien besteht, möchte ich eine einzelne Protokolldatei erstellen, damit ich den Kontrollfluss der Anwendung besser verstehen kann.

Folgendes weiß ich:

  1. Zum Abrufen des Funktionsnamens kann function_name.__name__ich den Funktionsnamen verwenden , möchte ihn jedoch nicht verwenden (damit ich schnell ein Generikum kopieren und Log.info("Message")in den Hauptteil aller Funktionen einfügen kann). Ich weiß, dass dies in C mit einem __func__Makro möglich ist, bin mir aber bei Python nicht sicher.

  2. Um den Dateinamen und die Zeilennummer zu erhalten, habe ich gesehen, dass (und ich glaube das) meine Anwendung die Python- locals()Funktion verwendet, aber in einer Syntax, die mir nicht vollständig bekannt ist, zB: options = "LOG.debug('%(flag)s : %(flag_get)s' % locals())und ich habe es mit like versucht, LOG.info("My message %s" % locals())was so etwas erzeugt {'self': <__main__.Class_name object at 0x22f8cd0>}. Irgendwelche Eingaben dazu bitte?

  3. Ich weiß, wie man die Protokollierung verwendet und einen Handler hinzufügt, um in einer Datei zu protokollieren, bin mir jedoch nicht sicher, ob eine einzelne Datei zum Aufzeichnen aller Protokollnachrichten in der richtigen Reihenfolge der Funktionsaufrufe im Projekt verwendet werden kann.

Ich würde mich über jede Hilfe sehr freuen.

Vielen Dank!

user1126425
quelle
Sie können den Python-Debugger mithilfe von verwenden import pdb; pdb.set_trace()und dann den Code interaktiv durchlaufen. Dies kann Ihnen helfen, den Programmfluss zu verfolgen.
Matthew Schinckel
Großartige Idee! Danke Matt. Es wäre immer noch hilfreich, ein Protokoll zu erhalten, wie in der Frage erwähnt, damit ich nicht jedes Mal debuggen muss. Kennen Sie auch eine IDE für Python, die so gut ist wie Eclipse für Java (Strg + Klick führt Sie zur Funktionsdefinition), die ich verwenden kann, um das Debuggen zu vereinfachen?
user1126425

Antworten:

27

Sie haben hier einige geringfügig verwandte Fragen.

Ich beginne mit dem einfachsten: (3). Mit können loggingSie alle Aufrufe zu einer einzelnen Protokolldatei oder einem anderen Ausgabeziel zusammenfassen: Sie befinden sich in der Reihenfolge, in der sie im Prozess aufgetreten sind.

Als nächstes: (2). locals()liefert ein Diktat des aktuellen Umfangs. In einer Methode ohne andere Argumente haben Sie also einen selfGültigkeitsbereich, der einen Verweis auf die aktuelle Instanz enthält. Der Trick, der Sie verblüfft, ist die Zeichenfolgenformatierung mit einem Diktat als RHS des %Operators. "%(foo)s" % barwird durch den Wert von ersetzt bar["foo"].

Schließlich können Sie einige Introspektionstricks verwenden, die denen ähneln, mit denen Sie pdbweitere Informationen protokollieren können:

def autolog(message):
    "Automatically log the current function details."
    import inspect, logging
    # Get the previous frame in the stack, otherwise it would
    # be this function!!!
    func = inspect.currentframe().f_back.f_code
    # Dump the message + the name of this function to the log.
    logging.debug("%s: %s in %s:%i" % (
        message, 
        func.co_name, 
        func.co_filename, 
        func.co_firstlineno
    ))

Dadurch werden die übergebene Nachricht sowie der (ursprüngliche) Funktionsname, der Dateiname, in dem die Definition angezeigt wird, und die Zeile in dieser Datei protokolliert. Schauen Sie sich inspect an - Inspizieren Sie lebende Objekte auf weitere Details.

Wie bereits in meinem Kommentar erwähnt, können Sie jederzeit in eine pdbinteraktive Debugging-Eingabeaufforderung wechseln, indem Sie die Zeile einfügen import pdb; pdb.set_trace()und Ihr Programm erneut ausführen. Auf diese Weise können Sie den Code schrittweise durchlaufen und die Daten nach Belieben überprüfen.

Matthew Schinckel
quelle
Danke Matt! Ich werde diese Autolog-Funktion ausprobieren. Ich habe ein wenig Verwirrung hinsichtlich der Verwendung von dict als RHS des% -Operators: Würde ich '%(foo)s : %(bar)s'auch den bar["foo"]Wert des ' drucken ? Oder ist es etwas anders als Ihr Beispiel?
user1126425
Grundsätzlich wird alles in der Form %(<foo>)sdurch den Wert des Objekts ersetzt, auf das im Diktat von verwiesen wird <foo>. Weitere Beispiele / Details finden Sie unter docs.python.org/library/stdtypes.html#string-formatting
Matthew Schinckel
3
Die Antwort von @synthesizerpatel ist viel hilfreicher.
Jan
501

Die richtige Antwort hierfür ist die Verwendung der bereits bereitgestellten funcNameVariablen

import logging
logger = logging.getLogger('root')
FORMAT = "[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s"
logging.basicConfig(format=FORMAT)
logger.setLevel(logging.DEBUG)

Dann, wo immer Sie wollen, fügen Sie einfach hinzu:

logger.debug('your message') 

Beispielausgabe eines Skripts, an dem ich gerade arbeite:

[invRegex.py:150 -          handleRange() ] ['[A-Z]']
[invRegex.py:155 -     handleRepetition() ] [[<__main__.CharacterRangeEmitter object at 0x10ba03050>, '{', '1', '}']]
[invRegex.py:197 -          handleMacro() ] ['\\d']
[invRegex.py:155 -     handleRepetition() ] [[<__main__.CharacterRangeEmitter object at 0x10ba03950>, '{', '1', '}']]
[invRegex.py:210 -       handleSequence() ] [[<__main__.GroupEmitter object at 0x10b9fedd0>, <__main__.GroupEmitter object at 0x10ba03ad0>]]
Synthesizerpatel
quelle
59
Das hätte die Antwort sein sollen!
user3885927
1
Großartig. Eine Sache, die hinzugefügt werden muss: Können wir die Protokolldatei so benennen, dass sie dynamisch mit der Codedatei identisch ist? Beispiel: Ich habe versucht, logging.basicConfig (Dateiname = "% (Dateiname)", Format = FORMAT), um den Dateinamen dynamisch zu übernehmen, aber es wurde ein statischer Wert verwendet. irgendein Vorschlag?
Ausreißer
2
@Outlier Nein, der empfohlene Weg, dies zu erreichen, ist viagetLogger(__name__)
farthVader
2
Ich habe eine Frage: In Java habe ich irgendwo gelesen, dass das Drucken der Zeilennummer nicht empfohlen wird, da es zusätzliche Zeit benötigt, um herauszufinden, von welcher Zeile aus der Logger aufgerufen wird. In Python ist das nicht wahr?
McSonk
2
Irrelevant, aber logging.getLogger('root')wahrscheinlich nicht das root, was Sie erwarten, es ist nicht der Logger, sondern ein gewöhnlicher Logger mit dem Namen 'root'.
0xc0de
5

funcname, linenameUnd linenoInformationen über die letzte Funktion zur Verfügung stellen, die die Protokollierung taten.

Wenn Sie einen Wrapper für Logger haben (z. B. Singleton-Logger), funktioniert die Antwort von @ synthetizerpatel möglicherweise nicht für Sie.

Um die anderen Anrufer im Aufrufstapel herauszufinden, haben Sie folgende Möglichkeiten:

import logging
import inspect

class Singleton(type):
    _instances = {}

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

class MyLogger(metaclass=Singleton):
    logger = None

    def __init__(self):
        logging.basicConfig(
            level=logging.INFO,
            format="%(asctime)s - %(threadName)s - %(message)s",
            handlers=[
                logging.StreamHandler()
            ])

        self.logger = logging.getLogger(__name__ + '.logger')

    @staticmethod
    def __get_call_info():
        stack = inspect.stack()

        # stack[1] gives previous function ('info' in our case)
        # stack[2] gives before previous function and so on

        fn = stack[2][1]
        ln = stack[2][2]
        func = stack[2][3]

        return fn, func, ln

    def info(self, message, *args):
        message = "{} - {} at line {}: {}".format(*self.__get_call_info(), message)
        self.logger.info(message, *args)
SpiralDev
quelle
1
Ihre Antwort war genau das, was ich brauchte, um mein Problem zu lösen. Danke dir.
Fehler - Syntaktische Reue
Da Python 3.8, die loggingSkipping - Klasse unterstützt Stapelebene out-of-the-box: Methoden wie log(), debug()etc. nun ein akzeptieren stacklevelArgument. Siehe die Dokumente .
Amain