web-dev-qa-db-fra.com

Journalisation des exceptions non capturées dans Python

Comment faire en sorte que les exceptions non capturées soient générées via le module logging plutôt que vers stderr?

Je réalise que la meilleure façon de faire serait:

try:
    raise Exception, 'Throwing a boring exception'
except Exception, e:
    logging.exception(e)

Mais ma situation est telle qu’il serait vraiment gentil si logging.exception(...) était invoquée automatiquement chaque fois qu’une exception n’est pas interceptée.

153
Jacob Marble

Comme Ned l'a souligné, sys.excepthook est invoqué chaque fois qu'une exception est levée et non capturée. L'implication pratique de ceci est que dans votre code, vous pouvez remplacer le comportement par défaut de sys.excepthook faire tout ce que vous voulez (y compris en utilisant logging.exception).

Comme exemple d'homme de paille:

>>> import sys
>>> def foo(exctype, value, tb):
...     print 'My Error Information'
...     print 'Type:', exctype
...     print 'Value:', value
...     print 'Traceback:', tb
... 

Passer outre sys.excepthook:

>>> sys.excepthook = foo

Commettez une erreur de syntaxe évidente (laissez les deux-points) et récupérez les informations d'erreur personnalisées:

>>> def bar(a, b)
My Error Information
Type: <type 'exceptions.SyntaxError'>
Value: invalid syntax (<stdin>, line 1)
Traceback: None

Pour plus d'informations sur sys.excepthook: http://docs.python.org/library/sys.html#sys.excepthook

126
Jacinda

Voici un petit exemple complet qui inclut également quelques autres astuces:

import sys
import logging
logger = logging.getLogger(__name__)
handler = logging.StreamHandler(stream=sys.stdout)
logger.addHandler(handler)

def handle_exception(exc_type, exc_value, exc_traceback):
    if issubclass(exc_type, KeyboardInterrupt):
        sys.__excepthook__(exc_type, exc_value, exc_traceback)
        return

    logger.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback))

sys.excepthook = handle_exception

if __== "__main__":
    raise RuntimeError("Test unhandled")
  • Ignore KeyboardInterrupt pour qu'un programme console python puisse se fermer avec Ctrl + C.

  • Comptez entièrement sur le module de journalisation de python pour formater l'exception.

  • Utilisez un enregistreur personnalisé avec un exemple de gestionnaire. Celui-ci modifie l'exception non gérée pour aller sur stdout plutôt que sur stderr, mais vous pouvez ajouter toutes sortes de gestionnaires dans ce même style à l'objet enregistreur.

144
gnu_lorien

La méthode sys.excepthook sera invoqué si une exception n'est pas capturée: http://docs.python.org/library/sys.html#sys.excepthook

Lorsqu'une exception est déclenchée et non capturée, l'interprète appelle sys.excepthook avec trois arguments, la classe d'exception, l'instance d'exception et un objet traceback. Dans une session interactive, cela se produit juste avant que le contrôle soit renvoyé à l'invite; Dans un programme Python, cela se produit juste avant la fermeture du programme. Vous pouvez personnaliser le traitement de ces exceptions de niveau supérieur en affectant une autre fonction à trois arguments à sys.excepthook.

26
Ned Batchelder

Pourquoi pas:

import sys
import logging
import traceback

def log_except_hook(*exc_info):
    text = "".join(traceback.format_exception(*exc_info))
    logging.error("Unhandled exception: %s", text)

sys.excepthook = log_except_hook

None()

Voici la sortie avec sys.excepthook comme vu ci-dessus:

$ python tb.py
ERROR:root:Unhandled exception: Traceback (most recent call last):
  File "tb.py", line 11, in <module>
    None()
TypeError: 'NoneType' object is not callable

Voici la sortie avec le sys.excepthook commenté:

$ python tb.py
Traceback (most recent call last):
  File "tb.py", line 11, in <module>
    None()
TypeError: 'NoneType' object is not callable

La seule différence est que l'ancien a ERROR:root:Unhandled exception: au début de la première ligne.

18
Tiago Coutinho

Pour construire sur la réponse de Jacinda, mais en utilisant un objet logger:

def catchException(logger, typ, value, traceback):
    logger.critical("My Error Information")
    logger.critical("Type: %s" % typ)
    logger.critical("Value: %s" % value)
    logger.critical("Traceback: %s" % traceback)

# Use a partially applied function
func = lambda typ, value, traceback: catchException(logger, typ, value, traceback)
sys.excepthook = func
6
Mike

Enveloppez votre appel d'entrée d'application dans un try...except bloquer afin que vous puissiez attraper et enregistrer (et peut-être ré-augmenter) toutes les exceptions non capturées. Par exemple. au lieu de:

if __== '__main__':
    main()

Faire ceci:

if __== '__main__':
    try:
        main()
    except Exception as e:
        logger.exception(e)
        raise
5
flaviovs

Peut-être que vous pourriez faire quelque chose en haut d'un module qui redirige stderr vers un fichier, puis enregistrez ce fichier en bas

sock = open('error.log', 'w')               
sys.stderr = sock

doSomething() #makes errors and they will log to error.log

logging.exception(open('error.log', 'r').read() )
3
JiminyCricket

Bien que la réponse de @ gnu_lorien me donne un bon point de départ, mon programme se bloque lors de la première exception.

Je suis arrivé avec une solution personnalisée (et/ou améliorée), qui enregistre silencieusement les exceptions de fonctions décorées avec @handle_error.

import logging

__author__ = 'ahmed'
logging.basicConfig(filename='error.log', level=logging.DEBUG)


def handle_exception(exc_type, exc_value, exc_traceback):
    import sys
    if issubclass(exc_type, KeyboardInterrupt):
        sys.__excepthook__(exc_type, exc_value, exc_traceback)
        return
    logging.critical(exc_value.message, exc_info=(exc_type, exc_value, exc_traceback))


def handle_error(func):
    import sys

    def __inner(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception, e:
            exc_type, exc_value, exc_tb = sys.exc_info()
            handle_exception(exc_type, exc_value, exc_tb)
        finally:
            print(e.message)
    return __inner


@handle_error
def main():
    raise RuntimeError("RuntimeError")


if __== "__main__":
    for _ in xrange(1, 20):
        main()
2
guneysus

Pour répondre à la question de M. Zeus abordée dans la section de commentaire de la réponse acceptée, je l'utilise pour consigner les exceptions non capturées dans une console interactive (testée avec PyCharm 2018-2019). J'ai découvert que sys.excepthook Ne fonctionnait pas dans un shell python), j'ai donc cherché plus profondément et découvert que je pouvais utiliser sys.exc_info À la place. Cependant, sys.exc_info ne prend aucun argument contrairement à sys.excepthook qui prend 3 arguments.

Ici, j'utilise sys.excepthook Et sys.exc_info Pour consigner les deux exceptions dans une console interactive et un script avec une fonction wrapper. Pour attacher une fonction de hook aux deux fonctions, j'ai deux interfaces différentes selon que les arguments sont donnés ou non.

Voici le code:

def log_exception(exctype, value, traceback):
    logger.error("Uncaught exception occurred!",
                 exc_info=(exctype, value, traceback))


def attach_hook(hook_func, run_func):
    def inner(*args, **kwargs):
        if not (args or kwargs):
            # This condition is for sys.exc_info
            local_args = run_func()
            hook_func(*local_args)
        else:
            # This condition is for sys.excepthook
            hook_func(*args, **kwargs)
        return run_func(*args, **kwargs)
    return inner


sys.exc_info = attach_hook(log_exception, sys.exc_info)
sys.excepthook = attach_hook(log_exception, sys.excepthook)

La configuration de la journalisation se trouve dans la réponse de gnu_lorien.

0
Nabs