Exception de journal avec traceback

153

Comment puis-je enregistrer mes erreurs Python?

try:
    do_something()
except:
    # How can I log my exception here, complete with its traceback?
TIMEX
la source

Réponses:

203

À utiliser logging.exceptiondepuis le except:gestionnaire / bloc pour consigner l'exception en cours avec les informations de trace, précédées d'un message.

import logging
LOG_FILENAME = '/tmp/logging_example.out'
logging.basicConfig(filename=LOG_FILENAME, level=logging.DEBUG)

logging.debug('This message should go to the log file')

try:
    run_my_stuff()
except:
    logging.exception('Got exception on main handler')
    raise

Vous voyez maintenant le fichier journal, /tmp/logging_example.out:

DEBUG:root:This message should go to the log file
ERROR:root:Got exception on main handler
Traceback (most recent call last):
  File "/tmp/teste.py", line 9, in <module>
    run_my_stuff()
NameError: name 'run_my_stuff' is not defined
nosklo
la source
1
J'ai regardé le code django pour cela, et je suppose que la réponse est non, mais y a-t-il un moyen de limiter le traçage à une certaine quantité de caractères ou de profondeur? Le problème est que pour les gros retraits, cela prend assez de temps.
Eduard Luca
10
Notez que si vous définissez un enregistreur avec logger = logging.getLogger('yourlogger')vous devez écrire logger.exception('...')pour que cela fonctionne ...
576i
Pouvons-nous modifier cela pour que le message soit imprimé avec le niveau de journal INFO?
NM du
Notez que pour certaines applications externes, telles qu'Azure Insight, le trackback n'est pas stocké dans les journaux. Il est alors nécessaire de les transmettre explicitement à la chaîne de message comme indiqué ci-dessous.
Edgar H le
139

Les exc_infooptions d' utilisation peuvent être meilleures, reste un avertissement ou un titre d'erreur:

try:
    # coode in here
except Exception as e:
    logging.error(e, exc_info=True)
mouche
la source
Je ne me souviens jamais comment exc_info=s'appelle le kwarg; Merci!
berto
4
Ceci est identique à logging.exception, à l'exception du fait que le type est enregistré deux fois de manière redondante. Utilisez simplement logging.exception à moins que vous ne souhaitiez un niveau autre que error.
Wyrmwood
@Wyrmwood ce n'est pas identique car vous devez fournir un message àlogging.exception
Peter Wood
58

Mon travail m'a récemment chargé de consigner tous les retraits / exceptions de notre application. J'ai essayé de nombreuses techniques que d'autres avaient mises en ligne comme celle ci-dessus, mais j'ai opté pour une approche différente. Remplacer traceback.print_exception.

J'ai un article sur http://www.bbarrows.com/ Ce serait beaucoup plus facile à lire mais je vais le coller ici également.

Lorsque j'ai été chargé de consigner toutes les exceptions que notre logiciel pourrait rencontrer dans la nature, j'ai essayé un certain nombre de techniques différentes pour enregistrer nos traces d'exceptions python. Au début, j'ai pensé que le hook d'exception du système python, sys.excepthook, serait l'endroit idéal pour insérer le code de journalisation. J'essayais quelque chose de similaire à:

import traceback
import StringIO
import logging
import os, sys

def my_excepthook(excType, excValue, traceback, logger=logger):
    logger.error("Logging an uncaught exception",
                 exc_info=(excType, excValue, traceback))

sys.excepthook = my_excepthook  

Cela a fonctionné pour le thread principal, mais j'ai vite découvert que my sys.excepthook n'existerait pas sur les nouveaux threads démarrés par mon processus. C'est un problème énorme car presque tout se passe dans les threads de ce projet.

Après avoir cherché sur Google et lu beaucoup de documentation, les informations les plus utiles que j'ai trouvées provenaient du suivi des problèmes Python.

Le premier article sur le fil montre un exemple de travail du sys.excepthookNON persistant entre les fils (comme indiqué ci-dessous). Apparemment, c'est un comportement attendu.

import sys, threading

def log_exception(*args):
    print 'got exception %s' % (args,)
sys.excepthook = log_exception

def foo():
    a = 1 / 0

threading.Thread(target=foo).start()

Les messages sur ce fil de discussion sur Python entraînent vraiment 2 hacks suggérés. Soit sous Thread-classez et encapsulez la méthode run dans notre propre bloc try except afin de capturer et enregistrer les exceptions, soit dans le patch monkey threading.Thread.runpour exécuter votre propre try except bloquer et enregistrer les exceptions.

La première méthode de sous-classification Threadme semble moins élégante dans votre code car vous auriez à importer et utiliser votre Threadclasse personnalisée PARTOUT où vous vouliez avoir un fil de journalisation. Cela a fini par être un problème car j'ai dû rechercher toute notre base de code et remplacer tout normal Threadspar cette coutume Thread. Cependant, ce que cela faisait était clair Threadet il serait plus facile pour quelqu'un de diagnostiquer et de déboguer si quelque chose n'allait pas avec le code de journalisation personnalisé. Un thread de journalisation personnalisé peut ressembler à ceci:

class TracebackLoggingThread(threading.Thread):
    def run(self):
        try:
            super(TracebackLoggingThread, self).run()
        except (KeyboardInterrupt, SystemExit):
            raise
        except Exception, e:
            logger = logging.getLogger('')
            logger.exception("Logging an uncaught exception")

La deuxième méthode de patching de singe threading.Thread.runest agréable car je pourrais l'exécuter une fois juste après __main__et instrumenter mon code de journalisation dans toutes les exceptions. Le correctif de singe peut être ennuyeux à déboguer car il change la fonctionnalité attendue de quelque chose. Le correctif suggéré par le suivi des problèmes Python était:

def installThreadExcepthook():
    """
    Workaround for sys.excepthook thread bug
    From
http://spyced.blogspot.com/2007/06/workaround-for-sysexcepthook-bug.html

(https://sourceforge.net/tracker/?func=detail&atid=105470&aid=1230540&group_id=5470).
    Call once from __main__ before creating any threads.
    If using psyco, call psyco.cannotcompile(threading.Thread.run)
    since this replaces a new-style class method.
    """
    init_old = threading.Thread.__init__
    def init(self, *args, **kwargs):
        init_old(self, *args, **kwargs)
        run_old = self.run
        def run_with_except_hook(*args, **kw):
            try:
                run_old(*args, **kw)
            except (KeyboardInterrupt, SystemExit):
                raise
            except:
                sys.excepthook(*sys.exc_info())
        self.run = run_with_except_hook
    threading.Thread.__init__ = init

Ce n'est que lorsque j'ai commencé à tester ma journalisation des exceptions que j'ai réalisé que tout allait mal.

Pour tester j'avais placé un

raise Exception("Test")

quelque part dans mon code. Cependant, encapsuler une méthode qui appelait cette méthode était un bloc try except qui imprimait le traçage et avalait l'exception. C'était très frustrant car j'ai vu le traçage apporter imprimé à STDOUT mais ne pas être journalisé. C'est alors que j'ai décidé qu'une méthode beaucoup plus simple de journalisation des retraits était simplement de corriger la méthode utilisée par tout le code python pour imprimer les retraits eux-mêmes, traceback.print_exception. J'ai fini avec quelque chose de similaire à ce qui suit:

def add_custom_print_exception():
    old_print_exception = traceback.print_exception
    def custom_print_exception(etype, value, tb, limit=None, file=None):
        tb_output = StringIO.StringIO()
        traceback.print_tb(tb, limit, tb_output)
        logger = logging.getLogger('customLogger')
        logger.error(tb_output.getvalue())
        tb_output.close()
        old_print_exception(etype, value, tb, limit=None, file=None)
    traceback.print_exception = custom_print_exception

Ce code écrit la traceback dans un tampon de chaîne et le consigne dans la journalisation ERROR. J'ai un gestionnaire de journalisation personnalisé configuré l'enregistreur «customLogger» qui prend les journaux de niveau ERREUR et les renvoie à la maison pour analyse.

Brad Barrows
la source
2
Une approche assez intéressante. Une question - add_custom_print_exceptionne semble pas être sur le site auquel vous avez lié, et à la place, il y a un code final assez différent. Selon vous, lequel est le meilleur / le plus définitif et pourquoi? Merci!
fantastique
Merci, bonne réponse!
101
Il y a une faute de frappe et coller. sur l'appel délégué à old_print_exception, la limite et le fichier doivent être passés limite et fichier, pas None - old_print_exception (etype, value, tb, limit, file)
Marvin
Pour votre dernier bloc de code, plutôt que d'initialiser un StringIO et d'y imprimer l'exception, vous pouvez simplement appeler logger.error(traceback.format_tb())(ou format_exc () si vous voulez aussi les informations d'exception).
James
8

Vous pouvez enregistrer toutes les exceptions non interceptées sur le thread principal en affectant un gestionnaire à sys.excepthook, peut-être en utilisant le exc_infoparamètre des fonctions de journalisation de Python :

import sys
import logging

logging.basicConfig(filename='/tmp/foobar.log')

def exception_hook(exc_type, exc_value, exc_traceback):
    logging.error(
        "Uncaught exception",
        exc_info=(exc_type, exc_value, exc_traceback)
    )

sys.excepthook = exception_hook

raise Exception('Boom')

Si votre programme utilise des threads, cependant, notez que les threads créés avec threading.Threadne se déclencheront passys.excepthook lorsqu'une exception non interceptée se produit à l'intérieur, comme indiqué dans le numéro 1230540 sur le suivi des problèmes de Python. Certains hacks ont été suggérés pour contourner cette limitation, comme le monkey-patching Thread.__init__pour écraser self.runavec une runméthode alternative qui enveloppe l'original dans un trybloc et appelle sys.excepthookde l'intérieur du exceptbloc. Alternativement, vous pouvez simplement envelopper manuellement le point d'entrée pour chacun de vos threads dans try/ exceptyourself.

Mark Amery
la source
3

Les messages d'exception non capturés vont à STDERR, donc au lieu d'implémenter votre journalisation en Python lui-même, vous pouvez envoyer STDERR vers un fichier en utilisant le shell que vous utilisez pour exécuter votre script Python. Dans un script Bash, vous pouvez le faire avec la redirection de sortie, comme décrit dans le guide BASH .

Exemples

Ajouter les erreurs au fichier, autre sortie au terminal:

./test.py 2>> mylog.log

Écraser le fichier avec une sortie STDOUT et STDERR entrelacée:

./test.py &> mylog.log
panchicore
la source
2

Ce que je cherchais:

import sys
import traceback

exc_type, exc_value, exc_traceback = sys.exc_info()
traceback_in_var = traceback.format_tb(exc_traceback)

Voir:

Martin Thoma
la source
2

Vous pouvez récupérer le traceback à l'aide d'un logger, à n'importe quel niveau (DEBUG, INFO, ...). Notez qu'en utilisant logging.exception, le niveau est ERROR.

# test_app.py
import sys
import logging

logging.basicConfig(level="DEBUG")

def do_something():
    raise ValueError(":(")

try:
    do_something()
except Exception:
    logging.debug("Something went wrong", exc_info=sys.exc_info())
DEBUG:root:Something went wrong
Traceback (most recent call last):
  File "test_app.py", line 10, in <module>
    do_something()
  File "test_app.py", line 7, in do_something
    raise ValueError(":(")
ValueError: :(

ÉDITER:

Cela fonctionne aussi (en utilisant python 3.6)

logging.debug("Something went wrong", exc_info=True)
Constantin De La Roche
la source
1

Voici une version qui utilise sys.excepthook

import traceback
import sys

logger = logging.getLogger()

def handle_excepthook(type, message, stack):
     logger.error(f'An unhandled exception occured: {message}. Traceback: {traceback.format_tb(stack)}')

sys.excepthook = handle_excepthook
sveilleux2
la source
Que diriez-vous d'utiliser {traceback.format_exc()}au lieu de {traceback.format_tb(stack)}?
variable le
0

peut-être pas aussi élégant, mais plus facile:

#!/bin/bash
log="/var/log/yourlog"
/path/to/your/script.py 2>&1 | (while read; do echo "$REPLY" >> $log; done)
Hugo Walter
la source
-1

Voici un exemple simple tiré de la documentation de python 2.6 :

import logging
LOG_FILENAME = '/tmp/logging_example.out'
logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG,)

logging.debug('This message should go to the log file')
rogeriopvl
la source
4
La question était de savoir comment enregistrer le retraçage
Konstantin Schubert