2 de Enero, 2012

Excepciones no capturadas

En el trabajo contribuimos a varios proyectos Open Source (uno de ellos liberado y mantenido por nosotros: sftpcloudfs), y muchos de ellos incluyen servicios en Python.

Para mi que vengo de C, las excepciones son esa característica del lenguaje que más amor/odio inspiran. Capturar todo suele ser mala idea, porque dependiendo del problema hay que dar una solución u otra, aunque hay veces que algunos módulos no son muy claros con sus excepciones.

Un ejemplo de eso son los ssl.SSLError que nos pueden caer en cualquier momento con httplib y otros módulos que implementan el mismo interfaz (como el interfaz Python para Cloud Files; que algún dolor de cabeza me ha dado :S).

Los servicios tienen la característica común de ser un daemon, es decir: una aplicación no interactiva que se ejecuta de fondo y no tiene una terminal asociada.

Este tipo de aplicaciones suelen llevar un registro en un fichero o vía syslog (el logger del sistema), pero las excepciones que normalmente vemos en la terminal cuando el intérprete de Python detecta una excepción que no ha sido capturada, se pierden.

Por mucho control de calidad que se haga a un servicio, es posible que aparezcan algunos errores en un entorno de trabajo real, que son precisamente muy complicados de reproducir. Para evitar que perdamos la información de esas excepciones no capturadas, podemos usar: sys.excepthook.

Podemos incluir fácilmente información sobre las excepciones en nuestro código de log de la siguiente forma:

import logging
import sys
import traceback

handler = logging.FileHandler('test.log')
log = logging.getLogger()
log.addHandler(handler)
sys.excepthook = lambda exc_type, exc_value, exc_traceback: \
    log.critical('UNCAUGHT EXCEPTION %r: %s' % (exc_type, traceback.format_tb(exc_traceback)))

raise KeyboardInterrupt()

Es un ejemplo muy sencillo que usa la funcionalidad de log de Python para registrar en un fichero las excepciones no capturadas (en el ejemplo un KeyboardInterrupt).

Además he usado el módulo traceback para dar formato al mensaje que registramos, en lugar de usar el ejemplo que viene en la documentación... porque usando el parámetro exc_info la estructura de los logs se puede fastidiar por los saltos de linea (¡sobretodo si usamos syslog!).

Este ejemplo genera entradas como:

UNCAUGHT EXCEPTION <type 'exceptions.KeyboardInterrupt'>: ['  File "test.py", line 11, in \n    raise KeyboardInterrupt()\n']

Por lo demás es muy sencillo, y nos puede servir como garantía de que cualquier problema no esperado en nuestro servicio aparecerá registrada en nuestro log, aunque la aplicación termine de forma imprevista ;).

Anotación por Juan J. Martínez, clasificada en: python.

Hay 2 comentarios

Gravatar

me suena ese código! ¿Pero tu entrada falta algo no? “People that dump a stack trace to syslog should die in a fire!” :D

por Andrew W, en 2012-01-07 00:24:02

Gravatar

Eso es :D

Si usamos exc_info para pasar la información de la excepción, las funciones de log escupen varias lineas para el traceback que resultan en algo bastante complicado de seguir en syslog :(

El ejemplo que comento en la anotación muestra solo una linea (con los saltos representados por '\n’), que es más adecuado ;).

por Juanjo, en 2012-01-07 09:50:39

Los comentarios están cerrados: los comentarios se cierran automáticamente una vez pasados 30 días. Si quieres comentar algo acerca de la anotación, puedes hacerlo por e-mail.

Algunas anotaciones relacionadas: