Làm thế nào để in toàn bộ truy nguyên mà không tạm dừng chương trình?


779

Tôi đang viết một chương trình phân tích 10 trang web, định vị tệp dữ liệu, lưu tệp và sau đó phân tích cú pháp chúng để tạo dữ liệu có thể dễ dàng sử dụng trong thư viện NumPy. Có rất nhiều lỗi mà tập tin này gặp phải thông qua các liên kết xấu, XML kém, các mục bị thiếu và những thứ khác tôi chưa phân loại được. Tôi ban đầu thực hiện chương trình này để xử lý các lỗi như thế này:

try:
    do_stuff()
except:
    pass

Nhưng bây giờ tôi muốn đăng nhập lỗi:

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

Lưu ý đây là in ra một tệp nhật ký để xem lại sau. Điều này thường in dữ liệu rất vô dụng. Điều tôi muốn là in chính xác các dòng được in khi lỗi gây ra mà không thử ngoại trừ chặn ngoại lệ, nhưng tôi không muốn nó dừng chương trình của mình vì nó được lồng trong một loạt các vòng lặp mà tôi muốn xem để hoàn thành

Câu trả lời:


583

Một số câu trả lời khác đã chỉ ra mô-đun trac trở lại .

Xin lưu ý rằng print_exc, trong một số trường hợp góc, bạn sẽ không có được những gì bạn mong đợi. Trong Python 2.x:

import traceback

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

    traceback.print_exc()

... sẽ hiển thị truy nguyên của ngoại lệ cuối cùng :

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

Nếu bạn thực sự cần phải truy cập bản gốc traceback một giải pháp là bộ nhớ cache infos ngoại lệ như trở về từ exc_infotrong một biến địa phương và hiển thị nó bằng 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

Sản xuất:

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

Vài cạm bẫy với điều này mặc dù:

  • Từ tài liệu của sys_info:

    Việc gán giá trị trả về theo dõi cho biến cục bộ trong hàm đang xử lý ngoại lệ sẽ gây ra tham chiếu vòng tròn . Điều này sẽ ngăn bất kỳ thứ gì được tham chiếu bởi một biến cục bộ trong cùng chức năng hoặc bằng cách truy nguyên khỏi rác được thu thập. [...] Nếu bạn cần truy xuất lại, hãy đảm bảo xóa nó sau khi sử dụng (tốt nhất là thực hiện với câu lệnh thử ... cuối cùng)

  • nhưng, từ cùng một tài liệu:

    Bắt đầu với Python 2.2, các chu trình như vậy sẽ tự động được thu hồi khi bật tính năng thu gom rác và chúng không thể truy cập được, nhưng vẫn hiệu quả hơn để tránh tạo chu kỳ.


Mặt khác, bằng cách cho phép bạn truy cập vào truy nguyên liên quan đến một ngoại lệ, Python 3 tạo ra một kết quả ít ngạc nhiên hơn:

import traceback

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

    traceback.print_tb(err.__traceback__)

... sẽ hiển thị:

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


258

Nếu bạn đang gỡ lỗi và chỉ muốn xem theo dõi ngăn xếp hiện tại, bạn chỉ cần gọi:

traceback.print_stack()

Không cần phải tự đưa ra một ngoại lệ chỉ để bắt lại.


9
Mô-đun truy nguyên thực hiện chính xác điều đó - nâng cao và bắt một ngoại lệ.
pppery 7/11/2015

3
Đầu ra đi đến STDERR theo mặc định BTW. Không xuất hiện trong nhật ký của tôi vì nó đã được chuyển hướng ở một nơi khác.
mở

101

Làm thế nào để in toàn bộ truy nguyên mà không tạm dừng chương trình?

Khi bạn không muốn dừng chương trình của mình do lỗi, bạn cần xử lý lỗi đó bằng một lần thử / ngoại trừ:

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

Để trích xuất toàn bộ truy nguyên, chúng tôi sẽ sử dụng tracebackmô-đun từ thư viện chuẩn:

import traceback

Và để tạo một stacktrace phức tạp để chứng minh rằng chúng ta có được stacktrace đầy đủ:

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

def do_something_that_might_error():
    raise_error()

In ấn

Để in toàn bộ truy nguyên, sử dụng traceback.print_excphương pháp:

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

Bản in nào:

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!

Tốt hơn là in, ghi nhật ký:

Tuy nhiên, cách tốt nhất là cài đặt bộ ghi cho mô-đun của bạn. Nó sẽ biết tên của mô-đun và có thể thay đổi cấp độ (trong số các thuộc tính khác, chẳng hạn như trình xử lý)

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

Trong trường hợp đó, bạn sẽ muốn logger.exceptionchức năng thay thế:

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

Nhật ký nào:

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!

Hoặc có lẽ bạn chỉ muốn chuỗi, trong trường hợp đó, bạn sẽ muốn traceback.format_exchàm thay thế:

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

Nhật ký nào:

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!

Phần kết luận

Và đối với cả ba tùy chọn, chúng tôi thấy chúng tôi nhận được cùng một đầu ra như khi chúng tôi gặp lỗi:

>>> 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
như đã nói ở trên và đối với tôi cũng vậy, traceback.print_exc()chỉ trả về cuộc gọi cuối cùng: làm thế nào để bạn thành công để trả lại một số cấp độ của ngăn xếp (và có thể là tất cả các cấp độ?)
herve-guerin

@geekobi Tôi không chắc bạn đang hỏi gì ở đây. Tôi chứng minh rằng chúng tôi có được truy nguyên đến điểm vào của chương trình / thông dịch viên. Bạn không rõ ràng về cái gì?
Aaron Hall

1
Những gì @geekobi đang nói là nếu bạn bắt và nâng lại, tracBack.print_exc () sẽ chỉ trả lại ngăn xếp tăng lại, chứ không phải ngăn xếp ban đầu.
fizloki

@fizloki bạn "hồi sinh" thế nào? Bạn đang thực hiện một raisechuỗi trần hoặc ngoại lệ, hoặc bạn đang che giấu dấu vết ban đầu? xem stackoverflow.com/questions/2052390/ từ
Aaron Hall

21

Đầu tiên, không sử dụng prints để ghi nhật ký, có mô-đun stdlib đáng kinh ngạc, đã được chứng minh và được cân nhắc kỹ lưỡng để làm điều đó : logging. Bạn chắc chắn nên sử dụng nó thay thế.

Thứ hai, không nên làm lộn xộn với các công cụ không liên quan khi có cách tiếp cận đơn giản và tự nhiên. Đây là:

log = logging.getLogger(__name__)

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

Đó là nó. Bạn đã hoàn thành

Giải thích cho bất cứ ai quan tâm đến cách mọi thứ hoạt động dưới mui xe

Những gì log.exceptionthực sự đang làm chỉ là một cuộc gọi đến log.error(nghĩa là đăng nhập sự kiện với cấp độ ERROR) in dấu vết sau đó.

Tại sao nó tốt hơn?

Vâng, đây là một số cân nhắc:

  • nó vừa phải ;
  • nó là đơn giản;
  • nó đơn giản.

Tại sao không ai nên sử dụng tracebackhoặc gọi logger với exc_info=Truehoặc bị bẩn tay sys.exc_info?

Vâng, chỉ vì! Tất cả đều tồn tại cho các mục đích khác nhau. Ví dụ, traceback.print_excđầu ra của một chút khác biệt so với các tracebacks do chính trình thông dịch tạo ra. Nếu bạn sử dụng nó, bạn sẽ nhầm lẫn bất cứ ai đọc nhật ký của bạn, họ sẽ đập đầu vào chúng.

Chuyển qua exc_info=Truecác cuộc gọi đăng nhập là không phù hợp. Tuy nhiên , nó rất hữu ích khi bắt các lỗi có thể phục hồi và bạn muốn ghi lại chúng (sử dụng, ví dụ INFOmức độ) bằng tracebacks, vì log.exceptiontạo ra các bản ghi chỉ có một cấp độ - ERROR.

Và bạn chắc chắn nên tránh gây rối với sys.exc_infocàng nhiều càng tốt. Nó không chỉ là một giao diện công cộng, nó là một giao diện nội bộ - bạn có thể sử dụng nó nếu bạn chắc chắn biết những gì bạn đang làm. Nó không dành cho chỉ in ngoại lệ.


4
Nó cũng không hoạt động như vốn có. Không phải vậy đâu. Tôi chưa làm xong: câu trả lời này chỉ lãng phí thời gian.
A. Rager

Tôi cũng sẽ thêm rằng bạn chỉ có thể làm logging.exception(). Không cần tạo bản ghi nhật ký trừ khi bạn có yêu cầu đặc biệt.
Shital Shah

9

Ngoài câu trả lời của @Aaron Hall, nếu bạn đang đăng nhập, nhưng không muốn sử dụng logging.exception()(vì nó đăng nhập ở cấp độ LRI), bạn có thể sử dụng cấp độ thấp hơn và vượt qua exc_info=True. ví dụ

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

7

Để có được dấu vết ngăn xếp chính xác , như một chuỗi, sẽ được nâng lên nếu không có thử / ngoại trừ ở đó để bước qua nó, chỉ cần đặt chuỗi này vào khối ngoại trừ bắt ngoại lệ vi phạm.

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

Đây là cách sử dụng nó (giả sử flaky_funcđược xác định và loggọi hệ thống ghi nhật ký yêu thích của bạn):

import traceback
import sys

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

Đó là một ý tưởng tốt để bắt và nâng KeyboardInterruptcấp lại, để bạn vẫn có thể giết chương trình bằng Ctrl-C. Ghi nhật ký nằm ngoài phạm vi của câu hỏi, nhưng một lựa chọn tốt là đăng nhập . Tài liệu cho các mô-đun systrac trở lại .


4
Điều này không hoạt động trong Python 3 và cần phải thay đổi thành desired_trace = traceback.format_exc(). Chuyển qua sys.exc_info()làm đối số không bao giờ là điều chính xác để làm, nhưng bị âm thầm bỏ qua trong Python 2, nhưng không phải trong Python 3 (dù sao 3.6.4).
martineau

2
KeyboardInterruptkhông bắt nguồn (trực tiếp hoặc gián tiếp) từ Exception. (Cả hai đều có nguồn gốc từ BaseException.) Điều này có nghĩa là except Exception:sẽ không bao giờ bắt được a KeyboardInterrupt, và do đó, điều except KeyboardInterrupt: raisenày là hoàn toàn không cần thiết.
AJNeufeld

traceback.format_exc(sys.exc_info())không làm việc cho tôi với python 3.6.10
Nam G VU

6

Bạn sẽ cần đặt thử / ngoại trừ bên trong phần bên trong nhất, nơi có thể xảy ra lỗi, nghĩa là

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

... và như thế

Nói cách khác, bạn sẽ cần phải bọc các câu lệnh có thể thất bại trong thử / ngoại trừ càng cụ thể càng tốt, trong vòng lặp bên trong nhất có thể.


6

Một nhận xét về ý kiến ​​của câu trả lời này : print(traceback.format_exc())làm một công việc tốt hơn cho tôi hơn traceback.print_exc(). Với cái sau, hellođôi khi bị "trộn" một cách kỳ lạ với văn bản theo dõi, giống như nếu cả hai muốn viết vào thiết bị xuất chuẩn hoặc stderr cùng một lúc, tạo ra đầu ra kỳ lạ (ít nhất là khi xây dựng từ bên trong trình soạn thảo văn bản và xem đầu ra trong Bảng "Xây dựng kết quả").

TracBack (cuộc gọi gần đây nhất vừa qua):
Tệp "C: \ Users \ User \ Desktop \ test.py", dòng 7, trong
hell do_ ware ()
Tệp "C: \ Users \ User \ Desktop \ test.py", dòng 4 , trong do_ ware
1/0
ZeroDivisionError: chia số nguyên hoặc modulo bằng 0
o
[Hoàn thành trong 0,1 giây ]

Vì vậy, tôi sử dụng:

import traceback, sys

def do_stuff():
    1/0

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

5

Tôi không thấy điều này được đề cập trong bất kỳ câu trả lời nào khác. Nếu bạn đi xung quanh một đối tượng Ngoại lệ vì bất kỳ lý do gì ...

Trong Python 3.5+, bạn có thể nhận được một dấu vết từ một đối tượng Exception bằng cách sử dụng tracBack.TracBackException.from_exception () . Ví dụ:

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

Tuy nhiên, đoạn mã trên dẫn đến:

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

Đây chỉ là hai cấp độ của ngăn xếp, trái ngược với những gì đã được in trên màn hình có ngoại lệ được nêu ra stack_lvl_2()và không bị chặn (không chú ý đến # raisedòng).

Theo tôi hiểu, đó là bởi vì một ngoại lệ chỉ ghi lại mức hiện tại của ngăn xếp khi nó được nâng lên, stack_lvl_3()trong trường hợp này. Khi nó được chuyển trở lại qua ngăn xếp, nhiều cấp độ sẽ được thêm vào nó __traceback__. Nhưng chúng tôi đã chặn nó stack_lvl_2(), nghĩa là tất cả những gì nó phải ghi là cấp 3 và 2. Để có được dấu vết đầy đủ như được in trên thiết bị xuất chuẩn, chúng tôi phải bắt nó ở mức cao nhất (thấp nhất?):

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

Kết quả nào trong:

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

Lưu ý rằng bản in ngăn xếp là khác nhau, dòng đầu tiên và cuối cùng bị thiếu. Bởi vì nó khác biệtformat() .

Việc chặn ngoại lệ càng xa điểm mà nó được nêu ra càng tốt giúp cho mã đơn giản hơn đồng thời cung cấp thêm thông tin.


Điều này tốt hơn rất nhiều so với (các) phương pháp trước đó, nhưng vẫn bị chê bai một cách lố bịch chỉ để in ra một stacktrace. Java mất ít mã FGS.
elhefe

4

Nhận toàn bộ truy nguyên dưới dạng một chuỗi từ đối tượng ngoại lệ với traceback.format_exception

Nếu bạn chỉ có đối tượng ngoại lệ, bạn có thể lấy dấu vết dưới dạng một chuỗi từ bất kỳ điểm nào của mã trong Python 3 với:

import traceback

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

Ví dụ đầy đủ:

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

Đầu ra:

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

Tài liệu: https://docs.python.org/3.7/l Library / tracBack.html # tracBack.format_exception

Xem thêm: Trích xuất thông tin truy nguyên từ một đối tượng ngoại lệ

Đã thử nghiệm trong Python 3.7.3.


3

Bạn muốn mô-đun trac trở lại . Nó sẽ cho phép bạn in các ngăn xếp stack như Python thường làm. Cụ thể, hàm print_last sẽ in ngoại lệ cuối cùng và dấu vết ngăn xếp.


2

Nếu bạn đã có một đối tượng Lỗi và bạn muốn in toàn bộ, bạn cần thực hiện cuộc gọi hơi khó xử này:

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

Điều đó đúng, print_exceptionba đối số vị trí: Loại ngoại lệ, đối tượng ngoại lệ thực tế và thuộc tính truy nguyên nội bộ của ngoại lệ.

Trong python 3.5 trở lên, type(err)tùy chọn ... nhưng đó là một đối số theo vị trí, vì vậy bạn vẫn phải vượt qua Không có vị trí rõ ràng.

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

Tôi không biết tại sao tất cả những điều này không chỉ traceback.print_exception(err). Tại sao bạn lại muốn in ra một lỗi, cùng với một dấu vết khác với lỗi thuộc về lỗi đó, nằm ngoài tôi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.