จะพิมพ์การติดตามย้อนกลับแบบเต็มโดยไม่หยุดโปรแกรมได้อย่างไร


779

ฉันกำลังเขียนโปรแกรมที่แยกวิเคราะห์ 10 เว็บไซต์หาไฟล์ข้อมูลบันทึกไฟล์แล้วแยกวิเคราะห์เพื่อสร้างข้อมูลที่สามารถใช้งานได้ง่ายในไลบรารี NumPy มีตันของข้อผิดพลาดนี้เผชิญหน้าไฟล์ผ่านการเชื่อมโยงที่ไม่ดีที่เกิดขึ้นได้ไม่ดี XML รายการหายไปและสิ่งอื่น ๆ ฉันยังจะจัดหมวดหมู่ ฉันเริ่มทำโปรแกรมนี้เพื่อจัดการข้อผิดพลาดเช่นนี้:

try:
    do_stuff()
except:
    pass

แต่ตอนนี้ฉันต้องการบันทึกข้อผิดพลาด:

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

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

คำตอบ:


583

คำตอบอื่น ๆ ได้ชี้ให้เห็นโมดูลการย้อนกลับ

โปรดสังเกตว่าprint_excในบางกรณีคุณจะไม่ได้รับสิ่งที่คาดหวัง ใน Python 2.x:

import traceback

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

    traceback.print_exc()

... จะแสดงการย้อนกลับของข้อยกเว้นล่าสุด :

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

หากคุณต้องการเข้าถึงtracebackดั้งเดิมวิธีหนึ่งคือการแคชinfos ข้อยกเว้นที่ส่งคืนจากexc_infoในตัวแปรโลคัลและแสดงโดยใช้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

การผลิต:

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

ข้อผิดพลาดเล็กน้อยกับสิ่งนี้แม้ว่า:

  • จากเอกสารของsys_info:

    การกำหนดค่า traceback ผลตอบแทนให้กับตัวแปรท้องถิ่นในการทำงานที่มีการจัดการข้อยกเว้นจะทำให้เกิดการอ้างอิงแบบวงกลม สิ่งนี้จะป้องกันสิ่งที่อ้างอิงโดยตัวแปรโลคัลในฟังก์ชันเดียวกันหรือโดยการย้อนกลับจากการรวบรวมขยะ [... ] หากคุณต้องการการสืบค้นกลับตรวจสอบให้แน่ใจว่าได้ลบมันหลังการใช้งาน (ทำได้ดีที่สุดกับลอง ... ในที่สุดก็งบ)

  • แต่จากเอกสารเดียวกัน:

    เริ่มต้นด้วย Python 2.2 รอบดังกล่าวจะถูกเรียกคืนโดยอัตโนมัติเมื่อเปิดใช้งานการรวบรวมขยะและไม่สามารถเข้าถึงได้ แต่จะมีประสิทธิภาพมากขึ้นเพื่อหลีกเลี่ยงการสร้างรอบ


ในอีกทางหนึ่งโดยอนุญาตให้คุณเข้าถึงการสืบค้นกลับที่เกี่ยวข้องกับข้อยกเว้น Python 3 ให้ผลลัพธ์ที่น่าแปลกใจน้อยลง:

import traceback

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

    traceback.print_tb(err.__traceback__)

... จะแสดง:

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

709

traceback.format_exc()หรือsys.exc_info()จะให้ข้อมูลเพิ่มเติมถ้านั่นคือสิ่งที่คุณต้องการ

import traceback
import sys

try:
    do_stuff()
except Exception:
    print(traceback.format_exc())
    # or
    print(sys.exc_info()[2])

1
print(sys.exc_info()[0]<class 'Exception'>พิมพ์
weberc2

2
อย่าใช้ exc ... การสืบค้นกลับมีข้อมูลทั้งหมดstackoverflow.com/questions/4564559/…
qrtLs

258

หากคุณกำลังดีบั๊กและต้องการดูการติดตามสแต็กปัจจุบันคุณสามารถโทร:

traceback.print_stack()

ไม่จำเป็นต้องเพิ่มข้อยกเว้นด้วยตนเองเพียงเพื่อตรวจจับอีกครั้ง


9
โมดูล traceback ทำเช่นนั้นอย่างแน่นอน - ยกระดับและตรวจสอบข้อยกเว้น
pppery

3
เอาต์พุตไปที่ STDERR โดยค่าเริ่มต้น BTW ไม่ปรากฏในบันทึกของฉันเพราะมันถูกเปลี่ยนเส้นทางที่อื่น
mpen

101

จะพิมพ์การติดตามย้อนกลับแบบเต็มโดยไม่หยุดโปรแกรมได้อย่างไร

เมื่อคุณไม่ต้องการหยุดโปรแกรมของคุณเนื่องจากข้อผิดพลาดคุณต้องจัดการกับข้อผิดพลาดนั้นด้วยการลอง / ยกเว้น:

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

ในการแยกการtracebackสืบค้นกลับแบบเต็มเราจะใช้โมดูลจากไลบรารีมาตรฐาน:

import traceback

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

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

def do_something_that_might_error():
    raise_error()

การพิมพ์

หากต้องการพิมพ์การติดตามย้อนกลับแบบเต็มให้ใช้traceback.print_excวิธีการ:

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

สิ่งที่พิมพ์:

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!

ดีกว่าการพิมพ์บันทึก:

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

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

ในกรณีนี้คุณจะต้องการใช้logger.exceptionฟังก์ชันแทน:

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!

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

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

บันทึกใด:

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!

ข้อสรุป

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

>>> 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
เป็นที่กล่าวข้างต้นและสำหรับฉันมากเกินไปtraceback.print_exc()ผลตอบแทนเพียงโทรล่าสุด: ทำอย่างไรให้คุณประสบความสำเร็จที่จะกลับมาหลายระดับของสแต็ค (และอาจ levele ทั้งหมด s?)
Herve-Guerin

@geekobi ฉันไม่แน่ใจว่าสิ่งที่คุณถามที่นี่ ฉันแสดงให้เห็นว่าเราได้รับการสืบค้นกลับถึงจุดเริ่มต้นของโปรแกรม / ล่าม คุณยังไม่ชัดเจนในเรื่องอะไร?
Aaron Hall

1
สิ่งที่ @geekobi กำลังพูดคือถ้าคุณจับและเพิ่มอีกครั้ง traceback.print_exc () จะส่งคืนสแต็กเพิ่มอีกครั้งไม่ใช่สแต็กดั้งเดิม
fizloki

@fizloki คุณเป็นอย่างไร "reraising" คุณกำลังทำสายเปลือยraiseหรือข้อยกเว้นหรือคุณซ่อน traceback ดั้งเดิมหรือไม่ ดูstackoverflow.com/questions/2052390/…
Aaron Hall

21

ครั้งแรกไม่ได้ใช้printสำหรับการเข้าสู่ระบบมี astable พิสูจน์อย่างดีและคิดออกโมดูล STDLIB loggingที่จะทำว่า: คุณควรใช้มันแทน

ประการที่สองอย่าล่อลวงให้ยุ่งเหยิงด้วยเครื่องมือที่ไม่เกี่ยวข้องเมื่อมีวิธีการดั้งเดิมและเรียบง่าย นี่มันคือ:

log = logging.getLogger(__name__)

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

แค่นั้นแหละ. คุณทำเสร็จแล้ว

คำอธิบายสำหรับทุกคนที่สนใจในสิ่งที่ทำงานภายใต้ประทุน

สิ่งที่log.exceptionกำลังทำจริง ๆ คือการเรียกไปยังlog.error(นั่นคือบันทึกเหตุการณ์ที่มีระดับERROR) แล้วพิมพ์การติดตามกลับแล้ว

ทำไมจะดีกว่า

นี่คือข้อควรพิจารณาบางประการ:

  • มันถูกต้องแล้ว
  • มันตรงไปตรงมา;
  • มันง่าย

ทำไมไม่มีใครควรใช้tracebackหรือโทร logger ด้วยexc_info=Trueหรือทำให้มือสกปรกด้วยsys.exc_info?

ก็เพราะ! พวกเขาทั้งหมดมีอยู่เพื่อวัตถุประสงค์ที่แตกต่างกัน ตัวอย่างเช่นtraceback.print_excเอาต์พุตของมันแตกต่างจากการย้อนกลับเล็กน้อยที่ผลิตโดยล่ามเอง หากคุณใช้มันคุณจะสับสนทุกคนที่อ่านบันทึกของคุณพวกเขาจะต่อสู้กับพวกเขา

การส่งผ่านexc_info=Trueสายเข้าสู่ระบบนั้นไม่เหมาะสม แต่มันจะเป็นประโยชน์เมื่อจับข้อผิดพลาดจะได้รับคืนและคุณต้องการเข้าสู่ระบบพวกเขา (ใช้เช่นINFOระดับ) กับ tracebacks เป็นอย่างดีเพราะlog.exceptionผลิตท่อนเพียงระดับหนึ่ง ERROR-

และคุณควรหลีกเลี่ยงการยุ่งกับsys.exc_infoสิ่งที่คุณทำได้ นี่ไม่ใช่อินเทอร์เฟซสาธารณะ แต่เป็นอินเทอร์เฟซภายในคุณสามารถใช้งานได้หากคุณรู้ว่ากำลังทำอะไรอยู่ มันไม่ได้มีไว้สำหรับพิมพ์ข้อยกเว้น


4
มันยังไม่ทำงานตามที่เป็นอยู่ แค่นั้นแหละ ฉันไม่ได้ทำตอนนี้: คำตอบนี้เสียเวลา
A. Rager

logging.exception()ฉันยังจะเพิ่มที่คุณก็สามารถทำได้ ไม่จำเป็นต้องสร้างอินสแตนซ์ของบันทึกเว้นแต่คุณจะมีข้อกำหนดพิเศษ
Shital Shah

9

นอกเหนือจากคำตอบของ @Aaron Hall หากคุณกำลังบันทึก แต่ไม่ต้องการใช้logging.exception()(เนื่องจากบันทึกในระดับข้อผิดพลาด) คุณสามารถใช้ระดับที่ต่ำกว่าและผ่านexc_info=Trueได้ เช่น

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

7

ในการรับการติดตามสแต็กที่แม่นยำเช่นเดียวกับสตริงที่จะเพิ่มขึ้นหากไม่มีการลอง / ยกเว้นเพื่อก้าวข้ามมันให้วางสิ่งนี้ลงในบล็อกยกเว้นที่จับข้อยกเว้นที่ละเมิด

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

ต่อไปนี้เป็นวิธีใช้ (สมมติว่าflaky_funcมีการกำหนดและlogโทรระบบบันทึกที่คุณชื่นชอบ):

import traceback
import sys

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

เป็นความคิดที่ดีที่จะจับและเพิ่มอีกครั้งKeyboardInterruptเพื่อให้คุณยังคงสามารถฆ่าโปรแกรมโดยใช้ Ctrl-C เข้าสู่ระบบอยู่นอกขอบเขตของคำถาม แต่เป็นตัวเลือกที่ดีคือการเข้าสู่ระบบ เอกสารประกอบสำหรับsysและโมดูลการย้อนกลับ


4
นี้ไม่ได้ทำงานในหลาม 3 desired_trace = traceback.format_exc()และความต้องการที่จะเปลี่ยนไป การผ่านsys.exc_info()การโต้แย้งไม่ใช่สิ่งที่ถูกต้อง แต่จะไม่ได้รับความสนใจใน Python 2 แต่ไม่ใช่ใน Python 3 (3.6.4 อยู่ดี)
martineau

2
KeyboardInterruptไม่ได้มา (โดยตรงหรือโดยอ้อม) Exceptionจาก (ทั้งสองมาจากBaseException.) วิธีนี้except Exception:จะไม่เคยจับKeyboardInterruptและทำให้except KeyboardInterrupt: raiseไม่จำเป็นอย่างสมบูรณ์
AJNeufeld

traceback.format_exc(sys.exc_info())ไม่ทำงานกับฉันด้วย python 3.6.10
Nam G VU

6

คุณจะต้องลอง / ยกเว้นภายในวงในที่สุดที่ข้อผิดพลาดอาจเกิดขึ้นเช่น

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

... และต่อไป

กล่าวอีกนัยหนึ่งคุณจะต้องตัดคำสั่งที่อาจล้มเหลวในลอง / ยกเว้นเฉพาะที่เป็นไปได้ในวงภายในมากที่สุด


6

ข้อสังเกตเกี่ยวกับคำตอบนี้ 's ความคิดเห็น: ไม่ได้งานที่ดีกว่าสำหรับผมกว่าprint(traceback.format_exc()) traceback.print_exc()กับหลังhelloบางครั้งก็แปลก "ผสม" กับข้อความย้อนกลับเช่นถ้าทั้งสองต้องการที่จะเขียนไปยัง stdout หรือ stderr ในเวลาเดียวกันผลิตผลลัพธ์แปลก ๆ (อย่างน้อยเมื่อสร้างจากภายในตัวแก้ไขข้อความและดูผลลัพธ์ใน แผง "สร้างผลลัพธ์")

Traceback (การโทรล่าสุดครั้งล่าสุด):
ไฟล์ "C: \ Users \ User \ Desktop \ test.py", บรรทัดที่ 7, ใน
นรก do_stuff ()
ไฟล์ "C: \ Users \ User \ Desktop \ test.py", บรรทัด 4 ใน do_stuff
1/0
ZeroDivisionError: การหารจำนวนเต็มหรือโมดูโลตามศูนย์
o
[เสร็จสิ้นใน0.1 วินาที]

ดังนั้นฉันใช้:

import traceback, sys

def do_stuff():
    1/0

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

5

ฉันไม่เห็นสิ่งนี้พูดถึงในคำตอบอื่น ๆ หากคุณผ่านวัตถุยกเว้นด้วยเหตุผลใดก็ตาม ...

ในหลาม 3.5 ขึ้นไปคุณจะได้รับการติดตามจากวัตถุยกเว้นใช้traceback.TracebackException.from_exception () ตัวอย่างเช่น:

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

อย่างไรก็ตามโค้ดข้างต้นส่งผลให้:

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

นี่เป็นเพียงสองระดับของสแต็กซึ่งตรงข้ามกับสิ่งที่จะพิมพ์บนหน้าจอมีข้อยกเว้นยกขึ้นstack_lvl_2()และไม่ถูกดักจับ (ไม่ใส่เครื่องหมายใน# raiseบรรทัด)

ตามที่ฉันเข้าใจแล้วนั่นเป็นเพราะข้อยกเว้นจะบันทึกเฉพาะระดับปัจจุบันของสแต็กเมื่อเพิ่มขึ้นstack_lvl_3()ในกรณีนี้ __traceback__ในขณะที่มันจะถูกส่งกลับขึ้นผ่านสแต็คระดับมากขึ้นมีการเพิ่มของ แต่เราดักมันไว้ในstack_lvl_2()ความหมายทั้งหมดที่บันทึกได้คือระดับ 3 และ 2 เพื่อให้ได้ร่องรอยเต็มตามที่พิมพ์บน stdout เราจะต้องจับที่ระดับสูงสุด (ต่ำสุด):

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

ซึ่งผลลัพธ์ใน:

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

ขอให้สังเกตว่าการพิมพ์แบบสแต็กแตกต่างกันบรรทัดแรกและบรรทัดสุดท้ายหายไป เพราะมันเป็นสิ่งที่แตกต่างกันformat()

การดักจับข้อยกเว้นที่อยู่ไกลจากจุดที่ถูกยกขึ้นให้มากที่สุดจะทำให้โค้ดง่ายขึ้นในขณะเดียวกันก็ให้ข้อมูลเพิ่มเติม


นี่เป็นวิธีที่ดีกว่าวิธีก่อนหน้านี้มาก แต่ก็ยังซับซ้อนพอสมควรที่จะพิมพ์สแต็คเทรซ Java ใช้รหัสน้อยกว่า FGS
elhefe

3

คุณต้องการโมดูลการย้อนกลับ มันจะให้คุณพิมพ์กองขยะเหมือนที่งูเหลือมทำ โดยเฉพาะอย่างยิ่งฟังก์ชันprint_lastจะพิมพ์ข้อยกเว้นสุดท้ายและการติดตามสแต็ก


3

รับ traceback แบบเต็มเป็นสตริงจากอ็อบเจกต์ exception ด้วย traceback.format_exception

หากคุณมีวัตถุข้อยกเว้นเท่านั้นคุณสามารถรับ traceback เป็นสตริงจากจุดใดก็ได้ของรหัสใน Python 3 ด้วย:

import traceback

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

ตัวอย่างเต็มรูปแบบ:

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

เอาท์พุท:

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

เอกสารประกอบ: https://docs.python.org/3.7/library/traceback.html#traceback.format_exception

ดูเพิ่มเติม: แยกข้อมูลการติดตามกลับจากวัตถุข้อยกเว้น

ทดสอบใน Python 3.7.3


2

หากคุณมีวัตถุข้อผิดพลาดอยู่แล้วและคุณต้องการพิมพ์สิ่งทั้งหมดคุณต้องทำการโทรที่น่าอึดอัดใจเล็กน้อย:

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

ถูกprint_exceptionต้องใช้เวลาสามข้อโต้แย้งตำแหน่ง: ประเภทของข้อยกเว้นวัตถุข้อยกเว้นที่เกิดขึ้นจริงและคุณสมบัติการติดตามกลับภายในข้อยกเว้นของตัวเอง

ใน python 3.5 หรือใหม่กว่านั้นtype(err)เป็นตัวเลือก ... แต่เป็นอาร์กิวเมนต์ตำแหน่งดังนั้นคุณยังต้องผ่านไม่มีอย่างชัดเจนในที่ของมัน

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

traceback.print_exception(err)ผมมีความคิดว่าทำไมทั้งหมดนี้ไม่ได้เป็นเพียง เหตุใดคุณจึงต้องการพิมพ์ข้อผิดพลาดพร้อมกับการสืบค้นกลับนอกเหนือจากข้อผิดพลาดนั้นเป็นสิ่งที่อยู่นอกเหนือฉัน

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