web-dev-qa-db-fra.com

Comment enregistrer une erreur Python avec des informations de débogage?

J'imprime des messages d'exception Python dans un fichier journal contenant logging.error:

import logging
try:
    1/0
except ZeroDivisionError as e:
    logging.error(e)  # ERROR:root:division by zero

Est-il possible d'imprimer des informations plus détaillées sur l'exception et le code qui l'a générée que la chaîne d'exception? Des choses comme les numéros de ligne ou les traces de pile seraient géniales.

393

logger.exception générera une trace de pile à côté du message d'erreur.

Par exemple:

_import logging
try:
    1/0
except ZeroDivisionError as e:
    logging.exception("message")
_

Sortie:

_ERROR:root:message
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: integer division or modulo by zero
_

@ Paulo Check remarque, "notez que, dans Python 3, vous devez appeler la méthode _logging.exception_ située juste à l'intérieur de la partie except. Si vous appelez cette méthode dans un endroit arbitraire, vous pouvez obtenir une exception bizarre. La documentation alerte à ce sujet. "

622
SiggyF

Une bonne chose à propos de logging.exception que la réponse de SiggyF ne s'affiche pas, c'est que vous pouvez transmettre un message arbitraire et que la journalisation affichera le traçage complet avec tous les détails de l'exception:

import logging
try:
    1/0
except ZeroDivisionError:
    logging.exception("Deliberate divide by zero traceback")

Avec le comportement de consignation par défaut (dans les versions récentes) consistant à imprimer uniquement les erreurs sur sys.stderr, il se présente comme suit:

>>> import logging
>>> try:
...     1/0
... except ZeroDivisionError:
...     logging.exception("Deliberate divide by zero traceback")
... 
ERROR:root:Deliberate divide by zero traceback
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: integer division or modulo by zero
171
ncoghlan

Utiliser les options exc_info peut être mieux, pour vous permettre de choisir le niveau d'erreur (si vous utilisez exception, il affichera toujours error):

try:
    # do something here
except Exception as e:
    logging.fatal(e, exc_info=True)  # log exception info at FATAL log level
109
flycee

Citant

Que se passe-t-il si votre application enregistre d'une autre manière - sans utiliser le module logging?

Maintenant, traceback pourrait être utilisé ici.

import traceback

def log_traceback(ex, ex_traceback=None):
    if ex_traceback is None:
        ex_traceback = ex.__traceback__
    tb_lines = [ line.rstrip('\n') for line in
                 traceback.format_exception(ex.__class__, ex, ex_traceback)]
    exception_logger.log(tb_lines)
  • Utilisez-le dans Python 2 :

    try:
        # your function call is here
    except Exception as ex:
        _, _, ex_traceback = sys.exc_info()
        log_traceback(ex, ex_traceback)
    
  • Utilisez-le dans Python 3 :

    try:
        x = get_number()
    except Exception as ex:
        log_traceback(ex)
    
31
zangw

Si vous utilisez des journaux clairs - tous vos enregistrements de journal doivent correspondre à cette règle: one record = one line. En suivant cette règle, vous pouvez utiliser grep et d’autres outils pour traiter vos fichiers journaux.

Mais les informations de traçage sont multilignes. Donc, ma réponse est une version étendue de la solution proposée par zangw ci-dessus dans ce fil. Le problème est que les lignes de traçage peuvent contenir \n, nous devons donc effectuer un travail supplémentaire pour supprimer les fins de ligne:

import logging


logger = logging.getLogger('your_logger_here')

def log_app_error(e: BaseException, level=logging.ERROR) -> None:
    e_traceback = traceback.format_exception(e.__class__, e, e.__traceback__)
    traceback_lines = []
    for line in [line.rstrip('\n') for line in e_traceback]:
        traceback_lines.extend(line.splitlines())
    logger.log(level, traceback_lines.__str__())

Après cela (lorsque vous analyserez vos journaux), vous pourrez copier/coller les lignes de suivi requises à partir de votre fichier journal et procédez comme suit:

ex_traceback = ['line 1', 'line 2', ...]
for line in ex_traceback:
    print(line)

Profit!

12
doomatel

Cette réponse se construit à partir des excellents précédents.

Dans la plupart des applications, vous n'appelez pas logging.exception (e) directement. Vous avez probablement défini un enregistreur personnalisé spécifique à votre application ou à votre module, comme suit:

# Set the name of the app or module
my_logger = logging.getLogger('NEM Sequencer')
# Set the log level
my_logger.setLevel(logging.INFO)

# Let's say we want to be fancy and log to a graylog2 log server
graylog_handler = graypy.GELFHandler('some_server_ip', 12201)
graylog_handler.setLevel(logging.INFO)
my_logger.addHandler(graylog_handler)

Dans ce cas, utilisez simplement l'enregistreur pour appeler l'exception (e) comme ceci:

try:
    1/0
except ZeroDivisionError, e:
    my_logger.exception(e)
9
Will

Un peu de traitement de décorateur (très inspiré par la monade Maybe et le lifting). Vous pouvez supprimer en toute sécurité Python 3.6 des annotations de type et utiliser un style de formatage de message plus ancien.

faillible.py

from functools import wraps
from typing import Callable, TypeVar, Optional
import logging


A = TypeVar('A')


def fallible(*exceptions, logger=None) \
        -> Callable[[Callable[..., A]], Callable[..., Optional[A]]]:
    """
    :param exceptions: a list of exceptions to catch
    :param logger: pass a custom logger; None means the default logger, 
                   False disables logging altogether.
    """
    def fwrap(f: Callable[..., A]) -> Callable[..., Optional[A]]:

        @wraps(f)
        def wrapped(*args, **kwargs):
            try:
                return f(*args, **kwargs)
            except exceptions:
                message = f'called {f} with *args={args} and **kwargs={kwargs}'
                if logger:
                    logger.exception(message)
                if logger is None:
                    logging.exception(message)
                return None

        return wrapped

    return fwrap

Démo:

In [1] from fallible import fallible

In [2]: @fallible(ArithmeticError)
    ...: def div(a, b):
    ...:     return a / b
    ...: 
    ...: 

In [3]: div(1, 2)
Out[3]: 0.5

In [4]: res = div(1, 0)
ERROR:root:called <function div at 0x10d3c6ae8> with *args=(1, 0) and **kwargs={}
Traceback (most recent call last):
  File "/Users/user/fallible.py", line 17, in wrapped
    return f(*args, **kwargs)
  File "<ipython-input-17-e056bd886b5c>", line 3, in div
    return a / b

In [5]: repr(res)
'None'

Vous pouvez également modifier cette solution pour renvoyer quelque chose de plus significatif que None à partir de la partie except (ou même rendre la solution générique en spécifiant cette valeur de retour dans les arguments de fallible.).

4
Eli Korvigo