Python Logging (nome funzione, nome file, numero di riga) utilizzando un singolo file


109

Sto cercando di imparare come funziona un'applicazione. E per questo sto inserendo i comandi di debug come prima riga del corpo di ogni funzione con l'obiettivo di registrare il nome della funzione e il numero di riga (all'interno del codice) in cui invio un messaggio all'output del log. Infine, poiché questa applicazione comprende molti file, desidero creare un unico file di registro in modo da poter comprendere meglio il flusso di controllo dell'applicazione.

Ecco quello che so:

  1. per ottenere il nome della funzione, posso usare function_name.__name__ma non voglio usare function_name (in modo da poter copiare e incollare rapidamente un generico Log.info("Message")nel corpo di tutte le funzioni). So che questo potrebbe essere fatto in C usando la __func__macro ma non sono sicuro di Python.

  2. per ottenere il nome del file e il numero di riga, ho visto che (e credo che) la mia applicazione utilizza la locals()funzione Python ma in una sintassi di cui non sono completamente a conoscenza, ad esempio: options = "LOG.debug('%(flag)s : %(flag_get)s' % locals())e l'ho provato usando like LOG.info("My message %s" % locals())che produce qualcosa di simile {'self': <__main__.Class_name object at 0x22f8cd0>}. Qualche input su questo per favore?

  3. So come utilizzare la registrazione e aggiungere un gestore per accedere a un file, ma non sono sicuro che un singolo file possa essere utilizzato per registrare tutti i messaggi di registro nell'ordine corretto delle chiamate di funzione nel progetto.

Apprezzerei davvero qualsiasi aiuto disponibile.

Grazie!


È possibile accedere al debugger Python utilizzando import pdb; pdb.set_trace()e quindi eseguire il passaggio del codice in modo interattivo. Questo può aiutarti a tracciare il flusso del programma.
Matthew Schinckel,

Grande idea! Grazie Matt. Sarebbe comunque utile ottenere un registro come menzionato nella domanda in modo da non dover eseguire il debug ogni volta. Inoltre, conosci un IDE per python che è buono come Eclipse per Java (ctrl + clic ti porta alla definizione della funzione) che posso utilizzare per rendere più facile il debug?
user1126425

Risposte:


28

Hai alcune domande marginalmente correlate qui.

Inizierò con il più semplice: (3). Utilizzando loggingè possibile aggregare tutte le chiamate a un singolo file di registro o altra destinazione di output: saranno nell'ordine in cui si sono verificate nel processo.

Avanti: (2). locals()fornisce un dettato dell'ambito corrente. Pertanto, in un metodo che non ha altri argomenti, hai selfin ambito, che contiene un riferimento all'istanza corrente. Il trucco utilizzato che ti lascia perplesso è la formattazione delle stringhe utilizzando un dict come RHS %dell'operatore. "%(foo)s" % barsarà sostituito da qualunque sia il valore di bar["foo"].

Infine, puoi utilizzare alcuni trucchi di introspezione, simili a quelli usati da pdbche possono registrare più informazioni:

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
    ))

Questo registrerà il messaggio passato, più il nome della funzione (originale), il nome del file in cui appare la definizione e la riga in quel file. Dai un'occhiata a Inspect - Ispeziona oggetti vivi per maggiori dettagli.

Come accennato in precedenza nel mio commento, puoi anche accedere a un pdbprompt di debug interattivo in qualsiasi momento inserendo la riga import pdb; pdb.set_trace()e rieseguendo il programma. Ciò consente di scorrere il codice, ispezionando i dati come si sceglie.


Grazie Matt! Proverò questa funzione di autolog. Ho un po 'di confusione riguardo all'uso di dict come RHS dell'operatore%: '%(foo)s : %(bar)s'stamperebbe anche il bar["foo"]valore di? O è un po 'diverso dal tuo esempio?
user1126425

Fondamentalmente, tutto il modulo %(<foo>)sviene sostituito dal valore dell'oggetto a cui fa riferimento nel dict da <foo>. Ci sono altri esempi / dettagli su docs.python.org/library/stdtypes.html#string-formatting
Matthew Schinckel

3
La risposta di @synthesizerpatel è molto più utile.
Jan

504

La risposta corretta è utilizzare la funcNamevariabile già fornita

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

Quindi, ovunque tu voglia, aggiungi semplicemente:

logger.debug('your message') 

Output di esempio da uno script su cui sto lavorando in questo momento:

[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>]]

61
Questa avrebbe dovuto essere la risposta!
user3885927

1
Fantastico .. Una cosa da aggiungere, possiamo denominare il file di log in modo che sia lo stesso del file di codice dinamicamente? es: ho provato a logging.basicConfig (filename = "% (filename)", format = FORMAT) per prendere dinamicamente il nome del file, ma ha assunto un valore statico. qualche suggerimento?
Valori anomali

2
@Outlier No, il modo consigliato per ottenerlo è tramitegetLogger(__name__)
farthVader

2
Ho una domanda: in Java da qualche parte ho letto che la stampa del numero di riga è scoraggiata in quanto ci vuole più tempo per capire da quale riga viene chiamato il logger. In Python questo non è vero?
McSonk

2
Irrilevante, ma logging.getLogger('root')probabilmente non è quello che ti aspetti, non è il rootlogger, ma un normale logger con nome "root".
0xc0de

5

funcname, linenameE linenofornire informazioni su l'ultima funzione che ha fatto la registrazione.

Se hai il wrapper di logger (ad es. Singleton logger), la risposta di @ synthesizerpatel potrebbe non funzionare per te.

Per scoprire gli altri chiamanti nello stack di chiamate puoi fare:

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)

1
La tua risposta era esattamente ciò di cui avevo bisogno per risolvere il mio problema. Grazie.
Errore - Rimorso sintattico

Dal momento che Python 3.8, i loggingsupporti di classe pila livello saltare out-of-the-box: metodi come log(), debug()ecc ora accettare un stacklevelargomento. Vedi i documenti .
amain
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.