Journalisation Python (nom de fonction, nom de fichier, numéro de ligne) à l'aide d'un seul fichier

109

J'essaie d'apprendre comment fonctionne une application. Et pour cela, j'insère des commandes de débogage comme première ligne du corps de chaque fonction dans le but de consigner le nom de la fonction ainsi que le numéro de ligne (dans le code) où j'envoie un message à la sortie du journal. Enfin, puisque cette application comprend de nombreux fichiers, je souhaite créer un seul fichier journal afin de mieux comprendre le flux de contrôle de l'application.

Voici ce que je sais:

  1. pour obtenir le nom de la fonction, je peux utiliser function_name.__name__mais je ne veux pas utiliser le nom_fonction (afin que je puisse rapidement copier et coller un générique Log.info("Message")dans le corps de toutes les fonctions). Je sais que cela pourrait être fait en C en utilisant une __func__macro, mais je ne suis pas sûr de python.

  2. pour obtenir le nom de fichier et le numéro de ligne, j'ai vu que (et je crois que) mon application utilise la locals()fonction Python mais dans une syntaxe dont je ne suis pas complètement conscient, par exemple: options = "LOG.debug('%(flag)s : %(flag_get)s' % locals())et je l'ai essayé en utilisant like LOG.info("My message %s" % locals())qui produit quelque chose comme {'self': <__main__.Class_name object at 0x22f8cd0>}. Des commentaires à ce sujet s'il vous plaît?

  3. Je sais comment utiliser la journalisation et y ajouter un gestionnaire pour me connecter à un fichier, mais je ne suis pas sûr qu'un seul fichier puisse être utilisé pour enregistrer tous les messages de journal dans l'ordre correct des appels de fonction dans le projet.

J'apprécierais grandement toute aide.

Merci!

user1126425
la source
Vous pouvez accéder au débogueur Python à l'aide de import pdb; pdb.set_trace(), puis parcourir le code de manière interactive. Cela peut vous aider à suivre le déroulement du programme.
Matthew Schinckel
Bonne idée! Merci Matt. Il serait toujours utile d'obtenir un journal comme mentionné dans la question afin de ne pas avoir à déboguer à chaque fois. Aussi, connaissez-vous un IDE pour python qui est aussi bon qu'Eclipse pour Java (ctrl + clic vous amène à la définition de fonction) que je peux utiliser pour faciliter le débogage?
user1126425

Réponses:

28

Vous avez ici quelques questions marginales.

Je vais commencer par le plus simple: (3). En utilisant, loggingvous pouvez regrouper tous les appels vers un seul fichier journal ou une autre cible de sortie: ils seront dans l'ordre dans lequel ils se sont produits dans le processus.

Ensuite: (2). locals()fournit un dict de la portée actuelle. Ainsi, dans une méthode qui n'a pas d'autres arguments, vous avez selfdans la portée, qui contient une référence à l'instance actuelle. L'astuce utilisée qui vous surprend est le formatage de la chaîne en utilisant un dict comme RHS de l' %opérateur. "%(foo)s" % barsera remplacé par quelle que soit la valeur de bar["foo"]is.

Enfin, vous pouvez utiliser des astuces d'introspection, similaires à celles utilisées par pdbqui peuvent enregistrer plus d'informations:

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

Cela enregistrera le message transmis, plus le nom de la fonction (d'origine), le nom de fichier dans lequel la définition apparaît et la ligne dans ce fichier. Jetez un œil à inspect - Inspectez les objets vivants pour plus de détails.

Comme je l'ai mentionné dans mon commentaire plus tôt, vous pouvez également accéder à une pdbinvite de débogage interactive à tout moment en insérant la ligne import pdb; pdb.set_trace()et en réexécutant votre programme. Cela vous permet de parcourir le code en inspectant les données à votre guise.

Matthew Schinckel
la source
Merci Matt! Je vais essayer cette fonction autolog. J'ai un peu de confusion concernant l 'utilisation de dict comme RHS de l' opérateur%: '%(foo)s : %(bar)s'serait également imprimer la bar["foo"]valeur de s? Ou est-ce quelque peu différent de votre exemple?
user1126425
Fondamentalement, tout le formulaire %(<foo>)sest remplacé par la valeur de l'objet référencé dans le dict par <foo>. Il y a plus d'exemples / détails sur docs.python.org/library/stdtypes.html#string-formatting
Matthew Schinckel
3
La réponse de @synthesizerpatel est beaucoup plus utile.
Jan
505

La bonne réponse pour cela est d'utiliser la funcNamevariable déjà fournie

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

Puis où vous voulez, ajoutez simplement:

logger.debug('your message') 

Exemple de sortie d'un script sur lequel je travaille en ce moment:

[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>]]
synthétiseurpatel
la source
61
Cela aurait dû être la réponse!
user3885927
1
Excellent .. Une chose à ajouter, pouvons-nous nommer le fichier journal pour qu'il soit le même que le fichier de code de manière dynamique? par exemple: j'ai essayé logging.basicConfig (filename = "% (filename)", format = FORMAT) pour prendre le nom de fichier dynamiquement, mais il a pris une valeur statique. toute suggestion?
Outlier
2
@Outlier Non, le moyen recommandé pour y parvenir est viagetLogger(__name__)
farthVader
2
J'ai une question: en Java, j'ai lu quelque part que l'impression du numéro de ligne est déconseillée car il faut plus de temps pour déterminer à partir de quelle ligne le logger est appelé. En python, ce n'est pas vrai?
McSonk
2
logging.getLogger('root')Non pertinent, mais ce n'est probablement pas ce à quoi vous vous attendez, ce n'est pas l' rootenregistreur, mais un enregistreur ordinaire avec le nom «root».
0xc0de
5

funcname, linenameEt linenofournir des informations sur la dernière fonction qui a fait l'enregistrement.

Si vous avez un wrapper de logger (par exemple un logger singleton), la réponse de @ synthesizerpatel pourrait ne pas fonctionner pour vous.

Pour connaître les autres appelants dans la pile d'appels, vous pouvez faire:

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
la source
1
Votre réponse était exactement ce dont j'avais besoin pour résoudre mon problème. Je vous remercie.
Erreur - Remords syntaxiques
Depuis Python 3.8, le loggingniveau de soutien de classe à sauter hors de la boîte: méthodes comme log(), debug(), etc. acceptent maintenant un stacklevelargument. Consultez la documentation .
amain le