web-dev-qa-db-fra.com

Exception de journal avec traceback

Comment enregistrer mes erreurs Python?

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

Utilisez logging.exception from avec un gestionnaire except: pour consigner l'exception en cours, précédée 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

En regardant 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
152
nosklo

Utiliser les options exc_info peut être mieux, reste l'avertissement ou le titre de l'erreur: 

try:
    # coode in here
except Exception as e:
    logging.error(e, exc_info=True)
97
flycee

Mon travail m'a récemment chargé de consigner toutes les traces/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 aussi.

Lorsque j'ai été chargé de consigner toutes les exceptions que notre logiciel pourrait rencontrer dans la nature, j'ai essayé différentes techniques pour consigner nos traces python d'exceptions. Au début, je pensais 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 fonctionnait pour le thread principal, mais j'ai vite constaté que mon sys.excepthook n'existerait pas dans les nouveaux threads démarrés par mon processus. C'est un gros problème car presque tout se passe dans les threads de ce projet.

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

Le premier article du fil de discussion montre un exemple de travail du sys.excepthook NON persistant entre les fils de discussion (comme indiqué ci-dessous). Apparemment, c'est le 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 Python Issue donnent vraiment 2 suggestions de piratage. Sous-classe Thread et encapsulez la méthode d’exécution dans notre propre essai sauf bloc afin de capturer et de consigner les exceptions ou monkey patch threading.Thread.run pour l’exécuter dans votre propre essai, sauf bloquer et enregistrer les exceptions.

La première méthode de sous-classement Thread me semble moins élégante dans votre code que vous auriez à importer et à utiliser votre classe Thread personnalisée PARTOUT où vous souhaitiez avoir un thread de journalisation. Cela a fini par être un problème parce que je devais parcourir toute notre base de code et remplacer tous les Threads normaux par ce Thread personnalisé. Cependant, il était clair que cette Thread était en train de faire et qu'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 custome pourrait 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 correction de singe threading.Thread.run est agréable car je pouvais juste l'exécuter une fois juste après __main__ et instrumenter mon code de journalisation dans toutes les exceptions. La correction de singe peut être gênante pour le débogage car elle modifie la fonctionnalité attendue de quelque chose. Le correctif suggéré à partir du suivi des problèmes Python était le suivant:

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 la journalisation des exceptions que j'ai réalisé que je m'y prenais 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 sauf qui imprimait la trace et avalait l'exception. C’était très frustrant car j’ai vu le traçage apporter imprimé à STDOUT mais ne pas être enregistré. C'est alors que j'ai décidé qu'une méthode beaucoup plus facile de journalisation des traces était simplement de corriger la méthode utilisée par tout le code python pour imprimer les traces elles-mêmes, traceback.print_exception . J'ai obtenu quelque chose de 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 le suivi dans un tampon de chaîne et le consigne dans le journal ERROR. J'ai un gestionnaire de journalisation personnalisé qui configure le consignateur 'customLogger' qui prend les journaux de niveau ERROR et les envoie à leur domicile pour analyse.

48
Brad Barrows

Vous pouvez enregistrer toutes les exceptions non interceptées sur le thread principal en affectant un gestionnaire à sys.excepthook , éventuellement en utilisant le paramètre exc_info 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')

Cependant, si votre programme utilise des threads, notez que les threads créés avec threading.Thread vont pas déclenchent sys.excepthook lorsqu'une exception non interceptée se produit à l'intérieur, comme indiqué dans Issue 1230540 sur le suivi des problèmes de Python. Certains hacks ont été suggérés pour contourner cette limitation, comme monkey-patcher Thread.__init__ pour écraser self.run avec une autre méthode run qui encapsule l'original dans un bloc try et appelle sys.excepthook depuis l'intérieur du bloc except. Sinon, vous pouvez simplement encapsuler vous-même manuellement le point d'entrée de chacun de vos threads dans la variable try/except.

6
Mark Amery

Les messages d'exception non capturés sont envoyés à STDERR. Ainsi, au lieu d'implémenter votre journalisation dans Python, vous pouvez envoyer STDERR dans 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, sous la forme décrit dans le guide BASH .

Exemples

Ajouter les erreurs au fichier, les autres sorties au terminal:

./test.py 2>> mylog.log

Écraser le fichier avec les sorties STDOUT et STDERR entrelacées:

./test.py &> mylog.log
3
panchicore

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)
0
Hugo Walter

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: 

0
Martin Thoma