Journalisation des exceptions non interceptées en Python

181

Comment provoquer la sortie d'exceptions non interceptées via le loggingmodule plutôt que vers stderr?

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

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

Mais ma situation est telle que ce serait vraiment bien si elles logging.exception(...)étaient invoquées automatiquement chaque fois qu'une exception n'est pas interceptée.

Marbre Jacob
la source

Réponses:

143

Comme Ned l'a souligné, sys.excepthookest invoquée chaque fois qu'une exception est levée et non interceptée. L'implication pratique de ceci est que dans votre code, vous pouvez remplacer le comportement par défaut de sys.excepthookpour faire 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
... 

Remplacer sys.excepthook:

>>> sys.excepthook = foo

Validez une erreur de syntaxe évidente (laissez de côté 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, lisez la documentation .

Jacinda
la source
4
@Codemonkey Ce n'est pas un mot-clé réservé, c'est un nom de type préexistant. Vous pouvez utiliser typecomme argument de fonction, bien que les IDE se plaignent de cacher le global type(un peu comme l'utilisation var self = thisen Javascript). Cela n'a pas vraiment d'importance à moins que vous n'ayez besoin d'accéder à l' typeobjet dans votre fonction, auquel cas vous pouvez l'utiliser type_comme argument à la place.
Ryan P
3
La phrase "every time" ici est trompeuse :: "sys.excepthook est invoquée chaque fois qu'une exception est levée et non interceptée" ... parce que dans un programme, il peut y avoir exactement une exception "uncaught". De plus, sys.excepthookn'est PAS appelé lorsqu'une exception est "déclenchée". Il est appelé lorsque le programme va se terminer en raison d'une exception non interceptée, ce qui ne peut pas se produire plus d'une fois.
Nawaz
2
@Nawaz: cela peut arriver plus d'une fois dans une REPL
jfs
2
@Nawaz Cela peut également se produire plusieurs fois si le programme utilise des threads. Il semble également que les boucles d'événements d'interface graphique (comme Qt) continuent de fonctionner, même si l'exception a atteint sys.excepthook
three_pineapples
1
Toute personne essayant de tester le code ci-dessus, assurez-vous que vous générez une erreur de traçage lors du test de votre fonction. SyntaxError n'est pas gérée par sys.excepthook. Vous pouvez utiliser print (1/0) et cela invoquera la fonction que vous avez définie pour remplacer sys.excepthook
Parth Karia
177

Voici un petit exemple complet qui comprend é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 __name__ == "__main__":
    raise RuntimeError("Test unhandled")
  • Ignorez KeyboardInterrupt pour qu'un programme python de console puisse quitter avec Ctrl + C.

  • Fiez-vous entièrement au 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 à stdout plutôt qu'à stderr, mais vous pouvez ajouter toutes sortes de gestionnaires dans ce même style à l'objet logger.

gnu_lorien
la source
13
J'utiliserais logger.critical()à l'intérieur du gestionnaire excepthook, car une exception non interceptée est assez critique je dirais.
gitaarik
2
C'est la réponse la plus pratique de l'OMI.
David Morales
@gnu_lorien merci pour l'extrait. Dans quel fichier mettriez-vous cela?
stelios le
@chefarov Le fichier principal dans lequel vous initialisez tous les autres
journaux
Salut, Comment pouvons-nous écrire dans un fichier comme debug.log cette information. Je l'ai essayé pour ajouter une ligne logging.basicConfig(level=logging.DEBUG, filename="debug.log", format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')mais n'a pas aidé.
GurhanCagin
26

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

Lorsqu'une exception est déclenchée et non interceptée, l'interpréteur appelle sys.excepthook avec trois arguments, la classe d'exception, l'instance d'exception et un objet de trace. Dans une session interactive, cela se produit juste avant que le contrôle ne soit renvoyé à l'invite; dans un programme Python, cela se produit juste avant la fermeture du programme. La gestion de ces exceptions de niveau supérieur peut être personnalisée en attribuant une autre fonction à trois arguments à sys.excepthook.

Ned Batchelder
la source
2
Pourquoi envoie-t-il la classe d'exception? Vous ne pouvez pas toujours l'obtenir en appelant typel'instance?
Neil G
Quel est le type des paramètres de sys.excepthook?
Martin Thoma le
23

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.excepthookcomme 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.excepthookcommentaire:

$ 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 le premier a ERROR:root:Unhandled exception:au début de la première ligne.

Tiago Coutinho
la source
Une autre différence est que le premier écrit la trace dans le système de journalisation, de sorte que tous les gestionnaires et formateurs que vous avez installés sont appliqués. Ce dernier écrit directement dans sys.stderr.
radiaph
8

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
Mike
la source
2
Il serait préférable d'utiliser functools.partial()au lieu de lambda. Voir: docs.python.org/2/library/functools.html#functools.partial
Mariusz Jamro
@MariuszJamro pourquoi?
davr
4

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

if __name__ == '__main__':
    main()

Faites ceci:

if __name__ == '__main__':
    try:
        main()
    except Exception as e:
        logger.exception(e)
        raise
flaviovs
la source
Ce n'est pas ce que la question se pose. L'intention de la question est de demander ce qu'il faut faire lorsque l'exception n'est PAS gérée par le code.
Mayank Jaiswal
1
Eh bien, Python est un langage de programmation, et cela implique qu'il ne fait pas les choses "automatiquement" (comme le souhaite l'OP), sauf si et quand vous lui demandez de le faire. En d'autres termes, il n'y a aucun moyen de consigner "automatiquement" toutes les exceptions, à moins que vous ne codiez pour cela - et c'est ce que contient ma réponse.
flaviovs
1
Eh bien, si vous regardez la réponse de Ned Batchelder, il y a quelque chose appelé crochet d'exception. Vous devez définir à un endroit dans votre code et toutes vos exceptions non interceptées sont gérées.
Mayank Jaiswal
1
Le hook d'exception ne change pas le fait qu'il n'est pas "automatique" (dans le sens souhaité par l'OP) - en d'autres termes, vous devez toujours le coder. La réponse de Ned (qui utilise le crochet d'exception) répond vraiment à la question initiale - c'est juste que, à mon avis , la façon dont il le fait est beaucoup moins pythonique que la mienne.
flaviovs
1
Cela dépend beaucoup de vos propres objectifs. Si vous programmez pour plaire à l'IDE, alors oui, attraper toutes les exceptions pourrait ne pas être une option. Mais si vous voulez gérer les erreurs avec élégance et afficher de bons commentaires à l'utilisateur, je crains que vous n'ayez besoin de détecter toutes les exceptions. Ok, assez de sarcasme :-) - si vous regardez attentivement, vous verrez que le code intercepte l'exception, mais re-relancez alors, donc à moins que votre IDE ne fasse quelque chose de "magique" qu'il ne devrait pas faire, il obtiendra quand même l'éxéption.
flaviovs
3

Vous pourriez peut-être faire quelque chose en haut d'un module qui redirige stderr vers un fichier, puis consigner 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() )
Jiminy Cricket
la source
3

Bien que la réponse de @ gnu_lorien m'ait donné un bon point de départ, mon programme plante à la première exception.

Je suis venu avec une solution personnalisée (et / ou) améliorée, qui enregistre silencieusement les exceptions des fonctions qui sont décorées @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 __name__ == "__main__":
    for _ in xrange(1, 20):
        main()
guneysus
la source
2

Pour répondre à la question de Mr.Zeus discutée dans la section commentaire de la réponse acceptée, j'utilise ceci pour enregistrer les exceptions non détectées dans une console interactive (testée avec PyCharm 2018-2019). J'ai découvert que sys.excepthookcela ne fonctionnait pas dans un shell python, alors j'ai regardé plus profondément et j'ai trouvé que je pourrais l'utiliser à la sys.exc_infoplace. Cependant, sys.exc_infone prend aucun argument contrairement à sys.excepthookcela prend 3 arguments.

Ici, j'utilise les deux sys.excepthooket sys.exc_infopour enregistrer les deux exceptions dans une console interactive et un script avec une fonction wrapper. Pour attacher une fonction hook aux deux fonctions, j'ai deux interfaces différentes selon que des 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 peut être trouvée dans la réponse de gnu_lorien.

Nabs
la source
2

Dans mon cas (en utilisant python 3) lors de l'utilisation de la réponse de @Jacinda, le contenu du retraçage n'a pas été imprimé. Au lieu de cela, il imprime juste l'objet lui - même: <traceback object at 0x7f90299b7b90>.

Au lieu de cela, je fais:

import sys
import logging
import traceback

def custom_excepthook(exc_type, exc_value, exc_traceback):
    # Do not print exception when user cancels the program
    if issubclass(exc_type, KeyboardInterrupt):
        sys.__excepthook__(exc_type, exc_value, exc_traceback)
        return

    logging.error("An uncaught exception occurred:")
    logging.error("Type: %s", exc_type)
    logging.error("Value: %s", exc_value)

    if exc_traceback:
        format_exception = traceback.format_tb(exc_traceback)
        for line in format_exception:
            logging.error(repr(line))

sys.excepthook = custom_excepthook
veuncent
la source