Bagaimana cara mencetak traceback penuh tanpa menghentikan program?


779

Saya sedang menulis program yang mem-parsing 10 situs web, mencari file data, menyimpan file, dan kemudian mem-parsingnya untuk membuat data yang dapat dengan mudah digunakan di perpustakaan NumPy. Ada banyak kesalahan yang ditemui file ini melalui tautan buruk, XML yang dibentuk dengan buruk, entri yang hilang, dan hal-hal lain yang belum saya kategorikan. Saya awalnya membuat program ini untuk menangani kesalahan seperti ini:

try:
    do_stuff()
except:
    pass

Tapi sekarang saya ingin mencatat kesalahan:

try:
    do_stuff()
except Exception, err:
    print Exception, err

Perhatikan ini dicetak ke file log untuk ditinjau nanti. Ini biasanya mencetak data yang sangat tidak berguna. Apa yang saya inginkan adalah mencetak baris yang sama persis yang dicetak ketika kesalahan muncul tanpa coba-kecuali mencegat pengecualian, tapi saya tidak ingin itu menghentikan program saya karena itu bersarang dalam serangkaian loop yang saya ingin lihat sampai selesai.

Jawaban:


583

Beberapa jawaban lain sudah menunjukkan modul traceback .

Harap perhatikan bahwa dengan print_exc, dalam beberapa kasus sudut, Anda tidak akan mendapatkan apa yang Anda harapkan. Dengan Python 2.x:

import traceback

try:
    raise TypeError("Oups!")
except Exception, err:
    try:
        raise TypeError("Again !?!")
    except:
        pass

    traceback.print_exc()

... akan menampilkan traceback dari pengecualian terakhir :

Traceback (most recent call last):
  File "e.py", line 7, in <module>
    raise TypeError("Again !?!")
TypeError: Again !?!

Jika Anda benar-benar perlu mengakses traceback asli, salah satu solusinya adalah dengan menyimpan informasi pengecualian yang dikembalikan dari exc_infovariabel lokal dan menampilkannya menggunakan print_exception:

import traceback
import sys

try:
    raise TypeError("Oups!")
except Exception, err:
    try:
        exc_info = sys.exc_info()

        # do you usefull stuff here
        # (potentially raising an exception)
        try:
            raise TypeError("Again !?!")
        except:
            pass
        # end of useful stuff


    finally:
        # Display the *original* exception
        traceback.print_exception(*exc_info)
        del exc_info

Memproduksi:

Traceback (most recent call last):
  File "t.py", line 6, in <module>
    raise TypeError("Oups!")
TypeError: Oups!

Beberapa perangkap dengan ini:

  • Dari dokumen sys_info:

    Menetapkan nilai balik traceback ke variabel lokal dalam fungsi yang menangani pengecualian akan menyebabkan referensi melingkar . Ini akan mencegah apa pun yang dirujuk oleh variabel lokal dalam fungsi yang sama atau dengan traceback dari pengumpulan sampah. [...] Jika Anda membutuhkan traceback, pastikan untuk menghapusnya setelah digunakan (sebaiknya dilakukan dengan mencoba ... akhirnya pernyataan)

  • tetapi, dari dokumen yang sama:

    Dimulai dengan Python 2.2, siklus seperti itu secara otomatis direklamasi ketika pengumpulan sampah diaktifkan dan mereka menjadi tidak terjangkau, tetapi tetap lebih efisien untuk menghindari membuat siklus.


Di sisi lain, dengan memungkinkan Anda mengakses traceback yang terkait dengan pengecualian, Python 3 menghasilkan hasil yang kurang mengejutkan:

import traceback

try:
    raise TypeError("Oups!")
except Exception as err:
    try:
        raise TypeError("Again !?!")
    except:
        pass

    traceback.print_tb(err.__traceback__)

... akan menampilkan:

  File "e3.py", line 4, in <module>
    raise TypeError("Oups!")


258

Jika Anda sedang debug dan hanya ingin melihat jejak stack saat ini, Anda dapat memanggil:

traceback.print_stack()

Tidak perlu mengajukan pengecualian secara manual hanya untuk menangkapnya lagi.


9
Modul traceback melakukan hal itu - menaikkan dan menangkap pengecualian.
pppery

3
Output pergi ke STDERR secara default BTW. Tidak muncul di log saya karena sedang dialihkan ke tempat lain.
mpen

101

Bagaimana cara mencetak traceback penuh tanpa menghentikan program?

Ketika Anda tidak ingin menghentikan program Anda karena kesalahan, Anda perlu menangani kesalahan itu dengan mencoba / kecuali:

try:
    do_something_that_might_error()
except Exception as error:
    handle_the_error(error)

Untuk mengekstrak traceback penuh, kami akan menggunakan tracebackmodul dari pustaka standar:

import traceback

Dan untuk membuat stacktrace yang cukup rumit untuk menunjukkan bahwa kita mendapatkan stacktrace lengkap:

def raise_error():
    raise RuntimeError('something bad happened!')

def do_something_that_might_error():
    raise_error()

Pencetakan

Untuk mencetak traceback penuh, gunakan traceback.print_excmetode ini:

try:
    do_something_that_might_error()
except Exception as error:
    traceback.print_exc()

Yang mencetak:

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

Lebih baik daripada mencetak, masuk:

Namun, praktik terbaik adalah membuat logger diatur untuk modul Anda. Ia akan mengetahui nama modul dan dapat mengubah level (di antara atribut lainnya, seperti penangan)

import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

Dalam hal ini, Anda akan menginginkan logger.exceptionfungsinya sebagai gantinya:

try:
    do_something_that_might_error()
except Exception as error:
    logger.exception(error)

Log mana:

ERROR:__main__:something bad happened!
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

Atau mungkin Anda hanya menginginkan string, dalam hal ini, Anda akan menginginkan traceback.format_excfungsinya sebagai gantinya:

try:
    do_something_that_might_error()
except Exception as error:
    logger.debug(traceback.format_exc())

Log mana:

DEBUG:__main__:Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

Kesimpulan

Dan untuk ketiga opsi ini, kami melihat kami mendapatkan output yang sama seperti ketika kami memiliki kesalahan:

>>> do_something_that_might_error()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

2
seperti yang dikatakan di atas dan bagi saya juga, traceback.print_exc()hanya mengembalikan panggilan terakhir: bagaimana Anda berhasil mengembalikan beberapa tingkat tumpukan (dan mungkin semua level?)
herve-guerin

@geekobi Saya tidak yakin apa yang Anda tanyakan di sini. Saya menunjukkan bahwa kita mendapatkan traceback ke titik masuk program / juru bahasa. Apa yang tidak jelas?
Aaron Hall

1
Apa yang dikatakan @geekobi adalah jika Anda menangkap dan menaikkan kembali, traceback.print_exc () hanya akan mengembalikan tumpukan peningkatan kembali, bukan tumpukan asli.
fizloki

@fizloki bagaimana kabarmu "reraising"? Apakah Anda melakukan raisechaining telanjang atau pengecualian, atau apakah Anda menyembunyikan traceback aslinya? lihat stackoverflow.com/questions/2052390/...
Aaron Hall

21

Pertama, jangan gunakan printkarena penebangan, ada astabil, terbukti dan dipikirkan modul stdlib untuk melakukannya: logging. Anda tentu harus menggunakannya sebagai gantinya.

Kedua, jangan tergoda untuk melakukan kekacauan dengan alat yang tidak terkait ketika ada pendekatan asli dan sederhana. Ini dia:

log = logging.getLogger(__name__)

try:
    call_code_that_fails()
except MyError:
    log.exception('Any extra info you want to see in your logs')

Itu dia. Anda selesai sekarang.

Penjelasan untuk siapa saja yang tertarik dengan cara kerja sesuatu di bawah tenda

Apa log.exceptionyang sebenarnya dilakukan hanyalah panggilan ke log.error(yaitu, log event dengan level ERROR) dan cetak traceback kemudian.

Mengapa ini lebih baik?

Nah, berikut beberapa pertimbangannya:

  • itu benar ;
  • itu mudah;
  • itu sederhana.

Mengapa tidak ada yang menggunakan tracebackatau memanggil logger dengan exc_info=Trueatau membuat tangan mereka kotorsys.exc_info ?

Ya, hanya karena! Mereka semua ada untuk tujuan yang berbeda. Sebagai contoh,traceback.print_exc output sedikit berbeda dari traceback yang diproduksi oleh interpreter itu sendiri. Jika Anda menggunakannya, Anda akan membingungkan siapa pun yang membaca log Anda, mereka akan membenturkan kepalanya ke mereka.

Melewati exc_info=Trueuntuk mencatat panggilan tidak tepat. Tapi , ini berguna ketika menangkap kesalahan yang dapat dipulihkan dan Anda ingin mencatatnya (menggunakan, misalnya INFOlevel) dengan traceback juga, karena log.exceptionmenghasilkan log hanya satu level -ERROR .

Dan Anda harus menghindari bermain-main sys.exc_infosebanyak mungkin. Ini bukan antarmuka publik, ini antarmuka internal - Anda dapat menggunakannya jika Anda benar-benar tahu apa yang Anda lakukan. Ini tidak dimaksudkan hanya untuk mencetak pengecualian.


4
Ini juga tidak berfungsi apa adanya. Bukan itu. Saya belum selesai sekarang: jawaban ini hanya membuang-buang waktu.
A. Rager

Saya juga ingin menambahkan bahwa Anda bisa melakukannya logging.exception(). Tidak perlu membuat instance log kecuali Anda memiliki persyaratan khusus.
Shital Shah

9

Selain jawaban @Aaron Hall, jika Anda masuk, tetapi tidak ingin menggunakan logging.exception()(karena log di tingkat ERROR), Anda dapat menggunakan tingkat yang lebih rendah dan lulus exc_info=True. misalnya

try:
    do_something_that_might_error()
except Exception:
    logger.info('General exception noted.', exc_info=True)

7

Untuk mendapatkan jejak stack yang tepat , sebagai string, yang akan dinaikkan jika tidak ada coba / kecuali ada di sana untuk melangkahi, cukup letakkan ini di blok kecuali yang menangkap pengecualian yang menyinggung.

desired_trace = traceback.format_exc(sys.exc_info())

Berikut cara menggunakannya (dengan asumsi flaky_funcdidefinisikan, dan logmemanggil sistem logging favorit Anda):

import traceback
import sys

try:
    flaky_func()
except KeyboardInterrupt:
    raise
except Exception:
    desired_trace = traceback.format_exc(sys.exc_info())
    log(desired_trace)

Ini ide yang bagus untuk menangkap dan menaikkan kembali KeyboardInterrupt, sehingga Anda masih bisa mematikan program menggunakan Ctrl-C. Logging berada di luar ruang lingkup pertanyaan, tetapi pilihan yang bagus adalah mencatat . Dokumentasi untuk sistem dan modul traceback .


4
Ini tidak berfungsi di Python 3 dan perlu diubah desired_trace = traceback.format_exc(). Melewati sys.exc_info()sebagai argumen tidak pernah merupakan hal yang benar untuk dilakukan, tetapi diabaikan dengan diam-diam dalam Python 2 — tetapi tidak dalam Python 3 (bagaimanapun, 3.6.4).
martineau

2
KeyboardInterrupttidak berasal (langsung atau tidak langsung) dari Exception. (Keduanya berasal dari BaseException.) Ini berarti except Exception:tidak akan pernah menangkap KeyboardInterrupt, dan dengan demikian except KeyboardInterrupt: raisesama sekali tidak perlu.
AJNeufeld

traceback.format_exc(sys.exc_info())tidak bekerja untuk saya dengan python 3.6.10
Nam G VU

6

Anda harus meletakkan try / kecuali di bagian paling dalam di mana kesalahan dapat terjadi, yaitu

for i in something:
    for j in somethingelse:
        for k in whatever:
            try:
                something_complex(i, j, k)
            except Exception, e:
                print e
        try:
            something_less_complex(i, j)
        except Exception, e:
            print e

... dan seterusnya

Dengan kata lain, Anda perlu membungkus pernyataan yang mungkin gagal dalam coba / kecuali sespesifik mungkin, dalam loop-dalam yang paling mungkin.


6

Sebuah komentar tentang komentar jawaban ini : print(traceback.format_exc())melakukan pekerjaan yang lebih baik untuk saya daripada traceback.print_exc(). Dengan yang terakhir, hellokadang-kadang anehnya "dicampur" dengan teks traceback, seperti jika keduanya ingin menulis ke stdout atau stderr pada saat yang sama, menghasilkan output yang aneh (setidaknya ketika membangun dari dalam editor teks dan melihat output di Panel "Bangun hasil").

Traceback (panggilan terakhir terakhir):
File "C: \ Users \ User \ Desktop \ test.py", baris 7,
neraka do_stuff ()
File "C: \ Users \ User \ Desktop \ test.py", baris 4 , di do_stuff
1/0
ZeroDivisionError: pembagian integer atau modulo oleh nol
o
[Selesai dalam 0,1s]

Jadi saya menggunakan:

import traceback, sys

def do_stuff():
    1/0

try:
    do_stuff()
except Exception:
    print(traceback.format_exc())
    print('hello')

5

Saya tidak melihat ini disebutkan dalam jawaban lain. Jika Anda melewati objek Pengecualian untuk alasan apa pun ...

Di Python 3.5+ Anda bisa mendapatkan jejak dari objek Exception menggunakan traceback.TracebackException.from_exception () . Sebagai contoh:

import traceback


def stack_lvl_3():
    raise Exception('a1', 'b2', 'c3')


def stack_lvl_2():
    try:
        stack_lvl_3()
    except Exception as e:
        # raise
        return e


def stack_lvl_1():
    e = stack_lvl_2()
    return e

e = stack_lvl_1()

tb1 = traceback.TracebackException.from_exception(e)
print(''.join(tb1.format()))

Namun, kode di atas menghasilkan:

Traceback (most recent call last):
  File "exc.py", line 10, in stack_lvl_2
    stack_lvl_3()
  File "exc.py", line 5, in stack_lvl_3
    raise Exception('a1', 'b2', 'c3')
Exception: ('a1', 'b2', 'c3')

Ini hanya dua tingkat tumpukan, yang bertentangan dengan apa yang akan dicetak di layar seandainya pengecualian dimunculkan stack_lvl_2()dan tidak disadap (hapus komentar# raise baris).

Seperti yang saya pahami, itu karena pengecualian hanya mencatat level stack saat ini ketika dinaikkan, stack_lvl_3()dalam kasus ini. Ketika itu diteruskan kembali melalui tumpukan, lebih banyak level yang ditambahkan ke dalamnya __traceback__. Tapi kami mencegatnya stack_lvl_2(), artinya yang harus dicatat hanyalah level 3 dan 2. Untuk mendapatkan jejak lengkap seperti yang dicetak di stdout, kami harus menangkapnya di level tertinggi (terendah?):

import traceback


def stack_lvl_3():
    raise Exception('a1', 'b2', 'c3')


def stack_lvl_2():
    stack_lvl_3()


def stack_lvl_1():
    stack_lvl_2()


try:
    stack_lvl_1()
except Exception as exc:
    tb = traceback.TracebackException.from_exception(exc)

print('Handled at stack lvl 0')
print(''.join(tb.stack.format()))

Yang mengakibatkan:

Handled at stack lvl 0
  File "exc.py", line 17, in <module>
    stack_lvl_1()
  File "exc.py", line 13, in stack_lvl_1
    stack_lvl_2()
  File "exc.py", line 9, in stack_lvl_2
    stack_lvl_3()
  File "exc.py", line 5, in stack_lvl_3
    raise Exception('a1', 'b2', 'c3')

Perhatikan bahwa cetakan tumpukan berbeda, baris pertama dan terakhir tidak ada. Karena itu berbedaformat() .

Mencegah pengecualian sejauh mungkin dari titik dimunculkannya kode yang lebih sederhana sembari memberikan lebih banyak informasi.


Ini jauh lebih baik daripada metode sebelumnya, tetapi masih berbelit-belit hanya untuk mencetak stacktrace. Java mengambil lebih sedikit kode FGS.
elhefe

4

Dapatkan traceback penuh sebagai string dari objek pengecualian traceback.format_exception

Jika Anda hanya memiliki objek pengecualian, Anda bisa mendapatkan traceback sebagai string dari setiap titik kode di Python 3 dengan:

import traceback

''.join(traceback.format_exception(None, exc_obj, exc_obj.__traceback__))

Contoh lengkap:

#!/usr/bin/env python3

import traceback

def f():
    g()

def g():
    raise Exception('asdf')

try:
    g()
except Exception as e:
    exc = e

tb_str = ''.join(traceback.format_exception(None, exc_obj, exc_obj.__traceback__))
print(tb_str)

Keluaran:

Traceback (most recent call last):
  File "./main.py", line 12, in <module>
    g()
  File "./main.py", line 9, in g
    raise Exception('asdf')
Exception: asdf

Dokumentasi: https://docs.python.org/3.7/library/traceback.html#traceback.format_exception

Lihat juga: Ekstrak info traceback dari objek pengecualian

Diuji dalam Python 3.7.3.


3

Anda ingin modul traceback . Ini akan membiarkan Anda mencetak tumpukan dump seperti yang biasa dilakukan Python. Secara khusus, fungsi print_last akan mencetak pengecualian terakhir dan jejak stack.


2

Jika Anda sudah memiliki objek Galat, dan Anda ingin mencetak semuanya, Anda perlu melakukan panggilan yang sedikit canggung ini:

import traceback
traceback.print_exception(type(err), err, err.__traceback__)

Itu benar, print_exceptionbutuh tiga argumen posisi: Jenis pengecualian, objek pengecualian aktual, dan properti traceback internal pengecualian itu sendiri.

Dalam python 3.5 atau yang lebih baru, type(err)ini opsional ... tapi itu adalah argumen posisi, jadi Anda masih harus secara eksplisit melewatkan None di tempatnya.

traceback.print_exception(None, err, err.__traceback__)

Saya tidak tahu mengapa semua ini tidak adil traceback.print_exception(err). Mengapa Anda ingin mencetak kesalahan, bersama dengan traceback selain dari kesalahan itu, ada di luar jangkauan saya.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.