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

470

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 números de línea o trazos de pila serían geniales.

probablemente en la playa
fuente

Respuestas:

735

logger.exception generará un seguimiento de la pila 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 "<stdin>", line 2, in <module>
ZeroDivisionError: integer division or modulo by zero

@Paulo Check señala: "tenga en cuenta que en Python 3 debe llamar al logging.exceptionmétodo justo dentro de la exceptparte. Si llama a este método en un lugar arbitrario, puede obtener una extraña excepción. Los documentos alertan sobre eso".

SiggyF
fuente
131
El exceptionmétodo simplemente llama error(message, exc_info=1). Tan pronto como pase exc_infoa cualquiera de los métodos de registro desde un contexto de excepción, obtendrá un rastreo.
Helmut Grohne
16
También puede configurar sys.excepthook(ver aquí ) para evitar tener que ajustar todo su código en try / except.
jul
23
Simplemente podrías escribir except Exception:porque no estás usando ede ninguna manera;)
Marco Ferrari
21
Es muy posible que desee inspeccionar ecuando intente depurar interactivamente su código. :) Por eso siempre lo incluyo.
Vicki Laidler
44
Corríjame si me equivoco, en este caso, no hay un manejo real de la excepción y, por lo tanto, tiene sentido agregar raiseal final del exceptalcance. De lo contrario, la ejecución continuará como si todo estuviera bien.
Dror
184

Una cosa buena acerca de logging.exceptionque la respuesta de SiggyF no se muestra es que puede pasar un mensaje arbitrario, y el registro aún mostrará 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 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 "<stdin>", line 2, in <module>
ZeroDivisionError: integer division or modulo by zero
ncoghlan
fuente
¿Se puede registrar una excepción sin proporcionar un mensaje?
Stevoisiak
@StevenVascellaro: te sugiero que pases ''si realmente no quieres escribir un mensaje ... sin embargo, la función no se puede invocar sin al menos un argumento, por lo que tendrás que darle algo.
ArtOfWarfare
147

El uso de exc_infoopciones puede ser mejor, para permitirle elegir el nivel de error (si lo usa exception, siempre estará en el errornivel):

try:
    # do something here
except Exception as e:
    logging.critical(e, exc_info=True)  # log exception info at CRITICAL log level
flycee
fuente
@CivFan: en realidad no miré las otras ediciones ni la introducción de la publicación; esa introducción también fue agregada por un editor de terceros. No veo en ninguna parte de los comentarios eliminados que esa sea la intención, pero también puedo deshacer mi edición y eliminar los comentarios, ya que ha pasado demasiado tiempo para que la votación haya sido por otra cosa que no sea la versión editada .
Martijn Pieters
¿Hay logging.fatalun método en la biblioteca de registro? Solo veo critical.
Ian
1
@Ian Es un alias para critical, al igual que lo warnes warning.
0xc0de
35

Citando

¿Qué sucede si su aplicación inicia sesión de alguna otra manera, sin usar el loggingmódulo?

Ahora, tracebackpodría usarse 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)
  • Úselo en Python 2 :

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

    try:
        x = get_number()
    except Exception as ex:
        log_traceback(ex)
zangw
fuente
¿Por qué colocó "_, _, ex_traceback = sys.exc_info ()" fuera de la función log_traceback y luego lo pasó como argumento? ¿Por qué no usarlo directamente dentro de la función?
Basil Musa
@BasilMusa, para responder su pregunta, en resumen, para que sea compatible con Python 3, porque ex_tracebackes de ex.__traceback__Python 3, pero ex_tracebackes de sys.exc_info()Python 2.
zangw
12

Si utiliza a logs - todos los registros de deben corresponder esta regla: one record = one line. Siguiendo esta regla, puede usar grepy otras herramientas para procesar sus archivos de registro.

Pero la información de rastreo es de varias líneas. Entonces 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 pueden tener \ndentro, por lo que debemos hacer un trabajo adicional para deshacernos de estas terminaciones de 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 analizará sus registros), podría copiar / pegar las líneas de rastreo requeridas desde su archivo de registro y hacer esto:

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

¡Lucro!

doomatel
fuente
9

Esta respuesta se acumula a partir de las 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, simplemente use el registrador para llamar a la excepción (e) así:

try:
    1/0
except ZeroDivisionError, e:
    my_logger.exception(e)
Será
fuente
Este es un toque final útil si desea un registrador dedicado solo para excepciones.
logicOnAbstraction
7

Puede registrar el seguimiento de la pila sin una excepción.

https://docs.python.org/3/library/logging.html#logging.Logger.debug

El segundo argumento de palabra clave opcional es stack_info, que por defecto es False. Si es verdadero, la información de la pila se agrega al mensaje de registro, incluida la llamada de registro real. Tenga en cuenta que esta no es la misma información de la pila que la que se muestra al especificar exc_info: la primera son los cuadros de la pila desde la parte inferior de la pila hasta la llamada de registro en el hilo actual, mientras que la segunda es información sobre los cuadros de la pila que se han desenrollado, después de una excepción, mientras busca controladores de excepciones.

Ejemplo:

>>> import logging
>>> logging.basicConfig(level=logging.DEBUG)
>>> logging.getLogger().info('This prints the stack', stack_info=True)
INFO:root:This prints the stack
Stack (most recent call last):
  File "<stdin>", line 1, in <module>
>>>
Baczek
fuente
5

Un poco de tratamiento de decorador (muy poco inspirado en la mónada tal vez y el levantamiento). Puede eliminar de forma segura las anotaciones de tipo Python 3.6 y usar un estilo de formato de mensaje anterior.

falible.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 <function div at 0x10d3c6ae8> 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 "<ipython-input-17-e056bd886b5c>", 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 el Nonede la exceptparte (o incluso hacer que la solución sea genérica, especificando este valor de retorno en falliblelos argumentos).

Eli Korvigo
fuente
0

En su módulo de registro (si es un módulo personalizado) simplemente habilite stack_info.

api_logger.exceptionLog("*Input your Custom error message*",stack_info=True)
Piyush Kumar
fuente
-1

Si puede hacer frente a la dependencia adicional, use twisted.log, no tiene que registrar explícitamente los errores y también devuelve todo el rastreo y el tiempo al archivo o secuencia.

Jakob Bowyer
fuente
8
Quizás twistedsea ​​una buena recomendación, pero esta respuesta realmente no contribuye mucho. No dice cómo usar twisted.log, ni qué ventajas tiene sobre el loggingmódulo de la biblioteca estándar, ni explica qué significa "no tiene que registrar explícitamente los errores" .
Mark Amery el
-8

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

caraconan
fuente
44
¿Eh? ¿Por qué es esa "la parte relevante" ? Todo lo que .split('\n')[-2]hace es tirar el número de línea y rastrear el resultado de format_exc()- ¡información útil que normalmente desea! Lo que es más, ni siquiera hace un buen trabajo de eso ; si su mensaje de excepción contiene una nueva línea, entonces este enfoque solo imprimirá la línea final del mensaje de excepción, lo que significa que pierde la clase de excepción y la mayor parte del mensaje de excepción además de perder el rastreo. -1.
Mark Amery el