web-dev-qa-db-fra.com

Journalisation Python (nom de fonction, nom de fichier, numéro de ligne) en utilisant un seul fichier

J'essaie d'apprendre comment une application fonctionne. Et pour cela, j'insère des commandes de débogage en tant que première ligne du corps de chaque fonction, l'objectif étant 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, comme cette application comprend de nombreux fichiers, je souhaite créer un fichier journal unique 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 souhaite pas utiliser le nom_fonction (pour pouvoir rapidement copier et coller un Log.info("Message") générique dans le corps de toutes les fonctions). Je sais que cela pourrait être fait en C en utilisant la macro __func__, mais je ne suis pas sûr du python.

  2. pour obtenir le nom de fichier et le numéro de ligne, j’ai vu (et je crois que) mon application utilise la fonction Python locals() mais avec une syntaxe dont je ne suis pas complètement au courant, par exemple: options = "LOG.debug('%(flag)s : %(flag_get)s' % locals()) et que j’ai essayé en utilisant LOG.info("My message %s" % locals()) qui produit {'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 consigner dans un fichier, mais je ne suis pas sûr qu'un seul fichier puisse être utilisé pour enregistrer tous les messages de journalisation dans le bon ordre des appels de fonctions du projet.

J'apprécierais grandement toute aide.

Merci!

82
user1126425

Vous avez quelques questions marginalement liées ici.

Je commencerai par le plus facile: (3). En utilisant logging, vous pouvez regrouper tous les appels dans un seul fichier journal ou dans une autre cible de sortie: ils se dérouleront dans l'ordre dans lequel ils se sont produits.

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 self dans la portée, qui contient une référence à l'instance actuelle. L'astuce utilisée est la mise en forme de la chaîne utilisant un dict comme RHS de l'opérateur %. "%(foo)s" % bar sera remplacé par quelle que soit la valeur de bar["foo"].

Enfin, vous pouvez utiliser certaines astuces d’introspection, similaires à celles utilisées par pdb, qui 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 passé, ainsi que le nom de la fonction (d'origine), le nom du fichier dans lequel la définition apparaît et la ligne de ce fichier. Regardez inspectez - Inspectez les objets vivants pour plus de détails.

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

21
Matthew Schinckel

La bonne réponse à cela est d’utiliser la variable funcName variable 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)

Ensuite, n'importe où, ajoutez simplement:

logger.debug('your message') 

Exemple de sortie d'un script sur lequel je travaille actuellement:

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

funcname, linename et lineno fournissent des informations sur la dernière fonction ayant effectué la journalisation.

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

Pour connaître les autres appelants de la pile d'appels, vous pouvez effectuer les tâches suivantes:

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(__+ '.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)
0
DeveScie