Comment enregistrer une erreur Python avec des informations de débogage?

j'imprime des messages d'exception Python dans un fichier journal avec logging.error :

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

est-il possible d'imprimer des informations plus détaillées sur l'exception et le code qui l'a générée que la chaîne d'exception? Des choses comme les numéros de ligne ou les traces de pile seraient grandes.

321
demandé sur Steven Vascellaro 2011-03-04 12:21:20

9 réponses

logger.exception produira une trace de pile à côté du message d'erreur.

par exemple:

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

sortie:

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

@Paulo chèque notes," soyez conscient que dans Python 3 vous devez appeler la méthode logging.exception juste à l'intérieur de la partie except . Si vous appelez cette méthode dans un endroit arbitraire vous pouvez obtenir une exception bizarre. Les docs d'alerte à ce sujet."

522
répondu SiggyF 2018-05-23 15:15:43

une bonne chose au sujet de logging.exception que la réponse de SiggyF ne montre pas est que vous pouvez passer dans un message arbitraire, et la journalisation montrera toujours le retraçage complet avec tous les détails d'exception:

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

avec le comportement par défaut (dans les versions récentes) de journalisation des erreurs d'impression juste à sys.stderr , il ressemble à ceci:

>>> 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
157
répondu ncoghlan 2018-05-23 15:14:51

utiliser les options exc_info peut être préférable, pour vous permettre de choisir le niveau d'erreur (si vous utilisez exception , il affichera toujours error ):

try:
    # do something here
except Exception as e:
    logging.fatal(e, exc_info=True)  # log exception info at FATAL log level
83
répondu flycee 2018-03-15 09:02:54

citant

que se passe – t-il si votre application effectue une journalisation d'une autre manière-sans utiliser le module logging ?

maintenant, traceback peut être utilisé ici.

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)
  • utilisez-le dans Python 2 :

    try:
        # your function call is here
    except Exception as ex:
        _, _, ex_traceback = sys.exc_info()
        log_traceback(ex, ex_traceback)
    
  • utilisez-le dans Python 3 :

    try:
        x = get_number()
    except Exception as ex:
        log_traceback(ex)
    
26
répondu zangw 2015-10-19 10:21:36

si vous utilisez des journaux ordinaires - tous vos journaux doivent correspondre à cette règle: one record = one line . En suivant cette règle, vous pouvez utiliser grep et d'autres outils pour traiter vos fichiers journaux.

mais l'information de retraçage est multi-ligne. Donc, ma réponse est une version étendue de la solution proposée par zangw ci-dessus dans ce fil. Le problème est que les lignes de traceback pourraient avoir \n à l'intérieur, donc nous avons besoin de faire un travail supplémentaire pour se débarrasser de cette ligne fin:

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__())

après cela (lorsque vous analyserez vos journaux) vous pouvez copier / coller les lignes de traceback nécessaires à partir de votre fichier journal et faire ceci:

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

Profit!

11
répondu doomatel 2017-05-23 11:55:19

cette réponse se construit à partir des excellents ci-dessus.

dans la plupart des applications, vous n'appellerez pas la journalisation.exception e) directement. Vous avez probablement défini un logger personnalisé spécifique à votre application ou module comme ceci:

# 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)

dans ce cas, utilisez simplement le logger pour appeler l'exception (e) comme ceci:

try:
    1/0
except ZeroDivisionError, e:
    my_logger.exception(e)
9
répondu Will 2018-05-23 15:19:03

un peu de traitement de décorateur (très librement inspiré par la Peut-être monade et le levage). Vous pouvez supprimer en toute sécurité les annotations de type Python 3.6 et utiliser un ancien style de formatage de message.

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

Démo:

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'

vous pouvez également modifier cette solution pour retourner quelque chose d'un peu plus significatif que None de la partie except (ou même faire le solution generic, en spécifiant cette valeur de retour dans les arguments de fallible ).

0
répondu Eli Korvigo 2018-07-12 17:09:50

si vous pouvez gérer la dépendance supplémentaire, utilisez twisted.log, vous n'avez pas besoin de log explicitement les erreurs et aussi il renvoie la totalité du traceback et du temps au fichier ou au flux.

-1
répondu Jakob Bowyer 2011-03-15 01:12:53

propre façon de le faire est d'utiliser format_exc() et ensuite d'analyser la sortie pour obtenir la partie pertinente:

from traceback import format_exc

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

concerne

-6
répondu caraconan 2013-02-20 16:50:22