Registro de excepciones no capturadas en Python

181

¿Cómo se generan excepciones no capturadas a través del loggingmódulo en lugar de hacerlo stderr?

Me doy cuenta de que la mejor manera de hacer esto sería:

try:
    raise Exception, 'Throwing a boring exception'
except Exception, e:
    logging.exception(e)

Pero mi situación es tal que sería realmente agradable si logging.exception(...)se invocara automáticamente cada vez que no se detectara una excepción.

Jacob Marble
fuente

Respuestas:

143

Como señaló Ned, sys.excepthookse invoca cada vez que se genera una excepción y no se detecta. La implicación práctica de esto es que en su código puede anular el comportamiento predeterminado de sys.excepthookhacer lo que quiera (incluido el uso logging.exception).

Como ejemplo de hombre de paja:

>>> import sys
>>> def foo(exctype, value, tb):
...     print 'My Error Information'
...     print 'Type:', exctype
...     print 'Value:', value
...     print 'Traceback:', tb
... 

Anular sys.excepthook:

>>> sys.excepthook = foo

Comete un error de sintaxis obvio (omita los dos puntos) y obtenga información de error personalizada:

>>> def bar(a, b)
My Error Information
Type: <type 'exceptions.SyntaxError'>
Value: invalid syntax (<stdin>, line 1)
Traceback: None

Para obtener más información acerca de sys.excepthook, lea los documentos .

Jacinda
fuente
44
@Codemonkey No es una palabra clave reservada, es un nombre de tipo preexistente. Puede usarlo typecomo argumento de función, aunque los IDE se quejarán por ocultar lo global type(al igual que usarlo var self = thisen Javascript). Realmente no importa a menos que necesite acceder al typeobjeto dentro de su función, en cuyo caso puede usarlo type_como argumento.
Ryan P
3
La frase "cada vez" aquí es engañosa :: "sys.excepthook se invoca cada vez que se genera una excepción y no se detecta" ... porque en un programa, puede haber exactamente una excepción "no detectada". Además, sys.excepthookNO se llama cuando se genera una excepción. Se llama cuando el programa va a terminar debido a una excepción no detectada, que no puede suceder más de una vez.
Nawaz
2
@Nawaz: puede suceder más de una vez en un REPL
jfs
2
@Nawaz También puede suceder varias veces si el programa usa subprocesos. También parece que los bucles de eventos de la GUI (como Qt) siguen ejecutándose, aunque la excepción ha llegado a sys.excepthook
three_pineapples
1
Cualquiera que intente probar el código anterior, asegúrese de generar un error de rastreo mientras prueba su función. SyntaxError no está siendo manejado por sys.excepthook. Puede usar print (1/0) y esto invocará la función que ha definido para anular sys.excepthook
Parth Karia
177

Aquí hay un pequeño ejemplo completo que también incluye algunos otros trucos:

import sys
import logging
logger = logging.getLogger(__name__)
handler = logging.StreamHandler(stream=sys.stdout)
logger.addHandler(handler)

def handle_exception(exc_type, exc_value, exc_traceback):
    if issubclass(exc_type, KeyboardInterrupt):
        sys.__excepthook__(exc_type, exc_value, exc_traceback)
        return

    logger.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback))

sys.excepthook = handle_exception

if __name__ == "__main__":
    raise RuntimeError("Test unhandled")
  • Ignorar KeyboardInterrupt para que un programa de consola de Python pueda salir con Ctrl + C.

  • Confíe completamente en el módulo de registro de Python para formatear la excepción.

  • Utilice un registrador personalizado con un controlador de ejemplo. Este cambia la excepción no controlada para ir a stdout en lugar de stderr, pero puede agregar todo tipo de controladores en este mismo estilo al objeto registrador.

gnu_lorien
fuente
13
Lo usaría logger.critical()dentro del controlador de excepción, ya que una excepción no detectada es bastante crítica, diría.
gitaarik
2
Esta es la respuesta más práctica de la OMI.
David Morales
@gnu_lorien gracias por el fragmento. ¿En qué archivo pondrías esto?
stelios
@chefarov El archivo principal donde inicializa todos los demás registros
gnu_lorien
Hola, ¿cómo podemos escribir en un archivo como debug.log esta información? Lo intento para agregar línea, logging.basicConfig(level=logging.DEBUG, filename="debug.log", format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')pero no me ayudó.
GurhanCagin
26

El método sys.excepthookse invocará si no se detecta una excepción: http://docs.python.org/library/sys.html#sys.excepthook

Cuando se genera una excepción y se detecta, el intérprete llama a sys.excepthook con tres argumentos, la clase de excepción, la instancia de excepción y un objeto de rastreo. En una sesión interactiva, esto sucede justo antes de que el control regrese a la solicitud; en un programa Python esto sucede justo antes de que el programa salga. El manejo de tales excepciones de nivel superior se puede personalizar asignando otra función de tres argumentos a sys.excepthook.

Ned Batchelder
fuente
2
¿Por qué envía la clase de excepción? ¿No siempre puedes obtener eso llamando typea la instancia?
Neil G
¿Cuál es el tipo de los parámetros de sys.excepthook?
Martin Thoma
23

Por qué no:

import sys
import logging
import traceback

def log_except_hook(*exc_info):
    text = "".join(traceback.format_exception(*exc_info))
    logging.error("Unhandled exception: %s", text)

sys.excepthook = log_except_hook

None()

Aquí está la salida sys.excepthookcomo se ve arriba:

$ python tb.py
ERROR:root:Unhandled exception: Traceback (most recent call last):
  File "tb.py", line 11, in <module>
    None()
TypeError: 'NoneType' object is not callable

Aquí está la salida con el sys.excepthookcomentario:

$ python tb.py
Traceback (most recent call last):
  File "tb.py", line 11, in <module>
    None()
TypeError: 'NoneType' object is not callable

La única diferencia es que la primera tiene ERROR:root:Unhandled exception:al principio de la primera línea.

Tiago Coutinho
fuente
Otra diferencia es que el primero escribe el seguimiento en el sistema de registro, por lo que se aplican todos los controladores y formateadores que haya instalado. Este último escribe directamente a sys.stderr.
radiaph
8

Para construir sobre la respuesta de Jacinda, pero usando un objeto logger:

def catchException(logger, typ, value, traceback):
    logger.critical("My Error Information")
    logger.critical("Type: %s" % typ)
    logger.critical("Value: %s" % value)
    logger.critical("Traceback: %s" % traceback)

# Use a partially applied function
func = lambda typ, value, traceback: catchException(logger, typ, value, traceback)
sys.excepthook = func
Miguel
fuente
2
Sería mejor usarlo en functools.partial()lugar de lambda. Ver: docs.python.org/2/library/functools.html#functools.partial
Mariusz Jamro
@MariuszJamro ¿por qué?
davr
4

Envuelva la llamada de entrada de la aplicación en un try...exceptbloque para que pueda capturar y registrar (y quizás volver a subir) todas las excepciones no detectadas. Por ejemplo, en lugar de:

if __name__ == '__main__':
    main()

Hacer esto:

if __name__ == '__main__':
    try:
        main()
    except Exception as e:
        logger.exception(e)
        raise
flaviovs
fuente
Esta no es la pregunta que se hace. La intención de la pregunta es preguntar qué hacer cuando la excepción NO se maneja mediante código.
Mayank Jaiswal
1
Bueno, Python es un lenguaje de programación, y esto implica que no hace las cosas "automáticamente" (como lo desea el OP), excepto si se le pide que lo haga. En otras palabras, no hay forma de registrar "automáticamente" todas las excepciones, a menos que lo codifique, y eso es lo que está en mi respuesta.
flaviovs
1
Bueno, si miras la respuesta de Ned Batchelder, hay algo llamado gancho de excepción. Debe definir en un lugar en su código y se manejan todas sus excepciones no detectadas.
Mayank Jaiswal
1
El enlace de excepción no cambia el hecho de que no es "automático" (en el sentido que quiere el OP); en otras palabras, todavía tiene que codificarlo. La respuesta de Ned (que usa el gancho de excepción) realmente aborda la pregunta original: es solo que, en mi opinión , la forma en que lo hace es mucho menos pitónica que la mía.
flaviovs
1
Depende más o menos de tus propios objetivos. Si programa para complacer al IDE, entonces la captura de todas las excepciones podría no ser una opción. Pero si desea manejar los errores con gracia y mostrar comentarios agradables al usuario, me temo que deberá detectar todas las excepciones. Ok, suficiente sarcasmo :-): si miras detenidamente, verás que el código intercepta la excepción, pero vuelve a subirlo, así que a menos que tu IDE esté haciendo algo "mágico" no debería estar haciendo, todavía obtendrá La excepción.
flaviovs
3

Tal vez podría hacer algo en la parte superior de un módulo que redirija stderr a un archivo y luego registrar ese archivo en la parte inferior

sock = open('error.log', 'w')               
sys.stderr = sock

doSomething() #makes errors and they will log to error.log

logging.exception(open('error.log', 'r').read() )
JiminyCricket
fuente
3

Aunque la respuesta de @ gnu_lorien me dio un buen punto de partida, mi programa falla en la primera excepción.

Llegué con una solución personalizada (y / o mejorada), que registra silenciosamente Excepciones de funciones decoradas @handle_error.

import logging

__author__ = 'ahmed'
logging.basicConfig(filename='error.log', level=logging.DEBUG)


def handle_exception(exc_type, exc_value, exc_traceback):
    import sys
    if issubclass(exc_type, KeyboardInterrupt):
        sys.__excepthook__(exc_type, exc_value, exc_traceback)
        return
    logging.critical(exc_value.message, exc_info=(exc_type, exc_value, exc_traceback))


def handle_error(func):
    import sys

    def __inner(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception, e:
            exc_type, exc_value, exc_tb = sys.exc_info()
            handle_exception(exc_type, exc_value, exc_tb)
        finally:
            print(e.message)
    return __inner


@handle_error
def main():
    raise RuntimeError("RuntimeError")


if __name__ == "__main__":
    for _ in xrange(1, 20):
        main()
guneysus
fuente
2

Para responder la pregunta del Sr.Zeus discutida en la sección de comentarios de la respuesta aceptada, utilizo esto para registrar excepciones no capturadas en una consola interactiva (probada con PyCharm 2018-2019). Descubrí sys.excepthookque no funciona en un shell de Python, así que busqué más y descubrí que podía usarlo sys.exc_info. Sin embargo, sys.exc_infono toma argumentos a diferencia de los sys.excepthookque toma 3 argumentos.

Aquí, uso ambos sys.excepthooky sys.exc_infopara registrar ambas excepciones en una consola interactiva y un script con una función de contenedor. Para adjuntar una función de enlace a ambas funciones, tengo dos interfaces diferentes dependiendo de si se dan argumentos o no.

Aquí está el código:

def log_exception(exctype, value, traceback):
    logger.error("Uncaught exception occurred!",
                 exc_info=(exctype, value, traceback))


def attach_hook(hook_func, run_func):
    def inner(*args, **kwargs):
        if not (args or kwargs):
            # This condition is for sys.exc_info
            local_args = run_func()
            hook_func(*local_args)
        else:
            # This condition is for sys.excepthook
            hook_func(*args, **kwargs)
        return run_func(*args, **kwargs)
    return inner


sys.exc_info = attach_hook(log_exception, sys.exc_info)
sys.excepthook = attach_hook(log_exception, sys.excepthook)

La configuración de registro se puede encontrar en la respuesta de gnu_lorien.

Nabs
fuente
2

En mi caso (usar python 3) cuando uso la respuesta de @Jacinda, el contenido del rastreo no se imprimió. En su lugar, sólo se imprime el objeto en sí mismo: <traceback object at 0x7f90299b7b90>.

En cambio hago:

import sys
import logging
import traceback

def custom_excepthook(exc_type, exc_value, exc_traceback):
    # Do not print exception when user cancels the program
    if issubclass(exc_type, KeyboardInterrupt):
        sys.__excepthook__(exc_type, exc_value, exc_traceback)
        return

    logging.error("An uncaught exception occurred:")
    logging.error("Type: %s", exc_type)
    logging.error("Value: %s", exc_value)

    if exc_traceback:
        format_exception = traceback.format_tb(exc_traceback)
        for line in format_exception:
            logging.error(repr(line))

sys.excepthook = custom_excepthook
vengativo
fuente