Registro de Python (nombre de función, nombre de archivo, número de línea) utilizando un solo archivo

109

Estoy tratando de aprender cómo funciona una aplicación. Y para esto, estoy insertando comandos de depuración como la primera línea del cuerpo de cada función con el objetivo de registrar el nombre de la función, así como el número de línea (dentro del código) donde envío un mensaje a la salida del registro. Finalmente, dado que esta aplicación se compone de muchos archivos, quiero crear un solo archivo de registro para poder comprender mejor el flujo de control de la aplicación.

Esto es lo que sé:

  1. para obtener el nombre de la función, puedo usar function_name.__name__pero no quiero usar el nombre_función (para poder copiar y pegar rápidamente un genérico Log.info("Message")en el cuerpo de todas las funciones). Sé que esto podría hacerse en C usando __func__macro, pero no estoy seguro de Python.

  2. para obtener el nombre del archivo y el número de línea, he visto que (y creo que) mi aplicación está usando la locals()función Python pero en una sintaxis de la que no estoy completamente consciente, por ejemplo: options = "LOG.debug('%(flag)s : %(flag_get)s' % locals())y lo intenté usando like LOG.info("My message %s" % locals())que produce algo como {'self': <__main__.Class_name object at 0x22f8cd0>}. ¿Alguna entrada sobre esto por favor?

  3. Sé cómo usar el registro y agregarle un controlador para registrarlo en un archivo, pero no estoy seguro de si se puede usar un solo archivo para registrar todos los mensajes de registro en el orden correcto de las llamadas a funciones en el proyecto.

Agradecería mucho cualquier ayuda.

¡Gracias!

usuario1126425
fuente
Puede ingresar al depurador de Python usando import pdb; pdb.set_trace()y luego recorrer el código de forma interactiva. Eso puede ayudarlo a rastrear el flujo del programa.
Matthew Schinckel
¡Gran idea! Gracias Matt. Aún sería útil obtener un registro como se menciona en la pregunta para no tener que depurar cada vez. Además, ¿conoce un IDE para python que sea tan bueno como Eclipse para Java (ctrl + clic lo lleva a la definición de función) que pueda utilizar para facilitar la depuración?
user1126425

Respuestas:

28

Tiene algunas preguntas marginalmente relacionadas aquí.

Empezaré con el más fácil: (3). Con loggingpuede agregar todas las llamadas a un solo archivo de registro u otro destino de salida: estarán en el orden en que ocurrieron en el proceso.

A continuación: (2). locals()proporciona un dictamen del alcance actual. Por lo tanto, en un método que no tiene otros argumentos, tiene un selfalcance, que contiene una referencia a la instancia actual. El truco que se está utilizando y que lo está confundiendo es el formato de cadena usando un dict como el RHS del %operador. "%(foo)s" % barserá reemplazado por el valor de bar["foo"].

Finalmente, puede usar algunos trucos de introspección, similares a los que usa para pdbregistrar más información:

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

Esto registrará el mensaje pasado, más el nombre de la función (original), el nombre del archivo en el que aparece la definición y la línea en ese archivo. Eche un vistazo a inspeccionar: inspeccionar objetos vivos para obtener más detalles.

Como mencioné en mi comentario anteriormente, también puede acceder a un pdbmensaje de depuración interactivo en cualquier momento insertando la línea import pdb; pdb.set_trace()y volviendo a ejecutar su programa. Esto le permite recorrer el código, inspeccionando los datos a su elección.

Matthew Schinckel
fuente
¡Gracias Matt! Probaré esta función de autolog. Tengo un poco de confusión con respecto al uso de dict como RHS del operador%: ¿ '%(foo)s : %(bar)s'también imprimiría el bar["foo"]valor de? ¿O es algo diferente a tu ejemplo?
user1126425
Básicamente, todo el formulario %(<foo>)sse reemplaza por el valor del objeto al que se hace referencia en el dict by <foo>. Hay más ejemplos / detalles en docs.python.org/library/stdtypes.html#string-formatting
Matthew Schinckel
3
La respuesta de @synthesizerpatel es mucho más útil.
Ene
503

La respuesta correcta para esto es usar la funcNamevariable ya proporcionada

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

Luego, en cualquier lugar que desee, simplemente agregue:

logger.debug('your message') 

Salida de ejemplo de un script en el que estoy trabajando en este 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>]]
sintetizador
fuente
61
¡Esta debería haber sido la respuesta!
user3885927
1
Genial ... Una cosa para agregar, ¿podemos nombrar el archivo de registro para que sea el mismo que el archivo de código de forma dinámica? Por ejemplo: intenté logging.basicConfig (filename = "% (filename)", format = FORMAT) para tomar el nombre del archivo de forma dinámica, pero tomó un valor estático. ¿cualquier sugerencia?
Valor atípico
2
@Outlier No, la forma recomendada de lograrlo es a través degetLogger(__name__)
farthVader
2
Tengo una pregunta: en algún lugar de Java, leí que no se recomienda imprimir el número de línea, ya que se necesita más tiempo para averiguar desde qué línea se llama al registrador. En Python esto no es cierto?
McSonk
2
Irrelevante, pero logging.getLogger('root')probablemente no es lo que espera, no es el rootregistrador, sino un registrador ordinario con el nombre 'root'.
0xc0de
5

funcname, linename Y linenoproporcionar información acerca de la última función que hizo el registro.

Si tiene un contenedor de registrador (por ejemplo, registrador singleton), entonces la respuesta de @ Synthesizerpatel podría no funcionar para usted.

Para averiguar las otras personas que llaman en la pila de llamadas, puede hacer lo siguiente:

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
fuente
1
Tu respuesta fue exactamente lo que necesitaba para resolver mi problema. Gracias.
Error - Remordimiento sintáctico
Como Python 3.8, los loggingsoportes clase de pila nivel de saltar fuera de la caja: métodos como log(), debug(), etc. ahora aceptar un stacklevelargumento. Consulte los documentos .
amain