Bagaimana cara mencatat kesalahan Python dengan informasi debug?

469

Saya mencetak pesan pengecualian Python ke file log dengan logging.error:

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

Apakah mungkin untuk mencetak informasi yang lebih terperinci tentang pengecualian dan kode yang dihasilkannya daripada hanya string pengecualian? Hal-hal seperti nomor baris atau jejak tumpukan akan menjadi hal yang hebat.

mungkin di pantai
sumber

Jawaban:

735

logger.exception akan menampilkan jejak tumpukan di samping pesan kesalahan.

Sebagai contoh:

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

Keluaran:

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

@Paulo Periksa catatan, "ketahuilah bahwa dengan Python 3 Anda harus memanggil logging.exceptionmetode tepat di dalam exceptbagian. Jika Anda memanggil metode ini di tempat yang sewenang-wenang, Anda mungkin akan mendapatkan pengecualian yang aneh. Doks memperingatkan tentang hal itu."

SiggyF
sumber
131
The exceptionMetode hanya panggilan error(message, exc_info=1). Segera setelah Anda melewati exc_infosalah satu metode logging dari konteks pengecualian, Anda akan mendapatkan traceback.
Helmut Grohne
16
Anda juga dapat mengatur sys.excepthook(lihat di sini ) untuk menghindari keharusan membungkus semua kode Anda di coba / kecuali.
juli
23
Anda bisa saja menulis except Exception:karena Anda tidak menggunakan ecara apa pun;)
Marco Ferrari
21
Anda mungkin ingin memeriksa eketika mencoba untuk debug kode Anda secara interaktif. :) Inilah sebabnya saya selalu memasukkannya.
Vicki Laidler
4
Perbaiki saya jika saya salah, dalam hal ini, tidak ada penanganan nyata pengecualian dan oleh karena itu masuk akal untuk menambahkan raisedi akhir exceptruang lingkup. Kalau tidak, berlari akan berlanjut seolah semuanya baik-baik saja.
Dror
184

Satu hal logging.exceptionyang menyenangkan tentang jawaban SiggyF ini tidak muncul adalah bahwa Anda dapat mengirimkan pesan yang sewenang-wenang, dan pencatatan log masih akan menampilkan traceback penuh dengan semua detail pengecualian:

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

Dengan perilaku pencatatan default (dalam versi terbaru) untuk kesalahan pencetakan saja sys.stderr, tampilannya seperti ini:

>>> 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
sumber
Bisakah pengecualian dicatat tanpa memberikan pesan?
Stevoisiak
@StevenVascellaro - Saya sarankan Anda lulus ''jika Anda benar-benar tidak ingin mengetik pesan ... fungsi tidak dapat dipanggil tanpa setidaknya satu argumen, jadi Anda harus memberikan sesuatu.
ArtOfWarfare
147

Menggunakan exc_infoopsi mungkin lebih baik, untuk memungkinkan Anda memilih tingkat kesalahan (jika Anda menggunakannya exception, itu akan selalu berada di errortingkat):

try:
    # do something here
except Exception as e:
    logging.critical(e, exc_info=True)  # log exception info at CRITICAL log level
flycee
sumber
@CivFan: Saya tidak benar-benar melihat pengeditan lain atau intro tulisan; intro itu juga ditambahkan oleh editor pihak ke-3. Saya tidak melihat di mana pun di komentar yang dihapus itu yang pernah menjadi maksudnya, tapi saya mungkin juga membatalkan edit saya dan menghapus komentar, sudah terlalu lama untuk pemungutan suara di sini untuk apa pun selain versi yang diedit .
Martijn Pieters
Apakah logging.fatalmetode di perpustakaan logging? Saya hanya melihat critical.
Ian
1
@Ian Ini alias untuk critical, sama seperti warnuntuk warning.
0xc0de
35

Mengutip

Bagaimana jika aplikasi Anda melakukan logging dengan cara lain - tidak menggunakan loggingmodul?

Sekarang, tracebackbisa digunakan di sini.

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)
  • Gunakan dalam Python 2 :

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

    try:
        x = get_number()
    except Exception as ex:
        log_traceback(ex)
    
zangw
sumber
Mengapa Anda menempatkan "_, _, ex_traceback = sys.exc_info ()" di luar fungsi log_traceback dan kemudian meneruskannya sebagai argumen? Mengapa tidak menggunakannya langsung di dalam fungsi?
Basil Musa
@BasilMusa, untuk menjawab pertanyaan Anda, singkatnya, agar kompatibel dengan Python 3, karena ex_tracebackdari ex.__traceback__bawah Python 3, tetapi ex_tracebackdari sys.exc_info()bawah Python 2.
zangw
12

Jika Anda menggunakan log polos - semua catatan log Anda harus sesuai aturan ini: one record = one line. Mengikuti aturan ini, Anda dapat menggunakan grepdan alat lain untuk memproses file log Anda.

Tetapi informasi traceback bersifat multi-line. Jadi jawaban saya adalah versi diperpanjang dari solusi yang diusulkan oleh zangw di atas di utas ini. Masalahnya adalah bahwa garis traceback bisa ada \ndi dalam, jadi kita perlu melakukan pekerjaan ekstra untuk menyingkirkan ujung garis ini:

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

Setelah itu (ketika Anda akan menganalisis log), Anda dapat menyalin / menempelkan baris traceback yang diperlukan dari file log Anda dan melakukan ini:

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

Keuntungan!

doomatel
sumber
9

Jawaban ini dibangun dari yang terbaik di atas.

Di sebagian besar aplikasi, Anda tidak akan memanggil logging.exception (e) secara langsung. Kemungkinan besar Anda telah menetapkan logger khusus untuk aplikasi atau modul Anda seperti ini:

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

Dalam hal ini, cukup gunakan logger untuk memanggil pengecualian (e) seperti ini:

try:
    1/0
except ZeroDivisionError, e:
    my_logger.exception(e)
Akan
sumber
Ini memang sentuhan akhir yang bermanfaat jika Anda menginginkan logger khusus hanya untuk pengecualian.
logicOnAbstractions
7

Anda dapat mencatat jejak stack tanpa terkecuali.

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

Argumen kata kunci opsional kedua adalah stack_info, yang defaultnya menjadi False. Jika benar, informasi tumpukan ditambahkan ke pesan logging, termasuk panggilan logging aktual. Perhatikan bahwa ini bukan informasi tumpukan yang sama seperti yang ditampilkan melalui menentukan exc_info: Yang pertama adalah tumpukan frame dari bagian bawah tumpukan hingga panggilan logging di utas saat ini, sedangkan yang terakhir adalah informasi tentang bingkai tumpukan yang telah dibatalkan, mengikuti pengecualian, saat mencari penangan pengecualian.

Contoh:

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

Sedikit perawatan dekorator (sangat longgar terinspirasi oleh Mungkin monad dan mengangkat). Anda dapat menghapus anotasi tipe Python 3.6 dengan aman dan menggunakan gaya pemformatan pesan yang lebih lama.

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'

Anda juga dapat memodifikasi solusi ini untuk mengembalikan sesuatu yang sedikit lebih bermakna daripada Nonedari exceptbagian (atau bahkan membuat solusi generik, dengan menentukan nilai pengembalian ini dalam fallibleargumennya).

Eli Korvigo
sumber
0

Dalam modul logging Anda (jika modul khusus) aktifkan stack_info.

api_logger.exceptionLog("*Input your Custom error message*",stack_info=True)
Piyush Kumar
sumber
-1

Jika Anda dapat mengatasi ketergantungan tambahan kemudian menggunakan twisted.log, Anda tidak perlu secara eksplisit mencatat kesalahan dan juga mengembalikan seluruh traceback dan waktu ke file atau streaming.

Jakob Bowyer
sumber
8
Mungkin twistedrekomendasi yang bagus, tetapi jawaban ini tidak banyak berkontribusi. Ia tidak mengatakan bagaimana cara menggunakannya twisted.log, atau kelebihan apa yang dimilikinya daripada loggingmodul dari pustaka standar, atau menjelaskan apa yang dimaksud dengan "Anda tidak harus secara eksplisit mencatat kesalahan" .
Mark Amery
-8

Cara bersih untuk melakukannya adalah menggunakan format_exc()dan kemudian mengurai output untuk mendapatkan bagian yang relevan:

from traceback import format_exc

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

Salam

caraconan
sumber
4
Hah? Mengapa itu "bagian yang relevan" ? Yang .split('\n')[-2]dilakukan hanyalah membuang nomor baris dan traceback dari hasil format_exc()- informasi berguna yang biasanya Anda inginkan! Terlebih lagi, bahkan tidak melakukan pekerjaan yang baik itu ; jika pesan pengecualian Anda berisi baris baru, maka pendekatan ini hanya akan mencetak baris terakhir dari pesan pengecualian - yang berarti bahwa Anda kehilangan kelas pengecualian dan sebagian besar pesan pengecualian di atas kehilangan traceback. -1.
Mark Amery