รับคำอธิบายข้อยกเว้นและการติดตามสแต็กซึ่งทำให้เกิดข้อยกเว้นทั้งหมดเป็นสตริง


423

ฉันเห็นโพสต์มากมายเกี่ยวกับการติดตามสแต็กและข้อยกเว้นใน Python แต่ไม่พบสิ่งที่ฉันต้องการ

ฉันมีรหัส Python 2.7 จำนวนหนึ่งซึ่งอาจทำให้เกิดข้อยกเว้น ฉันต้องการที่จะจับมันและกำหนดให้กับสตริงคำอธิบายแบบเต็มและการติดตามสแต็กที่ทำให้เกิดข้อผิดพลาด (เพียงแค่ทั้งหมดที่เราใช้เพื่อดูบนคอนโซล) ฉันต้องการสตริงนี้เพื่อพิมพ์ไปยังกล่องข้อความใน GUI

บางสิ่งเช่นนี้

try:
    method_that_can_raise_an_exception(params)
except Exception as e:
    print_to_textbox(complete_exception_description(e))

ปัญหาคือฟังก์ชั่นcomplete_exception_descriptionคืออะไร?

คำตอบ:


615

ดูtracebackโมดูลโดยเฉพาะformat_exc()ฟังก์ชั่น ที่นี่

import traceback

try:
    raise ValueError
except ValueError:
    tb = traceback.format_exc()
else:
    tb = "No error"
finally:
    print tb

2
ใช้งานได้กับข้อผิดพลาดล่าสุดเท่านั้นหรือไม่ จะเกิดอะไรขึ้นถ้าคุณเริ่มส่งข้อผิดพลาดไปยังบิตของรหัสอื่น ๆ ฉันกำลังเขียนlog_error(err)ฟังก์ชั่น
AnnanFay

มันทำงานกับข้อผิดพลาดที่ถูกจับและจัดการ
kindall

74

มาสร้างสแต็คเทรซที่ซับซ้อนอย่างเหมาะสมเพื่อแสดงให้เห็นว่าเราได้สแต็คเต็มรูปแบบ:

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

def do_something_that_might_error():
    raise_error()

การบันทึกสแต็คเต็มรูปแบบ

วิธีปฏิบัติที่ดีที่สุดคือการตั้งค่าตัวบันทึกสำหรับโมดูลของคุณ มันจะรู้ชื่อของโมดูลและสามารถเปลี่ยนระดับได้ (ระหว่างคุณสมบัติอื่น ๆ เช่นตัวจัดการ)

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

และเราสามารถใช้ตัวบันทึกนี้เพื่อรับข้อผิดพลาด:

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

ไฟล์บันทึกใด:

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!

ดังนั้นเราจึงได้ผลลัพธ์เช่นเดียวกับเมื่อเรามีข้อผิดพลาด:

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

รับเพียงสตริง

หากคุณต้องการสตริงจริงๆให้ใช้traceback.format_excฟังก์ชันแทนเพื่อสาธิตการบันทึกสตริงที่นี่:

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

ไฟล์บันทึกใด:

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!

1
นี่เป็นวิธีที่ดีที่สุดเมื่อใช้ python 3 (เทียบกับเช่นคำตอบบางส่วนด้านล่าง) หรือไม่
Yunti

1
@Yunti ฉันเชื่อว่า API นี้สอดคล้องกับ Python 2 และ 3
Aaron Hall

การจัดรูปแบบของคำตอบนี้ถูกกล่าวถึงใน meta: meta.stackoverflow.com/questions/386477/...
Lundin

ฉันส่งการแก้ไขต่อไปนี้ แต่ไม่ได้เข้าสู่ระบบเพื่อแสดงเป็นไม่ระบุชื่อ: except Exception as e: logger.exception("<<clearly and distinctly describe what failed here>>", exc_info=e)
arntg

@arntg ฉันขอขอบคุณที่คุณพยายามช่วย แต่การแก้ไขนั้นเป็นการเปลี่ยนแปลงที่เป็นอันตราย โปรดใช้ความระมัดระวังมากขึ้นในอนาคตเพื่อทำความเข้าใจ API ที่คุณพยายามใช้อย่างสมบูรณ์ ในกรณีนี้exc_infoอาร์กิวเมนต์คาดว่าจะมี "ข้อยกเว้น tuple" ในขณะที่errorเป็นตัวอย่างของExceptionวัตถุ (หรือ subclass) และไม่มีความจำเป็นที่จะเปลี่ยนแปลงไปerror e
Aaron Hall

39

ด้วย Python 3 รหัสต่อไปนี้จะจัดรูปแบบExceptionวัตถุอย่างที่จะได้รับโดยใช้traceback.format_exc():

import traceback

try: 
    method_that_can_raise_an_exception(params)
except Exception as ex:
    print(''.join(traceback.format_exception(etype=type(ex), value=ex, tb=ex.__traceback__)))

ข้อได้เปรียบที่ว่ามีเพียงExceptionวัตถุที่ต้องการ (ขอบคุณ__traceback__คุณลักษณะที่บันทึกไว้) และสามารถส่งผ่านได้ง่ายขึ้นเป็นอาร์กิวเมนต์ไปยังฟังก์ชันอื่นสำหรับการประมวลผลเพิ่มเติม


1
มันจะดีกว่า sys.exc_info () ซึ่งไม่ดีสไตล์ OO และใช้ตัวแปรทั่วโลก
WeiChing 林煒清

วิธีนี้จะถามวิธีการดึงข้อมูลการสืบค้นกลับจากวัตถุยกเว้นตามที่คุณได้ทำไว้ที่นี่: stackoverflow.com/questions/11414894/…
Ciro Santilli 郝海东冠状病病六四事件法轮功

มีวิธี Python3 ที่ง่ายกว่าโดยไม่ต้องใช้.__traceback__และtypeดูstackoverflow.com/a/58764987/5717886
don_vanchos

34
>>> import sys
>>> import traceback
>>> try:
...   5 / 0
... except ZeroDivisionError as e:
...   type_, value_, traceback_ = sys.exc_info()
>>> traceback.format_tb(traceback_)
['  File "<stdin>", line 2, in <module>\n']
>>> value_
ZeroDivisionError('integer division or modulo by zero',)
>>> type_
<type 'exceptions.ZeroDivisionError'>
>>>
>>> 5 / 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero

คุณใช้sys.exc_info ()เพื่อรวบรวมข้อมูลและฟังก์ชั่นในtracebackโมดูลเพื่อจัดรูปแบบ นี่คือตัวอย่างของการฟอร์แมต

สตริงข้อยกเว้นทั้งหมดอยู่ที่:

>>> ex = traceback.format_exception(type_, value_, traceback_)
>>> ex
['Traceback (most recent call last):\n', '  File "<stdin>", line 2, in <module>\n', 'ZeroDivisionError: integer division or modulo by zero\n']

9

สำหรับผู้ที่ใช้Python-3

การใช้tracebackโมดูลและexception.__traceback__สามารถแยกสแต็กการติดตามดังนี้:

  • คว้าstack-trace ปัจจุบันโดยใช้traceback.extract_stack()
  • ลบองค์ประกอบสามรายการสุดท้าย (เนื่องจากเป็นรายการในสแต็กที่นำฉันไปยังฟังก์ชันการแก้ไขข้อบกพร่องของฉัน)
  • ต่อท้าย__traceback__จากวัตถุยกเว้นโดยใช้traceback.extract_tb()
  • จัดรูปแบบสิ่งทั้งหมดโดยใช้ traceback.format_list()
import traceback
def exception_to_string(excp):
   stack = traceback.extract_stack()[:-3] + traceback.extract_tb(excp.__traceback__)  # add limit=?? 
   pretty = traceback.format_list(stack)
   return ''.join(pretty) + '\n  {} {}'.format(excp.__class__,excp)

การสาธิตง่ายๆ:

def foo():
    try:
        something_invalid()
    except Exception as e:
        print(exception_to_string(e))

def bar():
    return foo()

เราได้รับผลลัพธ์ต่อไปนี้เมื่อเราเรียกว่าbar():

  File "./test.py", line 57, in <module>
    bar()
  File "./test.py", line 55, in bar
    return foo()
  File "./test.py", line 50, in foo
    something_invalid()

  <class 'NameError'> name 'something_invalid' is not defined

ดูเหมือนว่ารหัสที่ซับซ้อนอ่านไม่ได้ ในPython 3.5+มีวิธีที่ง่ายและสง่างามมากขึ้น: stackoverflow.com/a/58764987/5717886
don_vanchos

6

คุณอาจพิจารณาใช้โมดูล Python ในตัวcgitbเพื่อรับข้อมูลข้อยกเว้นที่จัดรูปแบบได้ดีรวมถึงค่าตัวแปรโลคัลบริบทโค้ดต้นฉบับพารามิเตอร์ฟังก์ชัน ฯลฯ

เช่นรหัสนี้ ...

import cgitb
cgitb.enable(format='text')

def func2(a, divisor):
    return a / divisor

def func1(a, b):
    c = b - 5
    return func2(a, c)

func1(1, 5)

เราได้ผลลัพธ์ข้อยกเว้นนี้ ...

ZeroDivisionError
Python 3.4.2: C:\tools\python\python.exe
Tue Sep 22 15:29:33 2015

A problem occurred in a Python script.  Here is the sequence of
function calls leading up to the error, in the order they occurred.

 c:\TEMP\cgittest2.py in <module>()
    7 def func1(a, b):
    8   c = b - 5
    9   return func2(a, c)
   10
   11 func1(1, 5)
func1 = <function func1>

 c:\TEMP\cgittest2.py in func1(a=1, b=5)
    7 def func1(a, b):
    8   c = b - 5
    9   return func2(a, c)
   10
   11 func1(1, 5)
global func2 = <function func2>
a = 1
c = 0

 c:\TEMP\cgittest2.py in func2(a=1, divisor=0)
    3
    4 def func2(a, divisor):
    5   return a / divisor
    6
    7 def func1(a, b):
a = 1
divisor = 0
ZeroDivisionError: division by zero
    __cause__ = None
    __class__ = <class 'ZeroDivisionError'>
    __context__ = None
    __delattr__ = <method-wrapper '__delattr__' of ZeroDivisionError object>
    __dict__ = {}
    __dir__ = <built-in method __dir__ of ZeroDivisionError object>
    __doc__ = 'Second argument to a division or modulo operation was zero.'
    __eq__ = <method-wrapper '__eq__' of ZeroDivisionError object>
    __format__ = <built-in method __format__ of ZeroDivisionError object>
    __ge__ = <method-wrapper '__ge__' of ZeroDivisionError object>
    __getattribute__ = <method-wrapper '__getattribute__' of ZeroDivisionError object>
    __gt__ = <method-wrapper '__gt__' of ZeroDivisionError object>
    __hash__ = <method-wrapper '__hash__' of ZeroDivisionError object>
    __init__ = <method-wrapper '__init__' of ZeroDivisionError object>
    __le__ = <method-wrapper '__le__' of ZeroDivisionError object>
    __lt__ = <method-wrapper '__lt__' of ZeroDivisionError object>
    __ne__ = <method-wrapper '__ne__' of ZeroDivisionError object>
    __new__ = <built-in method __new__ of type object>
    __reduce__ = <built-in method __reduce__ of ZeroDivisionError object>
    __reduce_ex__ = <built-in method __reduce_ex__ of ZeroDivisionError object>
    __repr__ = <method-wrapper '__repr__' of ZeroDivisionError object>
    __setattr__ = <method-wrapper '__setattr__' of ZeroDivisionError object>
    __setstate__ = <built-in method __setstate__ of ZeroDivisionError object>
    __sizeof__ = <built-in method __sizeof__ of ZeroDivisionError object>
    __str__ = <method-wrapper '__str__' of ZeroDivisionError object>
    __subclasshook__ = <built-in method __subclasshook__ of type object>
    __suppress_context__ = False
    __traceback__ = <traceback object>
    args = ('division by zero',)
    with_traceback = <built-in method with_traceback of ZeroDivisionError object>

The above is a description of an error in a Python program.  Here is
the original traceback:

Traceback (most recent call last):
  File "cgittest2.py", line 11, in <module>
    func1(1, 5)
  File "cgittest2.py", line 9, in func1
    return func2(a, c)
  File "cgittest2.py", line 5, in func2
    return a / divisor
ZeroDivisionError: division by zero

5

หากคุณต้องการได้รับข้อมูลเดียวกันเมื่อไม่มีข้อยกเว้นคุณสามารถทำสิ่งนี้ได้ ทำimport tracebackแล้ว:

try:
    ...
except Exception as e:
    print(traceback.print_tb(e.__traceback__))

ฉันใช้ Python 3.7


3

สำหรับPython 3.5+ :

ดังนั้นคุณสามารถรับ stacktrace จากข้อยกเว้นของคุณเป็นจากข้อยกเว้นอื่น ๆ ใช้traceback.TracebackExceptionสำหรับมัน (เพียงแทนที่exด้วยข้อยกเว้นของคุณ):

print("".join(traceback.TracebackException.from_exception(ex).format())

ตัวอย่างเพิ่มเติมและคุณสมบัติอื่น ๆ เพื่อทำสิ่งนี้:

import traceback

try:
    1/0
except Exception as ex:
    print("".join(traceback.TracebackException.from_exception(ex).format()) == traceback.format_exc() == "".join(traceback.format_exception(type(ex), ex, ex.__traceback__))) # This is True !!
    print("".join(traceback.TracebackException.from_exception(ex).format()))

ผลลัพธ์จะเป็นดังนี้:

True
Traceback (most recent call last):
  File "untidsfsdfsdftled.py", line 29, in <module>
    1/0
ZeroDivisionError: division by zero


1

หากเป้าหมายของคุณคือการทำให้เกิดข้อยกเว้นและข้อความสแต็คเทรซดูเหมือนว่าเมื่อ python พ่นข้อผิดพลาดการทำงานต่อไปนี้ใน python 2 + 3:

import sys, traceback


def format_stacktrace():
    parts = ["Traceback (most recent call last):\n"]
    parts.extend(traceback.format_stack(limit=25)[:-2])
    parts.extend(traceback.format_exception(*sys.exc_info())[1:])
    return "".join(parts)

# EXAMPLE BELOW...

def a():
    b()


def b():
    c()


def c():
    d()


def d():
    assert False, "Noooh don't do it."


print("THIS IS THE FORMATTED STRING")
print("============================\n")

try:
    a()
except:
    stacktrace = format_stacktrace()
    print(stacktrace)

print("THIS IS HOW PYTHON DOES IT")
print("==========================\n")
a()

มันทำงานได้โดยการลบformat_stacktrace()สายสุดท้ายจากสแต็กและเข้าร่วมส่วนที่เหลือ เมื่อเรียกใช้ตัวอย่างข้างต้นจะให้ผลลัพธ์ต่อไปนี้:

THIS IS THE FORMATTED STRING
============================

Traceback (most recent call last):
  File "test.py", line 31, in <module>
    a()
  File "test.py", line 12, in a
    b()
  File "test.py", line 16, in b
    c()
  File "test.py", line 20, in c
    d()
  File "test.py", line 24, in d
    assert False, "Noooh don't do it."
AssertionError: Noooh don't do it.

THIS IS HOW PYTHON DOES IT
==========================

Traceback (most recent call last):
  File "test.py", line 38, in <module>
    a()
  File "test.py", line 12, in a
    b()
  File "test.py", line 16, in b
    c()
  File "test.py", line 20, in c
    d()
  File "test.py", line 24, in d
    assert False, "Noooh don't do it."
AssertionError: Noooh don't do it.

0

ฉันกำหนดคลาสผู้ช่วยเหลือดังต่อไปนี้:

import traceback
class TracedExeptions(object):
    def __init__(self):
        pass
    def __enter__(self):
        pass

    def __exit__(self, etype, value, tb):
      if value :
        if not hasattr(value, 'traceString'):
          value.traceString = "\n".join(traceback.format_exception(etype, value, tb))
        return False
      return True

ซึ่งฉันสามารถใช้ในภายหลังเช่นนี้:

with TracedExeptions():
  #some-code-which-might-throw-any-exception

และในภายหลังสามารถบริโภคได้เช่นนี้

def log_err(ex):
  if hasattr(ex, 'traceString'):
    print("ERROR:{}".format(ex.traceString));
  else:
    print("ERROR:{}".format(ex));

(พื้นหลัง: ฉันรู้สึกหงุดหงิดเนื่องจากการใช้Promises ร่วมกับExceptions ซึ่งโชคไม่ดีที่ส่งข้อยกเว้นที่เกิดขึ้นในที่เดียวไปยังตัวจัดการ on_rejected ในที่อื่นและทำให้ยากที่จะได้รับการย้อนกลับจากตำแหน่งเดิม)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.