Comment imprimer la trace complète sans arrêter le programme?

781

J'écris un programme qui analyse 10 sites Web, localise des fichiers de données, enregistre les fichiers, puis les analyse pour créer des données qui peuvent être facilement utilisées dans la bibliothèque NumPy. Il y a des tonnes d'erreurs que ce fichier rencontre à travers de mauvais liens, du XML mal formé, des entrées manquantes et d'autres choses que je n'ai pas encore catégorisées. J'ai initialement créé ce programme pour gérer des erreurs comme celle-ci:

try:
    do_stuff()
except:
    pass

Mais maintenant, je veux enregistrer les erreurs:

try:
    do_stuff()
except Exception, err:
    print Exception, err

Notez que cela imprime dans un fichier journal pour une révision ultérieure. Cela imprime généralement des données très inutiles. Ce que je veux, c'est imprimer exactement les mêmes lignes imprimées lorsque l'erreur se déclenche sans essayer, sauf en interceptant l'exception, mais je ne veux pas qu'il arrête mon programme car il est imbriqué dans une série de boucles for que j'aimerais voir à l'achèvement.

chriscauley
la source

Réponses:

585

Une autre réponse a déjà souligné le module traceback .

Veuillez noter qu'avec print_exc, dans certains cas, vous n'obtiendrez pas ce que vous attendez. En Python 2.x:

import traceback

try:
    raise TypeError("Oups!")
except Exception, err:
    try:
        raise TypeError("Again !?!")
    except:
        pass

    traceback.print_exc()

... affichera la trace de la dernière exception:

Traceback (most recent call last):
  File "e.py", line 7, in <module>
    raise TypeError("Again !?!")
TypeError: Again !?!

Si vous avez vraiment besoin d'accéder à la trace d' origine, une solution consiste à mettre en cache les informations d' exception renvoyées exc_infodans une variable locale et à l'afficher en utilisant print_exception:

import traceback
import sys

try:
    raise TypeError("Oups!")
except Exception, err:
    try:
        exc_info = sys.exc_info()

        # do you usefull stuff here
        # (potentially raising an exception)
        try:
            raise TypeError("Again !?!")
        except:
            pass
        # end of useful stuff


    finally:
        # Display the *original* exception
        traceback.print_exception(*exc_info)
        del exc_info

Produire:

Traceback (most recent call last):
  File "t.py", line 6, in <module>
    raise TypeError("Oups!")
TypeError: Oups!

Quelques pièges à cela cependant:

  • Du doc ​​de sys_info:

    L'affectation de la valeur de retour traceback à une variable locale dans une fonction qui gère une exception provoquera une référence circulaire . Cela empêchera tout ce qui est référencé par une variable locale dans la même fonction ou par le traçage d'être récupéré. [...] Si vous avez besoin du traceback, assurez-vous de le supprimer après utilisation (mieux vaut avec une instruction try ... finally)

  • mais, du même doc:

    À partir de Python 2.2, ces cycles sont automatiquement récupérés lorsque la récupération de place est activée et deviennent inaccessibles, mais cela reste plus efficace pour éviter de créer des cycles.


En revanche, en vous permettant d'accéder au traceback associé à une exception, Python 3 produit un résultat moins surprenant:

import traceback

try:
    raise TypeError("Oups!")
except Exception as err:
    try:
        raise TypeError("Again !?!")
    except:
        pass

    traceback.print_tb(err.__traceback__)

... Affichera:

  File "e3.py", line 4, in <module>
    raise TypeError("Oups!")
Sylvain Leroux
la source
711

traceback.format_exc()ou sys.exc_info()donnera plus d'informations si c'est ce que vous voulez.

import traceback
import sys

try:
    do_stuff()
except Exception:
    print(traceback.format_exc())
    # or
    print(sys.exc_info()[2])
voltige
la source
1
print(sys.exc_info()[0]impressions <class 'Exception'>.
weberc2
2
n'utilisez pas exc ... le traceback contient toutes les informations stackoverflow.com/questions/4564559/…
qrtLs
258

Si vous déboguez et souhaitez simplement voir la trace de la pile actuelle, vous pouvez simplement appeler:

traceback.print_stack()

Il n'est pas nécessaire de lever manuellement une exception juste pour la rattraper.

dimo414
la source
9
Le module traceback fait exactement cela - déclenche et intercepte une exception.
pppery
3
La sortie passe à STDERR par défaut BTW. N'apparaissait pas dans mes journaux car il était redirigé ailleurs.
mpen
101

Comment imprimer la trace complète sans arrêter le programme?

Lorsque vous ne voulez pas arrêter votre programme en cas d'erreur, vous devez gérer cette erreur avec un essai / sauf:

try:
    do_something_that_might_error()
except Exception as error:
    handle_the_error(error)

Pour extraire la trace complète, nous utiliserons le tracebackmodule de la bibliothèque standard:

import traceback

Et pour créer un stacktrace décemment compliqué pour démontrer que nous obtenons le stacktrace complet:

def raise_error():
    raise RuntimeError('something bad happened!')

def do_something_that_might_error():
    raise_error()

Impression

Pour imprimer la trace complète, utilisez la traceback.print_excméthode:

try:
    do_something_that_might_error()
except Exception as error:
    traceback.print_exc()

Qui imprime:

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

Mieux que l'impression, la journalisation:

Cependant, une meilleure pratique consiste à configurer un enregistreur pour votre module. Il connaîtra le nom du module et pourra changer de niveau (entre autres attributs, comme les gestionnaires)

import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

Dans ce cas, vous voudrez logger.exceptionplutôt la fonction:

try:
    do_something_that_might_error()
except Exception as error:
    logger.exception(error)

Quels journaux:

ERROR:__main__:something bad happened!
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

Ou peut-être que vous voulez juste la chaîne, auquel cas, vous voudrez traceback.format_excplutôt la fonction:

try:
    do_something_that_might_error()
except Exception as error:
    logger.debug(traceback.format_exc())

Quels journaux:

DEBUG:__main__:Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

Conclusion

Et pour les trois options, nous voyons que nous obtenons la même sortie que lorsque nous avons une erreur:

>>> do_something_that_might_error()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!
Aaron Hall
la source
2
comme dit ci-dessus et pour moi aussi, traceback.print_exc()ne renvoie que le dernier appel: comment réussissez-vous à renvoyer plusieurs niveaux de la pile (et éventuellement tous les niveaux?)
herve-guerin
@geekobi Je ne suis pas sûr de ce que vous demandez ici. Je démontre que nous remontons la trace jusqu'au point d'entrée du programme / interprète. Sur quoi n'êtes-vous pas clair?
Aaron Hall
1
Ce que @geekobi dit, c'est que si vous attrapez et relancez, traceback.print_exc () renverra simplement la pile de relance, pas la pile d'origine.
fizloki
@fizloki comment allez-vous "sur-relancer"? Faites-vous un raisechaînage nu ou d'exception, ou cachez-vous le traçage d'origine? voir stackoverflow.com/questions/2052390/…
Aaron Hall
20

Tout d' abord, ne pas utiliser prints pour l' exploitation forestière, il est astable, éprouvée et bien pensé module stdlib de le faire: logging. Vous devriez certainement l' utiliser à la place.

Deuxièmement, ne soyez pas tenté de gâcher des outils sans rapport avec une approche native et simple. C'est ici:

log = logging.getLogger(__name__)

try:
    call_code_that_fails()
except MyError:
    log.exception('Any extra info you want to see in your logs')

C'est ça. Vous avez terminé maintenant.

Explication pour quiconque s'intéresse à la façon dont les choses fonctionnent sous le capot

Ce qui log.exceptionse passe réellement est juste un appel à log.error(c'est-à-dire, un événement de journal avec niveau ERROR) et une trace de retour d'impression ensuite.

Pourquoi est-ce mieux?

Eh bien, voici quelques considérations:

  • il est juste à droite ;
  • c'est simple;
  • C'est simple.

Pourquoi personne ne devrait utiliser tracebackou appeler l'enregistreur exc_info=Trueou se salir les mains sys.exc_info?

Eh bien, juste parce que! Ils existent tous à des fins différentes. Par exemple, traceback.print_excla sortie de 'est un peu différente des traces produites par l'interpréteur lui-même. Si vous l'utilisez, vous confondrez tous ceux qui liront vos journaux, ils se cogneront la tête contre eux.

Passer exc_info=Truepour consigner les appels est tout simplement inapproprié. Mais , il est utile lors de la capture d'erreurs récupérables et vous souhaitez également les enregistrer (en utilisant, par exemple, le INFOniveau) avec des traces, car il log.exceptionproduit des journaux d'un seul niveau - ERROR.

Et vous devez absolument éviter de jouer avec sys.exc_infoautant que vous le pouvez. Ce n'est tout simplement pas une interface publique, c'est une interface interne - vous pouvez l' utiliser si vous savez vraiment ce que vous faites. Il n'est pas destiné à imprimer uniquement des exceptions.

tosh
la source
4
Cela ne fonctionne pas non plus tel quel. Ce n'est pas ça. Je n'ai pas fini maintenant: cette réponse fait perdre du temps.
A. Rager
J'ajouterais également que vous pouvez simplement le faire logging.exception(). Pas besoin de créer d'instance de journal, sauf si vous avez des exigences particulières.
Shital Shah
9

En plus de la réponse de @Aaron Hall, si vous vous connectez, mais que vous ne souhaitez pas l'utiliser logging.exception()(car il se connecte au niveau ERREUR), vous pouvez utiliser un niveau inférieur et passer exc_info=True. par exemple

try:
    do_something_that_might_error()
except Exception:
    logger.info('General exception noted.', exc_info=True)
Mark McDonald
la source
7

Pour obtenir la trace précise de la pile, sous forme de chaîne, qui aurait été déclenchée si aucun essai / except n'était là pour la franchir, placez-le simplement dans le bloc except qui attrape l'exception incriminée.

desired_trace = traceback.format_exc(sys.exc_info())

Voici comment l'utiliser (en supposant qu'il flaky_funcsoit défini et logappelle votre système de journalisation préféré):

import traceback
import sys

try:
    flaky_func()
except KeyboardInterrupt:
    raise
except Exception:
    desired_trace = traceback.format_exc(sys.exc_info())
    log(desired_trace)

C'est une bonne idée d'attraper et de relancer KeyboardInterrupts, afin que vous puissiez toujours tuer le programme en utilisant Ctrl-C. La journalisation sort du cadre de la question, mais une bonne option est la journalisation . Documentation pour les modules sys et traceback .

Edward Newell
la source
4
Cela ne fonctionne pas dans Python 3 et doit être remplacé par desired_trace = traceback.format_exc(). Passer sys.exc_info()l'argument n'a jamais été la bonne chose à faire, mais est silencieusement ignoré en Python 2, mais pas en Python 3 (3.6.4 de toute façon).
martineau
2
KeyboardInterruptn'est pas dérivé (directement ou indirectement) de Exception. (Les deux dérivent de BaseException.) Ce moyen except Exception:n'attrapera jamais un KeyboardInterrupt, et donc except KeyboardInterrupt: raiseest complètement inutile.
AJNeufeld
traceback.format_exc(sys.exc_info())ne fonctionne pas pour moi avec python 3.6.10
Nam G VU
6

Vous devrez placer le try / except dans la boucle la plus interne où l'erreur peut se produire, c'est-à-dire

for i in something:
    for j in somethingelse:
        for k in whatever:
            try:
                something_complex(i, j, k)
            except Exception, e:
                print e
        try:
            something_less_complex(i, j)
        except Exception, e:
            print e

... etc

En d'autres termes, vous devrez encapsuler les instructions qui peuvent échouer dans try / sauf aussi spécifiques que possible, dans la boucle la plus interne possible.

Ivo van der Wijk
la source
6

Une remarque sur les commentaires de cette réponse : print(traceback.format_exc())fait un meilleur travail pour moi que traceback.print_exc(). Avec ce dernier, le helloest parfois étrangement "mélangé" avec le texte de traceback, comme si les deux veulent écrire en même temps sur stdout ou stderr, produisant une sortie étrange (au moins lors de la construction depuis l'intérieur d'un éditeur de texte et de la visualisation dans le Panneau "Générer les résultats").

Traceback (dernier appel le plus récent):
Fichier "C: \ Users \ User \ Desktop \ test.py", ligne 7, en
enfer do_stuff ()
Fichier "C: \ Users \ User \ Desktop \ test.py", ligne 4 , dans do_stuff
1/0
ZeroDivisionError: division entière ou modulo par zéro
o
[Terminé en 0.1s]

J'utilise donc:

import traceback, sys

def do_stuff():
    1/0

try:
    do_stuff()
except Exception:
    print(traceback.format_exc())
    print('hello')
Basj
la source
5

Je ne vois cela mentionné dans aucune des autres réponses. Si vous passez un objet Exception pour une raison quelconque ...

Dans Python 3.5+, vous pouvez obtenir une trace à partir d'un objet Exception à l'aide de traceback.TracebackException.from_exception () . Par exemple:

import traceback


def stack_lvl_3():
    raise Exception('a1', 'b2', 'c3')


def stack_lvl_2():
    try:
        stack_lvl_3()
    except Exception as e:
        # raise
        return e


def stack_lvl_1():
    e = stack_lvl_2()
    return e

e = stack_lvl_1()

tb1 = traceback.TracebackException.from_exception(e)
print(''.join(tb1.format()))

Cependant, le code ci-dessus entraîne:

Traceback (most recent call last):
  File "exc.py", line 10, in stack_lvl_2
    stack_lvl_3()
  File "exc.py", line 5, in stack_lvl_3
    raise Exception('a1', 'b2', 'c3')
Exception: ('a1', 'b2', 'c3')

Il ne s'agit que de deux niveaux de la pile, contrairement à ce qui aurait été imprimé à l'écran si l'exception avait été levée stack_lvl_2()et non interceptée (décommentez la # raiseligne).

Si je comprends bien, c'est parce qu'une exception enregistre uniquement le niveau actuel de la pile lorsqu'elle est augmentée, stack_lvl_3()dans ce cas. Au fur et à mesure qu'il est remonté dans la pile, plus de niveaux sont ajoutés à son __traceback__. Mais nous l'avons intercepté stack_lvl_2(), ce qui signifie que tout ce qu'il a pu enregistrer était les niveaux 3 et 2. Pour obtenir la trace complète telle qu'imprimée sur stdout, nous devons l'attraper au niveau le plus élevé (le plus bas?):

import traceback


def stack_lvl_3():
    raise Exception('a1', 'b2', 'c3')


def stack_lvl_2():
    stack_lvl_3()


def stack_lvl_1():
    stack_lvl_2()


try:
    stack_lvl_1()
except Exception as exc:
    tb = traceback.TracebackException.from_exception(exc)

print('Handled at stack lvl 0')
print(''.join(tb.stack.format()))

Ce qui se traduit par:

Handled at stack lvl 0
  File "exc.py", line 17, in <module>
    stack_lvl_1()
  File "exc.py", line 13, in stack_lvl_1
    stack_lvl_2()
  File "exc.py", line 9, in stack_lvl_2
    stack_lvl_3()
  File "exc.py", line 5, in stack_lvl_3
    raise Exception('a1', 'b2', 'c3')

Notez que l'impression de la pile est différente, les première et dernière lignes sont manquantes. Parce que c'est différentformat() .

Intercepter l'exception le plus loin possible du point où elle a été levée rend le code plus simple tout en donnant plus d'informations.

bgdnlp
la source
C'est beaucoup mieux que les méthodes précédentes, mais c'est toujours ridiculement alambiqué juste pour imprimer une trace de pile. Java prend moins de code FGS.
elhefe
4

Obtenez le traceback complet sous forme de chaîne à partir de l'objet d'exception avec traceback.format_exception

Si vous n'avez que l'objet d'exception, vous pouvez obtenir le traceback sous forme de chaîne à partir de n'importe quel point du code en Python 3 avec:

import traceback

''.join(traceback.format_exception(None, exc_obj, exc_obj.__traceback__))

Exemple complet:

#!/usr/bin/env python3

import traceback

def f():
    g()

def g():
    raise Exception('asdf')

try:
    g()
except Exception as e:
    exc = e

tb_str = ''.join(traceback.format_exception(None, exc_obj, exc_obj.__traceback__))
print(tb_str)

Production:

Traceback (most recent call last):
  File "./main.py", line 12, in <module>
    g()
  File "./main.py", line 9, in g
    raise Exception('asdf')
Exception: asdf

Documentation: https://docs.python.org/3.7/library/traceback.html#traceback.format_exception

Voir aussi: Extraire les informations de traceback d'un objet d'exception

Testé en Python 3.7.3.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
3

Vous voulez le module traceback . Il vous permettra d'imprimer des vidages de pile comme Python le fait normalement. En particulier, la fonction print_last affichera la dernière exception et une trace de pile.

nmichaels
la source
2

Si vous avez déjà un objet Error et que vous souhaitez imprimer le tout, vous devez effectuer cet appel légèrement maladroit:

import traceback
traceback.print_exception(type(err), err, err.__traceback__)

C'est vrai, print_exceptionprend trois arguments positionnels: le type de l'exception, l'objet d'exception réel et la propre propriété de traceback interne de l'exception.

En python 3.5 ou version ultérieure, le type(err)est facultatif ... mais c'est un argument positionnel, vous devez donc toujours explicitement passer None à sa place.

traceback.print_exception(None, err, err.__traceback__)

Je ne sais pas pourquoi tout cela n'est pas juste traceback.print_exception(err). Pourquoi vous voudriez jamais imprimer une erreur, avec un retraçage autre que celui qui appartient à cette erreur, me dépasse.

nupanick
la source