Jak zalogować się do błędu Pythona przy użyciu informacji debugowania?

468

Drukuję komunikaty o wyjątkach Pythona do pliku dziennika z logging.error:

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

Czy można wydrukować bardziej szczegółowe informacje na temat wyjątku i kodu, który go wygenerował, niż tylko ciąg wyjątku? Takie rzeczy jak numery linii lub ślady stosu byłyby świetne.

prawdopodobnie na plaży
źródło

Odpowiedzi:

733

logger.exception wyświetli ślad stosu obok komunikatu o błędzie.

Na przykład:

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

Wynik:

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

@Paulo Check zauważa: „pamiętaj, że w Pythonie 3 musisz wywołać logging.exceptionmetodę bezpośrednio w exceptczęści. Jeśli wywołasz tę metodę w dowolnym miejscu, możesz uzyskać dziwny wyjątek. Dokumenty ostrzegają o tym”.

SiggyF
źródło
131
exceptionMetoda po prostu wywołuje error(message, exc_info=1). Jak tylko przejdziesz exc_infodo dowolnej metody rejestrowania z kontekstu wyjątku, otrzymasz traceback.
Helmut Grohne
16
Możesz także ustawić sys.excepthook(patrz tutaj ), aby uniknąć konieczności owijania całego kodu w try / wyjątkiem.
lip
23
Możesz po prostu pisać, except Exception:ponieważ nie używasz ew żaden sposób;)
Marco Ferrari
21
Możesz bardzo chcieć sprawdzić, ekiedy próbujesz interaktywnie debugować swój kod. :) Dlatego zawsze to uwzględniam.
Vicki Laidler
4
Popraw mnie, jeśli się mylę, w tym przypadku nie ma prawdziwej obsługi wyjątku, dlatego warto dodać raisena końcu exceptzakresu. W przeciwnym razie działanie będzie kontynuowane, jakby wszystko było w porządku.
Dror,
184

Jedną fajną rzeczą w tym, logging.exceptionże odpowiedź SiggyF nie pokazuje, jest to, że możesz przekazać dowolną wiadomość, a rejestrowanie nadal pokaże pełne śledzenie ze wszystkimi szczegółami wyjątków:

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

Przy domyślnym (w ostatnich wersjach) sposobie rejestrowania tylko błędów drukowania sys.stderr, wygląda to tak:

>>> 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
ncoghlan
źródło
Czy można zarejestrować wyjątek bez podania komunikatu?
Stevoisiak
@StevenVascellaro - Sugeruję podanie, ''jeśli naprawdę nie chcesz pisać wiadomości ... funkcja nie może zostać wywołana bez co najmniej jednego argumentu, więc musisz jej coś dać.
ArtOfWarfare
147

Korzystanie z exc_infoopcji może być lepsze, aby umożliwić wybór poziomu błędu (jeśli go użyjesz exception, zawsze będzie on na errorpoziomie):

try:
    # do something here
except Exception as e:
    logging.critical(e, exc_info=True)  # log exception info at CRITICAL log level
flycee
źródło
@CivFan: Właściwie to nie patrzyłem na inne zmiany ani wprowadzenie do posta; to wprowadzenie zostało również dodane przez zewnętrznego edytora. Nigdzie w usuniętych komentarzach nie widzę takiego zamiaru, ale równie dobrze mogę cofnąć edycję i usunąć komentarze, minęło już zbyt wiele czasu, aby głosowanie tutaj dotyczyło czegoś innego niż edytowana wersja .
Martijn Pieters
Czy logging.fatalw bibliotece rejestrowania jest metoda? Tylko widzę critical.
Ian
1
@Ian To pseudonim critical, tak jak warnto jest warning.
0xc0de
35

Cytowanie

Co się stanie, jeśli aplikacja rejestruje się w inny sposób - nie używając loggingmodułu?

Teraz tracebackmożna go tutaj użyć.

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)
  • Użyj go w Pythonie 2 :

    try:
        # your function call is here
    except Exception as ex:
        _, _, ex_traceback = sys.exc_info()
        log_traceback(ex, ex_traceback)
  • Użyj go w Pythonie 3 :

    try:
        x = get_number()
    except Exception as ex:
        log_traceback(ex)
zangw
źródło
Dlaczego umieściłeś „_, _, ex_traceback = sys.exc_info ()” poza funkcją log_traceback, a następnie przekazałeś go jako argument? Dlaczego nie użyć go bezpośrednio w funkcji?
Basil Musa,
@BasilMusa, aby w skrócie odpowiedzieć na twoje pytanie, aby był zgodny z Python 3, ponieważ ex_tracebackpochodzi z ex.__traceback__Python 3, ale ex_tracebackpochodzi z sys.exc_info()Python 2.
zangw
12

Jeśli używasz zwykłego dzienniki - wszystkie rekordy dziennika powinna odpowiadać tej reguły: one record = one line. Zgodnie z tą zasadą możesz używać grepinnych narzędzi do przetwarzania plików dziennika.

Ale informacje śledzenia są wieloliniowe. Tak więc moją odpowiedzią jest rozszerzona wersja rozwiązania zaproponowanego przez zangw powyżej w tym wątku. Problem polega na tym, że linie śledzenia mogą mieć \nwewnątrz, więc musimy wykonać dodatkową pracę, aby pozbyć się zakończeń linii:

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

Następnie (podczas analizy dzienników) możesz skopiować / wkleić wymagane wiersze śledzenia z pliku dziennika i wykonać następujące czynności:

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

Zysk!

doomatel
źródło
9

Ta odpowiedź składa się z powyższych doskonałych.

W większości aplikacji nie będziesz dzwonić bezpośrednio do dziennika.exception (e). Najprawdopodobniej zdefiniowałeś niestandardowy rejestrator specyficzny dla aplikacji lub modułu w następujący sposób:

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

W takim przypadku wystarczy użyć programu rejestrującego, aby wywołać wyjątek (e) w następujący sposób:

try:
    1/0
except ZeroDivisionError, e:
    my_logger.exception(e)
Będzie
źródło
Jest to przydatne wykończenie, jeśli chcesz dedykowanego rejestratora tylko dla wyjątków.
logicOnAbstractions
7

Możesz zarejestrować ślad stosu bez wyjątku.

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

Drugim opcjonalnym argumentem słowa kluczowego jest stack_info, domyślnie False. Jeśli true, do stosu dodawane są informacje stosu, w tym faktyczne wywołanie rejestrowania. Zauważ, że nie są to te same informacje o stosie, jakie są wyświetlane poprzez określenie exc_info: Pierwsza to ramki stosu od dołu stosu aż do wywołania rejestrowania w bieżącym wątku, podczas gdy drugie to informacje o klatkach stosu, które zostały rozwinięte, po wyjątku podczas wyszukiwania procedur obsługi wyjątków.

Przykład:

>>> 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>
>>>
Baczek
źródło
5

Trochę obróbki dekoratora (bardzo luźno zainspirowane Być może monadą i liftingiem). Możesz bezpiecznie usunąć adnotacje typu Python 3.6 i użyć starszego stylu formatowania wiadomości.

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

Próbny:

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'

Możesz również zmodyfikować to rozwiązanie, aby zwracało coś nieco bardziej znaczącego niż Nonez exceptczęści (lub nawet uczynić rozwiązanie ogólnym, podając tę ​​wartość zwracaną w fallibleargumentach).

Eli Korvigo
źródło
0

W module logowania (jeśli jest to moduł niestandardowy) wystarczy włączyć informacje o stosie.

api_logger.exceptionLog("*Input your Custom error message*",stack_info=True)
Piyush Kumar
źródło
-1

Jeśli możesz poradzić sobie z dodatkową zależnością, to użyj twisted.log, nie musisz jawnie rejestrować błędów, a także zwraca cały ślad i czas do pliku lub strumienia.

Jakob Bowyer
źródło
8
Być może twistedjest to dobra rekomendacja, ale ta odpowiedź tak naprawdę niewiele wnosi. Nie mówi, jak używać twisted.log, ani jakie ma zalety w stosunku do loggingmodułu ze standardowej biblioteki, ani nie wyjaśnia, co oznacza „nie musisz jawnie rejestrować błędów” .
Mark Amery
-8

Czystym sposobem na to jest użycie, format_exc()a następnie parsowanie danych wyjściowych, aby uzyskać odpowiednią część:

from traceback import format_exc

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

pozdrowienia

karakan
źródło
4
Co? Dlaczego jest to „odpowiednia część” ? Wszystko, .split('\n')[-2]co trzeba, to wyrzucić numer linii i ślad po wyniku format_exc()- przydatnych informacji, których normalnie potrzebujesz! Co więcej, nawet nie wykonuje dobrą robotę , że ; jeśli komunikat wyjątku zawiera nowy wiersz, to podejście spowoduje wydrukowanie tylko ostatniego wiersza komunikatu wyjątku - co oznacza, że ​​utracisz klasę wyjątku i większość komunikatu wyjątku oprócz utraty śledzenia. -1.
Mark Amery