Mencatat pengecualian yang tidak tertangkap dalam Python

181

Bagaimana Anda menyebabkan pengecualian tanpa tertangkap ke output melalui loggingmodul daripada ke stderr?

Saya menyadari cara terbaik untuk melakukan ini adalah:

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

Tapi situasi saya sedemikian rupa sehingga akan sangat baik jika logging.exception(...)dipanggil secara otomatis setiap kali pengecualian tidak tertangkap.

Jacob Marble
sumber

Jawaban:

143

Seperti yang ditunjukkan Ned, sys.excepthookdipanggil setiap kali pengecualian muncul dan tidak tertangkap. Implikasi praktis dari hal ini adalah bahwa dalam kode Anda, Anda dapat mengganti perilaku default sys.excepthookuntuk melakukan apa pun yang Anda inginkan (termasuk menggunakan logging.exception).

Sebagai contoh pria jerami:

>>> import sys
>>> def foo(exctype, value, tb):
...     print 'My Error Information'
...     print 'Type:', exctype
...     print 'Value:', value
...     print 'Traceback:', tb
... 

Timpa sys.excepthook:

>>> sys.excepthook = foo

Komit kesalahan sintaksis yang jelas (tinggalkan titik dua) dan dapatkan kembali informasi kesalahan khusus:

>>> def bar(a, b)
My Error Information
Type: <type 'exceptions.SyntaxError'>
Value: invalid syntax (<stdin>, line 1)
Traceback: None

Untuk informasi lebih lanjut tentang sys.excepthook, baca dokumen .

Jacinda
sumber
4
@Codemonkey Ini bukan kata kunci yang dipesan, ini adalah nama jenis yang sudah ada. Anda dapat menggunakan typesebagai argumen fungsi, meskipun IDE akan mengeluh tentang menyembunyikan global type(seperti menggunakan var self = thisJavascript). Tidak masalah kecuali Anda perlu mengakses typeobjek di dalam fungsi Anda, dalam hal ini Anda dapat menggunakan type_sebagai argumen.
Ryan P
3
Ungkapan "setiap kali" di sini adalah menyesatkan :: "sys.excepthook dipanggil setiap kali eksepsi dimunculkan dan tertangkap" ... karena dalam sebuah program, ada dapat menjadi tepat satu "tertangkap" pengecualian. Juga, sys.excepthookTIDAK disebut ketika pengecualian "dinaikkan". Disebut ketika program akan berakhir karena pengecualian tanpa tertangkap, yang tidak dapat terjadi lebih dari sekali.
Nawaz
2
@Nawaz: itu bisa terjadi lebih dari sekali dalam REPL
jfs
2
@Nawaz Hal ini juga dapat terjadi beberapa kali jika program menggunakan utas. Saya juga tampaknya loop acara GUI (seperti Qt) terus berjalan, meskipun pengecualian telah membuatnya menjadi sys.excepthook
three_pineapples
1
Siapa pun yang mencoba menguji kode di atas, pastikan Anda membuat kesalahan traceback saat menguji fungsi Anda. SyntaxError tidak sedang ditangani oleh sys.excepthook. Anda dapat menggunakan print (1/0) dan ini akan memanggil fungsi yang telah Anda tentukan untuk mengesampingkan sys.excepthook
Parth Karia
177

Berikut adalah contoh kecil lengkap yang juga mencakup beberapa trik lain:

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")
  • Abaikan KeyboardInterrupt sehingga program python konsol dapat keluar dengan Ctrl + C.

  • Bergantung sepenuhnya pada modul logging python untuk memformat pengecualian.

  • Gunakan logger khusus dengan handler contoh. Yang ini mengubah pengecualian tidak tertangani untuk pergi ke stdout daripada stderr, tetapi Anda bisa menambahkan semua jenis penangan dengan gaya yang sama ke objek logger.

gnu_lorien
sumber
13
Saya akan menggunakan logger.critical()di dalam handler excepthook, karena pengecualian tanpa penangkapan cukup penting yang akan saya katakan.
gitaarik
2
Ini adalah jawaban IMO paling praktis.
David Morales
@gnu_lorien terima kasih atas cuplikannya. Di file mana Anda akan meletakkan ini?
stelios
@chefarov File utama tempat Anda menginisialisasi semua logging lainnya
gnu_lorien
Hai, Bagaimana kita bisa menulis ke file seperti debug.log info ini. saya mencoba menambahkan baris logging.basicConfig(level=logging.DEBUG, filename="debug.log", format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')tetapi tidak membantu.
GurhanCagin
26

Metode sys.excepthookini akan dipanggil jika pengecualian tidak tertangkap: http://docs.python.org/library/sys.html#sys.excepthook

Ketika eksepsi dimunculkan dan tidak tertangkap, interpreter memanggil sys.excepthook dengan tiga argumen, kelas eksepsi, instance eksepsi, dan objek traceback. Dalam sesi interaktif ini terjadi sesaat sebelum kontrol dikembalikan ke prompt; dalam program Python ini terjadi tepat sebelum program keluar. Penanganan pengecualian tingkat atas tersebut dapat dikustomisasi dengan menetapkan fungsi tiga argumen lainnya ke sys.excepthook.

Ned Batchelder
sumber
2
Mengapa mengirim kelas pengecualian? Tidak bisakah kau selalu mendapatkannya dengan memanggil typeinstance?
Neil G
Apa jenis parameternya sys.excepthook?
Martin Thoma
23

Kenapa tidak:

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

Berikut adalah output dengan sys.excepthookseperti yang terlihat di atas:

$ 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

Ini adalah output dengan sys.excepthookkomentar:

$ python tb.py
Traceback (most recent call last):
  File "tb.py", line 11, in <module>
    None()
TypeError: 'NoneType' object is not callable

Satu-satunya perbedaan adalah bahwa yang pertama ada ERROR:root:Unhandled exception:di awal baris pertama.

Tiago Coutinho
sumber
Perbedaan lain adalah bahwa yang pertama menulis jejak ke sistem logging, sehingga penangan dan pemformat yang telah Anda instal diterapkan. Yang terakhir menulis langsung ke sys.stderr.
radiaph
8

Untuk membangun jawaban Jacinda, tetapi menggunakan objek 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
sumber
2
Akan lebih baik menggunakan functools.partial()bukan lambda. Lihat: docs.python.org/2/library/functools.html#functools.partial
Mariusz Jamro
@MariuszJamro mengapa?
davr
4

Bungkus panggilan entri aplikasi Anda dalam satu try...exceptblok sehingga Anda dapat menangkap dan mencatat (dan mungkin menaikkan kembali) semua pengecualian yang tidak tertangkap. Misalnya bukannya:

if __name__ == '__main__':
    main()

Melakukan hal ini:

if __name__ == '__main__':
    try:
        main()
    except Exception as e:
        logger.exception(e)
        raise
flaviovs
sumber
Ini bukan pertanyaan yang diajukan. Tujuan dari pertanyaan ini adalah untuk bertanya apa yang harus dilakukan ketika pengecualian TIDAK ditangani oleh kode.
Mayank Jaiswal
1
Nah, Python adalah bahasa pemrograman, dan ini menyiratkan bahwa ia tidak melakukan hal-hal "secara otomatis" (seperti yang diinginkan OP), kecuali jika dan ketika Anda memintanya untuk melakukannya. Dengan kata lain, tidak ada cara untuk "secara otomatis" mencatat semua pengecualian, kecuali Anda kode untuk itu - dan itulah yang ada di jawaban saya.
flaviovs
1
Nah, jika Anda melihat jawaban Ned Batchelder, ada sesuatu yang disebut hook pengecualian. Anda harus mendefinisikan di satu tempat dalam kode Anda dan semua pengecualian yang tidak tertangkap ditangani.
Mayank Jaiswal
1
Pengait pengecualian tidak mengubah fakta bahwa itu bukan "otomatis" (dalam arti OP ingin) - dengan kata lain, Anda masih harus kode itu. Jawaban Ned (yang menggunakan kait pengecualian) benar-benar menjawab pertanyaan awal - hanya saja, menurut pendapat saya , cara melakukannya jauh lebih sedikit pythonic daripada milik saya.
flaviovs
1
Cukup banyak tergantung pada tujuan Anda sendiri. Jika Anda memprogram untuk menyenangkan IDE, maka ya menangkap semua pengecualian mungkin bukan pilihan. Tetapi jika Anda ingin menangani kesalahan dengan anggun, dan menampilkan umpan balik yang bagus kepada pengguna, maka saya khawatir Anda perlu menangkap semua pengecualian. Ok, cukup sarkasme :-) - jika Anda perhatikan dengan teliti Anda akan melihat bahwa kode tersebut mencegat pengecualian, tetapi naikkan kembali, jadi kecuali jika IDE Anda melakukan sesuatu yang "ajaib" itu tidak boleh dilakukan, itu masih akan mendapatkan pengecualian.
flaviovs
3

Mungkin Anda bisa melakukan sesuatu di bagian atas modul yang mengarahkan ulang stderr ke file, dan kemudian login file itu di bagian bawah

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() )
JiminyCricket
sumber
3

Meskipun jawaban @ gnu_lorien memberi saya titik awal yang baik, program saya mogok pada pengecualian pertama.

Saya datang dengan solusi yang disesuaikan (dan / atau) yang disempurnakan, yang secara diam-diam mencatat Pengecualian fungsi yang didekorasi @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
sumber
2

Untuk menjawab pertanyaan dari Mr.Zeus yang dibahas di bagian komentar dari jawaban yang diterima, saya menggunakan ini untuk mencatat pengecualian yang tidak tertangkap di konsol interaktif (diuji dengan PyCharm 2018-2019). Saya menemukan sys.excepthooktidak bekerja di shell python jadi saya melihat lebih dalam dan menemukan bahwa saya bisa menggunakannya sys.exc_info. Namun, sys.exc_infotidak mengambil argumen seperti sys.excepthookitu membutuhkan 3 argumen.

Di sini, saya menggunakan keduanya sys.excepthookdan sys.exc_infountuk mencatat pengecualian di konsol interaktif dan skrip dengan fungsi wrapper. Untuk melampirkan fungsi kait ke kedua fungsi, saya memiliki dua antarmuka yang berbeda tergantung apakah argumen diberikan atau tidak.

Berikut kodenya:

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)

Setup logging dapat ditemukan di jawaban gnu_lorien.

Nabs
sumber
2

Dalam kasus saya (menggunakan python 3) ketika menggunakan jawaban @ Jacinda konten traceback tidak dicetak. Sebaliknya, itu hanya mencetak obyek itu sendiri: <traceback object at 0x7f90299b7b90>.

Sebaliknya saya lakukan:

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