web-dev-qa-db-fra.com

Comment imprimer le suivi sans arrêter le programme?

J'écris un programme qui analyse 10 sites Web, localise des fichiers de données, les enregistre, puis les analyse pour créer des données pouvant être facilement utilisées dans la bibliothèque NumPy. Il y a tonnes d'erreurs rencontrées par ce fichier par le biais de mauvais liens, d'un XML mal formé, d'entrées manquantes et d'autres éléments que je n'ai pas encore classés. J'ai initialement créé ce programme pour gérer les erreurs comme ceci:

try:
    do_stuff()
except:
    pass

Mais maintenant, je veux enregistrer les erreurs:

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

Notez qu'il s'agit d'une impression 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 d’imprimer exactement les mêmes lignes lorsque l’erreur se déclenche sans tentative d’essai, sauf l’interception de l’exception, mais je ne veux pas qu’elle arrête mon programme, car il est imbriqué dans une série de boucles for que je souhaiterais. voir à l'achèvement.

657
chriscauley

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

Veuillez noter qu'avec _print_exc_, dans certains cas, vous n'obtiendrez pas ce que vous espériez. Dans 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 l'exception last:

_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 à l'original traceback, une solution consiste à mettre en cache les informations d'exception comme retourné de exc_info dans une variable locale et l'afficher à l'aide de 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:

  • Extrait de la doc de sys_info :

    L'affectation de la valeur de retour traceback à une variable locale dans une fonction qui gère une exception entraînera une référence circulaire . Cela empêchera que tout ce qui est référencé par une variable locale dans la même fonction ou par le suivi soit collecté. [...] Si vous avez besoin de la trace, assurez-vous de la supprimer après utilisation (mieux faire avec une instruction try ... finally)

  • mais, du même doc:

    À partir de Python 2.2, ces cycles sont automatiquement récupérés lorsque le garbage collection est activé et deviennent inaccessibles, mais il reste plus efficace de éviter de créer des cycles.


D'autre part, en vous permettant d'accéder au traçage 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!")
_
474
Sylvain Leroux

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()[0])
642
volting

Si vous êtes en train de déboguer et que vous voulez juste voir la trace actuelle de la pile, vous pouvez simplement appeler:

traceback.print_stack()

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

212
dimo414

Comment imprimer le suivi sans arrêter le programme?

Si vous ne voulez pas arrêter votre programme en cas d'erreur, vous devez gérer cette erreur avec try/except:

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

Pour extraire le suivi complet, nous allons utiliser le module traceback de la bibliothèque standard:

import traceback

Et pour créer un stacktrace assez 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 méthode traceback.print_exc:

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

Quelles impressions:

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 bonne pratique consiste à configurer un enregistreur pour votre module. Il connaîtra le nom du module et pourra changer de niveau (entre autres attributs, tels que des gestionnaires)

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

Dans ce cas, vous voudrez utiliser la fonction logger.exception:

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 voulez-vous simplement la chaîne, auquel cas vous voudrez utiliser la fonction traceback.format_exc:

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 le même résultat 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!
82
Aaron Hall

Premièrement, n'utilisez pas prints pour la journalisation, il existe un module stdlib astucieux, éprouvé et bien pensé pour le faire: logging . Vous devez absolument l'utiliser à la place.

Deuxièmement, ne soyez pas tenté de faire un ( gâchis avec des outils non liés lorsqu'il existe 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 au fonctionnement sous le capot

Ce que log.exception est en train de faire n’est qu’un appel à log.error (c’est-à-dire un événement de journalisation de niveau ERROR) et print traceback ensuite.

Pourquoi est-ce mieux?

Eh bien, voici quelques considérations:

  • c'est juste right ;
  • c'est simple;
  • c'est simple.

Pourquoi personne ne devrait-il utiliser traceback ou appeler le logger avec exc_info=True ou se salir les mains avec sys.exc_info?

Eh bien, juste parce que! Ils existent tous à des fins différentes. Par exemple, la sortie de traceback.print_exc est légèrement différente de celle des traces générées par l'interpréteur lui-même. Si vous l'utilisez, vous allez confondre tous ceux qui liront vos journaux, ils se cogneront la tête.

Passer exc_info=True pour consigner les appels est tout simplement inapproprié. Mais , il est utile lorsque vous récupérez des erreurs récupérables et que vous souhaitez les consigner (en utilisant, par exemple, le niveau INFO) avec les retraits, parce que log.exception produit des journaux d'un seul niveau - ERROR.

Et vous devez absolument éviter de jouer avec sys.exc_info autant 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 ne s'agit pas uniquement d'imprimer des exceptions.

13
tosh

En plus de la réponse de @Aaron Hall, si vous vous enregistrez, mais que vous ne voulez pas utiliser logging.exception() (car il se connecte au niveau ERROR), 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)
8
Mark McDonald

Pour obtenir la trace de pile précise , sous forme de chaîne, qui aurait été levé si aucun essai/sauf s’il n’y avait lieu d’y passer, placez-le simplement dans le bloc except qui capture l’exception fautive.

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

Voici comment l'utiliser (en supposant que flaky_func soit défini et que log appelle votre système de consignation 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, de sorte que vous puissiez toujours tuer le programme en utilisant Ctrl-C. La journalisation sort du cadre de la question, mais une bonne option est journalisation . Documentation pour les modules sys et traceback .

7
Edward Newell

Une remarque à propos de cette réponse commentaires: print(traceback.format_exc()) fait mieux pour moi que traceback.print_exc(). Avec ce dernier, la hello est parfois étrangement "mélangée" avec le texte de trace, comme si les deux voulaient écrire simultanément sur stdout ou stderr, produisant un résultat étrange (du moins lors de la création depuis un éditeur de texte et voir la sortie dans le panneau "Résultats de la construction").

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

Donc j'utilise:

import traceback, sys

def do_stuff():
    1/0

try:
    do_stuff()
except Exception:
    print(traceback.format_exc())
    print('hello')
6
Basj

Vous devrez mettre try/except à l’intérieur de la boucle la plus interne, où l’erreur peut se produire, c.-à-d.

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 envelopper les instructions susceptibles d'échouer lors de l'essai/sauf aussi spécifique que possible, dans la boucle la plus interne possible.

6
Ivo van der Wijk

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

Dans Python 3.5+, vous pouvez obtenir une trace 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 a pour résultat:

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, par opposition à ce qui aurait été imprimé à l'écran si l'exception avait été levée dans stack_lvl_2() et non interceptée (supprimez le commentaire de la ligne # raise).

Si j'ai bien compris, c'est parce qu'une exception enregistre uniquement le niveau actuel de la pile lorsqu'elle est levée, stack_lvl_3() dans ce cas. Lorsqu’il est remonté dans la pile, de plus en plus de niveaux sont ajoutés à son __traceback__. Mais nous l'avons intercepté dans stack_lvl_2(), ce qui signifie que tout ce qu'il a à enregistrer est les niveaux 3 et 2. Pour obtenir la trace complète telle qu'elle est imprimée sur la sortie standard, 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 résulte en:

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'empilement est différent, les première et dernière lignes sont manquantes. Parce que c'est un différent format() .

Intercepter l’exception aussi loin que possible du point où elle a été levée simplifie le code tout en fournissant plus d’informations.

4
bgdnlp

Vous voulez le module traceback . Cela vous permettra d’imprimer des copies de pile comme Python le fait normalement. En particulier, la fonction print_last imprimera la dernière exception et une trace de pile.

3
nmichaels

Récupère la trace complète en tant que chaîne de l'objet exception avec traceback.format_exception

Si vous ne possédez que l'objet exception, vous pouvez obtenir le suivi en tant que chaîne à partir de n'importe quel point du code dans 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)

Sortie:

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 trace d'un objet exception

Testé en Python 3.7.3.

Bon mot:

import traceback
traceback.print_exc()

Utilisez le module traceback qui capture automatiquement l'exception en cours lorsqu'il se trouve dans un except:

https://stackoverflow.com/a/9555145/2217801

0