Excepción de registro con rastreo


152

¿Cómo puedo registrar mis errores de Python?

try:
    do_something()
except:
    # How can I log my exception here, complete with its traceback?

Respuestas:


203

Use logging.exceptiondesde el except:controlador / bloque para registrar la excepción actual junto con la información de rastreo, precedida por un mensaje.

import logging
LOG_FILENAME = '/tmp/logging_example.out'
logging.basicConfig(filename=LOG_FILENAME, level=logging.DEBUG)

logging.debug('This message should go to the log file')

try:
    run_my_stuff()
except:
    logging.exception('Got exception on main handler')
    raise

Ahora mirando el archivo de registro /tmp/logging_example.out:

DEBUG:root:This message should go to the log file
ERROR:root:Got exception on main handler
Traceback (most recent call last):
  File "/tmp/teste.py", line 9, in <module>
    run_my_stuff()
NameError: name 'run_my_stuff' is not defined

1
Revisé el código de Django para esto, y supongo que la respuesta es no, pero ¿hay alguna manera de limitar el rastreo a una cierta cantidad de caracteres o profundidad? El problema es que para rastreos grandes, lleva bastante tiempo.
Eduard Luca

10
Tenga en cuenta que si define un registrador con logger = logging.getLogger('yourlogger')usted, debe escribir logger.exception('...')para que esto funcione ...
576i

¿Podemos modificar esto para que el mensaje se imprima con el nivel de registro INFO?
NM

Tenga en cuenta que para ciertas aplicaciones externas, como Azure Insight, el trackback no se almacena en los registros. Luego es necesario pasarlos explícitamente a la cadena del mensaje como se muestra a continuación.
Edgar H

138

Las exc_infoopciones de uso pueden ser mejores, sigue siendo advertencia o título de error:

try:
    # coode in here
except Exception as e:
    logging.error(e, exc_info=True)

Nunca recuerdo cómo exc_info=se llama el kwarg; ¡Gracias!
berto

44
Esto es idéntico a logging.exception, con la excepción de que el tipo se registra de forma redundante dos veces. Simplemente use logging.exception a menos que desee un nivel diferente al error.
Wyrmwood

@Wyrmwood no es idéntica a la que tiene que suministrar un mensaje alogging.exception
Peter Wood

57

Mi trabajo recientemente me encargó registrar todas las trazas / excepciones de nuestra aplicación. Probé numerosas técnicas que otros habían publicado en línea, como la anterior, pero me decidí por un enfoque diferente. Primordial traceback.print_exception.

Tengo un escrito en http://www.bbarrows.com/ Eso sería mucho más fácil de leer, pero también lo pegaré aquí.

Cuando tuve la tarea de registrar todas las excepciones que nuestro software podría encontrar en la naturaleza, probé varias técnicas diferentes para registrar nuestros rastreos de excepciones de Python. Al principio pensé que el gancho de excepción del sistema python, sys.excepthook, sería el lugar perfecto para insertar el código de registro. Estaba intentando algo similar a:

import traceback
import StringIO
import logging
import os, sys

def my_excepthook(excType, excValue, traceback, logger=logger):
    logger.error("Logging an uncaught exception",
                 exc_info=(excType, excValue, traceback))

sys.excepthook = my_excepthook  

Esto funcionó para el hilo principal, pero pronto descubrí que my sys.excepthook no existiría en ningún hilo nuevo que mi proceso comenzó. Este es un gran problema porque casi todo sucede en subprocesos en este proyecto.

Después de buscar en Google y leer mucha documentación, la información más útil que encontré fue del rastreador de problemas de Python.

La primera publicación en el hilo muestra un ejemplo funcional de que sys.excepthookNO persiste en los hilos (como se muestra a continuación). Aparentemente este es el comportamiento esperado.

import sys, threading

def log_exception(*args):
    print 'got exception %s' % (args,)
sys.excepthook = log_exception

def foo():
    a = 1 / 0

threading.Thread(target=foo).start()

Los mensajes en este hilo de Python Issue realmente resultan en 2 hacks sugeridos. Puede subclasificar Thready envolver el método de ejecución en nuestro propio intento, excepto el bloque para capturar y registrar excepciones o el parche de mono threading.Thread.runpara ejecutar en su propio intento, excepto bloquear y registrar las excepciones.

El primer método de subclasificación Threadme parece menos elegante en su código, ya que tendría que importar y usar su Threadclase personalizada EN TODAS PARTES donde quisiera tener un hilo de registro. Esto terminó siendo una molestia porque tuve que buscar en toda nuestra base de código y reemplazar todo lo normal Threadscon esta costumbre Thread. Sin embargo, estaba claro qué Threadestaba haciendo esto y sería más fácil para alguien diagnosticar y depurar si algo salía mal con el código de registro personalizado. Un hilo de registro personalizado podría verse así:

class TracebackLoggingThread(threading.Thread):
    def run(self):
        try:
            super(TracebackLoggingThread, self).run()
        except (KeyboardInterrupt, SystemExit):
            raise
        except Exception, e:
            logger = logging.getLogger('')
            logger.exception("Logging an uncaught exception")

El segundo método de parchear mono threading.Thread.runes bueno porque podría ejecutarlo una vez justo después __main__e instrumentar mi código de registro en todas las excepciones. Sin embargo, los parches de mono pueden ser molestos para depurar, ya que cambia la funcionalidad esperada de algo. El parche sugerido del rastreador de problemas de Python fue:

def installThreadExcepthook():
    """
    Workaround for sys.excepthook thread bug
    From
http://spyced.blogspot.com/2007/06/workaround-for-sysexcepthook-bug.html

(https://sourceforge.net/tracker/?func=detail&atid=105470&aid=1230540&group_id=5470).
    Call once from __main__ before creating any threads.
    If using psyco, call psyco.cannotcompile(threading.Thread.run)
    since this replaces a new-style class method.
    """
    init_old = threading.Thread.__init__
    def init(self, *args, **kwargs):
        init_old(self, *args, **kwargs)
        run_old = self.run
        def run_with_except_hook(*args, **kw):
            try:
                run_old(*args, **kw)
            except (KeyboardInterrupt, SystemExit):
                raise
            except:
                sys.excepthook(*sys.exc_info())
        self.run = run_with_except_hook
    threading.Thread.__init__ = init

No fue hasta que comencé a probar mi registro de excepciones cuando me di cuenta de que estaba haciendo todo mal.

Para probar había colocado un

raise Exception("Test")

en algún lugar de mi código Sin embargo, envolver un método que llamó a este método fue un intento, excepto el bloque que imprimió el rastreo y se tragó la excepción. Esto fue muy frustrante porque vi que el rastreo se traía impreso a STDOUT pero no se registraba. Entonces decidí que un método mucho más fácil de registrar los rastreos era simplemente parchear el método que todo el código de Python usa para imprimir los mismos rastreos, traceback.print_exception. Terminé con algo similar a lo siguiente:

def add_custom_print_exception():
    old_print_exception = traceback.print_exception
    def custom_print_exception(etype, value, tb, limit=None, file=None):
        tb_output = StringIO.StringIO()
        traceback.print_tb(tb, limit, tb_output)
        logger = logging.getLogger('customLogger')
        logger.error(tb_output.getvalue())
        tb_output.close()
        old_print_exception(etype, value, tb, limit=None, file=None)
    traceback.print_exception = custom_print_exception

Este código escribe el rastreo en un Búfer de cadena y lo registra en ERROR de registro. Tengo un controlador de registro personalizado que configura el registrador 'customLogger' que toma los registros de nivel de ERROR y los envía a casa para su análisis.


2
Un enfoque bastante interesante. Una pregunta: add_custom_print_exceptionno parece estar en el sitio al que se vinculó, y en su lugar hay un código final bastante diferente allí. ¿Cuál dirías que es mejor / más final y por qué? ¡Gracias!
fantabolous

Gracias, gran respuesta!
101

Hay un error tipográfico de cortar y pegar. en la llamada delegada a old_print_exception, se debe pasar el límite y el archivo limit y file, no None - old_print_exception (etype, value, tb, limit, file)
Marvin

Para su último bloque de código, en lugar de inicializar un StringIO e imprimir la excepción, puede llamar logger.error(traceback.format_tb())(o format_exc () si también desea la información de excepción).
James

8

Puede registrar todas las excepciones no detectadas en el hilo principal asignando un controlador a sys.excepthook, quizás utilizando el exc_infoparámetro de las funciones de registro de Python :

import sys
import logging

logging.basicConfig(filename='/tmp/foobar.log')

def exception_hook(exc_type, exc_value, exc_traceback):
    logging.error(
        "Uncaught exception",
        exc_info=(exc_type, exc_value, exc_traceback)
    )

sys.excepthook = exception_hook

raise Exception('Boom')

Si los hilos de los usos del programa, sin embargo, a continuación, tenga en cuenta que los hilos creados usando threading.Threadvoluntad no de disparo sys.excepthookcuando se produce una excepción no capturada dentro de ellos, como se señala en Edición 1.230.540 de seguimiento de incidencias de Python. Se han sugerido algunos hacks para evitar esta limitación, como parches de mono Thread.__init__para sobrescribir self.runcon un runmétodo alternativo que envuelve el original en un trybloque y llama sys.excepthookdesde dentro del exceptbloque. Alternativamente, puede ajustar manualmente el punto de entrada para cada uno de sus hilos en try/ exceptusted mismo.


3

Los mensajes de excepción no capturados van a STDERR, por lo que, en lugar de implementar su registro en Python, podría enviar STDERR a un archivo utilizando el shell que esté utilizando para ejecutar su script Python. En un script Bash, puede hacer esto con la redirección de salida, como se describe en la guía BASH .

Ejemplos

Agregue errores al archivo, otra salida al terminal:

./test.py 2>> mylog.log

Sobrescribir archivo con salida STDOUT y STDERR intercalada:

./test.py &> mylog.log


1

Puede obtener el rastreo utilizando un registrador, en cualquier nivel (DEPURACIÓN, INFORMACIÓN, ...). Tenga en cuenta que usando logging.exception, el nivel es ERROR.

# test_app.py
import sys
import logging

logging.basicConfig(level="DEBUG")

def do_something():
    raise ValueError(":(")

try:
    do_something()
except Exception:
    logging.debug("Something went wrong", exc_info=sys.exc_info())
DEBUG:root:Something went wrong
Traceback (most recent call last):
  File "test_app.py", line 10, in <module>
    do_something()
  File "test_app.py", line 7, in do_something
    raise ValueError(":(")
ValueError: :(

EDITAR:

Esto también funciona (usando Python 3.6)

logging.debug("Something went wrong", exc_info=True)

1

Aquí hay una versión que usa sys.excepthook

import traceback
import sys

logger = logging.getLogger()

def handle_excepthook(type, message, stack):
     logger.error(f'An unhandled exception occured: {message}. Traceback: {traceback.format_tb(stack)}')

sys.excepthook = handle_excepthook

¿Qué tal usar en {traceback.format_exc()}lugar de {traceback.format_tb(stack)}?
variable

0

tal vez no tan elegante, pero más fácil:

#!/bin/bash
log="/var/log/yourlog"
/path/to/your/script.py 2>&1 | (while read; do echo "$REPLY" >> $log; done)

-1

Aquí hay un ejemplo simple tomado de la documentación de python 2.6 :

import logging
LOG_FILENAME = '/tmp/logging_example.out'
logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG,)

logging.debug('This message should go to the log file')

44
La pregunta era cómo registrar el rastreo
Konstantin Schubert
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.