Come posso registrare un errore Python con le informazioni di debug?


470

Sto stampando i messaggi di eccezione Python su un file di registro con logging.error :

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

È possibile stampare informazioni più dettagliate sull'eccezione e sul codice che l'ha generata oltre alla sola stringa di eccezione? Cose come numeri di riga o tracce di stack sarebbero grandiose.

Risposte:


735

logger.exception genererà una traccia dello stack accanto al messaggio di errore.

Per esempio:

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

Produzione:

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

@Paulo Check note, "tenere presente che in Python 3 è necessario chiamare il logging.exceptionmetodo appena all'interno della exceptparte. Se si chiama questo metodo in un luogo arbitrario, si potrebbe ottenere un'eccezione bizzarra. I documenti lo avvisano."


131
Il exceptionmetodo chiama semplicemente error(message, exc_info=1). Non appena si passa exc_infoa uno dei metodi di registrazione da un contesto di eccezione, si ottiene un traceback.
Helmut Grohne,

16
Puoi anche impostare sys.excepthook(vedi qui ) per evitare di dover racchiudere tutto il tuo codice in try / tranne.
lug

23
Potresti semplicemente scrivere except Exception:perché non stai usando ein alcun modo;)
Marco Ferrari,

21
È possibile che si desideri verificare ese si tenta di eseguire il debug interattivo del codice. :) Questo è il motivo per cui lo includo sempre.
Vicki Laidler,

4
Correggimi se sbaglio, in questo caso, non esiste una vera gestione dell'eccezione e quindi ha senso aggiungere raisealla fine exceptdell'ambito. Altrimenti, la corsa continuerà come se tutto andasse bene.
Dror,

184

Una cosa bella del fatto logging.exceptionche la risposta di SiggyF non mostra è che puoi passare un messaggio arbitrario e la registrazione mostrerà comunque il traceback completo con tutti i dettagli dell'eccezione:

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

Con il comportamento di registrazione predefinito (nelle versioni recenti) di solo errori di stampa sys.stderr, è simile al seguente:

>>> 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

È possibile registrare un'eccezione senza fornire un messaggio?
Stevoisiak,

@StevenVascellaro - Ti suggerirei di passare ''se davvero non vuoi scrivere un messaggio ... la funzione non può essere chiamata senza almeno un argomento, quindi dovrai dargli qualcosa.
ArtOfWarfare il

147

L'uso delle exc_infoopzioni può essere migliore, per consentirti di scegliere il livello di errore (se lo usi exception, sarà sempre a errorlivello):

try:
    # do something here
except Exception as e:
    logging.critical(e, exc_info=True)  # log exception info at CRITICAL log level

@CivFan: in realtà non ho guardato le altre modifiche o l'intro post; quell'introduzione è stata aggiunta anche da un editore di terze parti. Non vedo da nessuna parte nei commenti cancellati che questa sia mai stata l'intenzione, ma potrei anche annullare la mia modifica e rimuovere i commenti, è passato troppo tempo perché il voto qui sia stato diverso da quello della versione modificata .
Martijn Pieters

È logging.fatalun metodo nella libreria di registrazione? Vedo solo critical.
Ian,

1
@Ian È un alias critical, proprio come lo warnè warning.
0xc0de,

35

citando

Che cosa succede se la tua applicazione effettua la registrazione in un altro modo - non usando il loggingmodulo?

Ora, tracebackpotrebbe essere usato qui.

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)
  • Usalo in Python 2 :

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

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

Perché hai inserito "_, _, ex_traceback = sys.exc_info ()" fuori dalla funzione log_traceback e poi lo hai passato come argomento? Perché non utilizzarlo direttamente all'interno della funzione?
Basil Musa,

@BasilMusa, per rispondere alla tua domanda, in breve, compatibile con Python 3, perché il ex_traceback proviene da ex.__traceback__Python 3, ma ex_tracebackproviene da sys.exc_info()Python 2.
zangw

12

Se si utilizzano i registri di pianura - tutti i record del registro devono corrispondere questa regola: one record = one line. Seguendo questa regola è possibile utilizzare grepe altri strumenti per elaborare i file di registro.

Ma le informazioni di traceback sono multilinea. Quindi la mia risposta è una versione estesa della soluzione proposta da zangw sopra in questo thread. Il problema è che le linee di traceback potrebbero avere \nall'interno, quindi dobbiamo fare un lavoro extra per sbarazzarci di questi finali di riga:

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__())

Successivamente (quando analizzerai i tuoi registri) puoi copiare / incollare le linee di traceback richieste dal tuo file di registro e fare questo:

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

Profitto!


9

Questa risposta si basa su quelle eccellenti sopra.

Nella maggior parte delle applicazioni, non chiamerai direttamente logging.exception (e). Molto probabilmente hai definito un logger personalizzato specifico per la tua applicazione o modulo in questo modo:

# 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)

In questo caso, basta usare il logger per chiamare l'eccezione (e) in questo modo:

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

Questo è un utile tocco finale se vuoi un logger dedicato solo per le eccezioni.
logicOnAbstractions

7

È possibile registrare la traccia dello stack senza eccezioni.

https://docs.python.org/3/library/logging.html#logging.Logger.debug

Il secondo argomento della parola chiave opzionale è stack_info, che per impostazione predefinita è False. Se vero, le informazioni sullo stack vengono aggiunte al messaggio di registrazione, inclusa la chiamata di registrazione effettiva. Tieni presente che queste non sono le stesse informazioni sullo stack visualizzate attraverso la specifica di exc_info: il primo è frame dello stack dalla parte inferiore dello stack fino alla chiamata di registrazione nel thread corrente, mentre il secondo sono informazioni sui frame dello stack che sono state srotolate, a seguito di un'eccezione, durante la ricerca di gestori di eccezioni.

Esempio:

>>> import logging
>>> logging.basicConfig(level=logging.DEBUG)
>>> logging.getLogger().info('This prints the stack', stack_info=True)
INFO:root:This prints the stack
Stack (most recent call last):
  File "<stdin>", line 1, in <module>
>>>

5

Un po 'di trattamento per decoratori (ispirato in modo molto approssimativo alla monade e al lifting forse). Puoi rimuovere in sicurezza le annotazioni di tipo Python 3.6 e utilizzare uno stile di formattazione dei messaggi meno recente.

fallible.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

demo:

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'

Puoi anche modificare questa soluzione per restituire qualcosa di più significativo di None alla exceptparte (o persino rendere la soluzione generica, specificando questo valore di ritorno negli fallibleargomenti di).


0

Nel tuo modulo di registrazione (se modulo personalizzato) basta abilitare stack_info.

api_logger.exceptionLog("*Input your Custom error message*",stack_info=True)

-1

Se è possibile far fronte alla dipendenza aggiuntiva, utilizzare twisted.log, non è necessario registrare esplicitamente gli errori e restituire l'intero traceback e il tempo al file o al flusso.


8
Forse twistedè una buona raccomandazione, ma questa risposta in realtà non contribuisce molto. Non dice come usare twisted.log, né quali vantaggi ha rispetto al loggingmodulo dalla libreria standard, né spiega cosa si intende per "non è necessario registrare esplicitamente gli errori" .
Mark Amery,

-8

Un modo chiaro per farlo è usare format_exc()e quindi analizzare l'output per ottenere la parte pertinente:

from traceback import format_exc

try:
    1/0
except Exception:
    print 'the relevant part is: '+format_exc().split('\n')[-2]

Saluti


4
Eh? Perché quella "parte rilevante" ? Tutto ciò che .split('\n')[-2]fa è buttare via il numero di riga e il traceback dal risultato di format_exc()- informazioni utili che normalmente vuoi! Cosa c'è di più, non ha nemmeno fare un buon lavoro di che ; se il tuo messaggio di eccezione contiene una nuova riga, questo approccio stamperà solo la riga finale del messaggio di eccezione, il che significa che perdi la classe di eccezione e la maggior parte del messaggio di eccezione oltre a perdere il traceback. -1.
Mark Amery,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.