¿Cómo registro un error de Python con información de depuración?

Estoy imprimiendo mensajes de excepción de Python en un archivo de registro con logging.error :

 import logging try: 1/0 except ZeroDivisionError as e: logging.error(e) # ERROR:root:division by zero 

¿Es posible imprimir información más detallada sobre la excepción y el código que la generó que solo la cadena de excepción? Cosas como los números de línea o los trazos de stack serían geniales.

logger.exception generará un seguimiento de stack junto con el mensaje de error.

Por ejemplo:

 import logging try: 1/0 except ZeroDivisionError as e: logging.exception("message") 

Salida:

 ERROR:root:message Traceback (most recent call last): File "", line 2, in  ZeroDivisionError: integer division or modulo by zero 

@Paulo Verifique las notas, “tenga en cuenta que en Python 3 debe llamar al método logging.exception justo dentro de la parte de except . Si llama a este método en un lugar arbitrario, puede obtener una extraña excepción. La documentación advierte sobre eso”.

Una cosa buena sobre logging.exception que la respuesta de SiggyF no muestra es que puede pasar un mensaje arbitrario, y el registro seguirá mostrando el rastreo completo con todos los detalles de la excepción:

 import logging try: 1/0 except ZeroDivisionError: logging.exception("Deliberate divide by zero traceback") 

Con el comportamiento de registro predeterminado (en versiones recientes) de solo imprimir errores a sys.stderr , se ve así:

 >>> import logging >>> try: ... 1/0 ... except ZeroDivisionError: ... logging.exception("Deliberate divide by zero traceback") ... ERROR:root:Deliberate divide by zero traceback Traceback (most recent call last): File "", line 2, in  ZeroDivisionError: integer division or modulo by zero 

Usar las opciones de exc_info puede ser mejor, para permitirte elegir el nivel de error (si usas una exception , siempre mostrará el error ):

 try: # do something here except Exception as e: logging.fatal(e, exc_info=True) # log exception info at FATAL log level 

Citando

¿Qué sucede si su aplicación registra de otra manera, sin utilizar el módulo de logging ?

Ahora, el traceback podría ser utilizado aquí.

 import traceback def log_traceback(ex, ex_traceback=None): if ex_traceback is None: ex_traceback = ex.__traceback__ tb_lines = [ line.rstrip('\n') for line in traceback.format_exception(ex.__class__, ex, ex_traceback)] exception_logger.log(tb_lines) 
  • Utilízalo en Python 2 :

     try: # your function call is here except Exception as ex: _, _, ex_traceback = sys.exc_info() log_traceback(ex, ex_traceback) 
  • Utilízalo en Python 3 :

     try: x = get_number() except Exception as ex: log_traceback(ex) 

Si usa registros simples, todos sus registros deben corresponder a esta regla: one record = one line . Siguiendo esta regla, puede usar grep y otras herramientas para procesar sus archivos de registro.

Pero la información de rastreo es multilínea. Así que mi respuesta es una versión extendida de la solución propuesta por zangw arriba en este hilo. El problema es que las líneas de rastreo podrían tener \n dentro, por lo que necesitamos hacer un trabajo adicional para deshacernos de los finales de esta línea:

 import logging logger = logging.getLogger('your_logger_here') def log_app_error(e: BaseException, level=logging.ERROR) -> None: e_traceback = traceback.format_exception(e.__class__, e, e.__traceback__) traceback_lines = [] for line in [line.rstrip('\n') for line in e_traceback]: traceback_lines.extend(line.splitlines()) logger.log(level, traceback_lines.__str__()) 

Después de eso (cuando esté analizando sus registros), podría copiar / pegar las líneas de rastreo requeridas de su archivo de registro y hacer esto:

 ex_traceback = ['line 1', 'line 2', ...] for line in ex_traceback: print(line) 

¡Lucro!

Esta respuesta se acumula a partir de los excelentes anteriores.

En la mayoría de las aplicaciones, no llamará a logging.exception (e) directamente. Lo más probable es que haya definido un registrador personalizado específico para su aplicación o módulo como este:

 # Set the name of the app or module my_logger = logging.getLogger('NEM Sequencer') # Set the log level my_logger.setLevel(logging.INFO) # Let's say we want to be fancy and log to a graylog2 log server graylog_handler = graypy.GELFHandler('some_server_ip', 12201) graylog_handler.setLevel(logging.INFO) my_logger.addHandler(graylog_handler) 

En este caso, solo use el registrador para llamar a la excepción (e) así:

 try: 1/0 except ZeroDivisionError, e: my_logger.exception(e) 

Un poco de tratamiento de decoración (muy poco inspirado en la mónada Maybe y el lifting). Puede eliminar de forma segura las anotaciones de tipo Python 3.6 y utilizar un estilo de formato de mensaje anterior.

fallible.py

 from functools import wraps from typing import Callable, TypeVar, Optional import logging A = TypeVar('A') def fallible(*exceptions, logger=None) \ -> Callable[[Callable[..., A]], Callable[..., Optional[A]]]: """ :param exceptions: a list of exceptions to catch :param logger: pass a custom logger; None means the default logger, False disables logging altogether. """ def fwrap(f: Callable[..., A]) -> Callable[..., Optional[A]]: @wraps(f) def wrapped(*args, **kwargs): try: return f(*args, **kwargs) except exceptions: message = f'called {f} with *args={args} and **kwargs={kwargs}' if logger: logger.exception(message) if logger is None: logging.exception(message) return None return wrapped return fwrap 

Manifestación:

 In [1] from fallible import fallible In [2]: @fallible(ArithmeticError) ...: def div(a, b): ...: return a / b ...: ...: In [3]: div(1, 2) Out[3]: 0.5 In [4]: res = div(1, 0) ERROR:root:called  with *args=(1, 0) and **kwargs={} Traceback (most recent call last): File "/Users/user/fallible.py", line 17, in wrapped return f(*args, **kwargs) File "", line 3, in div return a / b In [5]: repr(res) 'None' 

También puede modificar esta solución para devolver algo un poco más significativo que None de la parte de except (o incluso hacer que la solución sea genérica, especificando este valor de retorno en los argumentos de fallible ).

Si puede hacer frente a la dependencia adicional y luego usar twisted.log, no tiene que registrar explícitamente los errores y también devuelve todo el seguimiento y la hora al archivo o al flujo.

Una forma limpia de hacerlo es usar format_exc() y luego analizar la salida para obtener la parte relevante:

 from traceback import format_exc try: 1/0 except Exception: print 'the relevant part is: '+format_exc().split('\n')[-2] 

Saludos