プログラムを停止せずに完全なトレースバックを印刷する方法は?


779

10個のWebサイトを解析し、データファイルを特定し、ファイルを保存し、解析して、NumPyライブラリですぐに使用できるデータを作成するプログラムを作成しています。ありますトン、このファイルの出会い私は分類するために、まだまし悪いリンク、不完全に形成されXML、行方不明のエントリ、および他のものによるエラーのは。私は最初にこのようなエラーを処理するためにこのプログラムを作りました:

try:
    do_stuff()
except:
    pass

しかし今、私はエラーをログに記録したいと思います:

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

これは、後で確認するためにログファイルに出力していることに注意してください。これは通常、非常に役に立たないデータを印刷します。私が欲しいのは、try-exceptが例外をインターセプトせずにエラーがトリガーされたときに出力されるのとまったく同じ行を出力することですが、プログラムが一連のforループにネストされているため、プログラムを停止させたくありません完了するまで参照してください。

回答:


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

元のトレースバックに本当にアクセスする必要がある場合の解決策の1つは、ローカル変数に返された例外情報をキャッシュし、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

    例外を処理している関数のローカル変数にトレースバックの戻り値を割り当てると、循環参照が発生します。これにより、同じ関数内のローカル変数またはトレースバックによって参照されるものはガベージコレクションされなくなります。[...] トレースバックが必要な場合は、使用後に必ず削除してください(try ... finallyステートメントで行うのが最適です)

  • しかし、同じドキュメントから:

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


258

デバッグしていて、現在のスタックトレースを見たいだけの場合は、次のように呼び出すだけです。

traceback.print_stack()

再びキャッチするためだけに手動で例外を発生させる必要はありません。


9
トレースバックモジュールはまさにそれを行います-例外を発生させてキャッチします。
pppery 2015年

3
デフォルトでは、出力はSTDERRに送られます。別の場所にリダイレクトされていたため、ログに表示されませんでした。
mpen 2018

101

プログラムを停止せずに完全なトレースバックを印刷する方法は?

エラーでプログラムを停止したくない場合は、try / exceptでそのエラーを処理する必要があります。

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!

結論

3つのオプションすべてについて、エラーが発生した場合と同じ出力が得られます。

>>> 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()最後の呼び出しのみを返します。スタックのいくつかのレベル(およびおそらくすべてのレベルs)を返すにはどうすれば成功しますか
herve-guerin

@geekobiここで何を求めているのかわかりません。プログラム/インタープリターのエントリー・ポイントまでトレースバックできることを示しています。何を明確にしていないのですか?
アーロンホール

1
@geekobiが言っているのは、キャッチしてリレイズした場合、traceback.print_exc()は元のスタックではなくリレイズスタックを返すだけです。
fizloki

@fizloki「リレイズ」はいかがですか?ベアraiseチェーンまたは例外チェーンを行っていますか、それとも元のトレースバックを隠していますか?stackoverflow.com/questions/2052390/…を
アーロンホール

21

まず、printロギングにsを使用しないでください。これを行うには、安定しており、実績のある十分に検討されたstdlibモジュールがありますlogging。あなたは間違いなく代わりにそれ使うべきです。

第2に、ネイティブでシンプルなアプローチがある場合に、無関係なツールで混乱を招かないようにしてください。ここにあります:

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ロガーを使用したり呼び出したりしてはならないexc_info=Trueたり、手を汚したりしsys.exc_infoですか?

まあ、それだけ!それらはすべて異なる目的で存在します。例えば、traceback.print_excの出力は、インタプリタ自体が生成するトレースバックとは少し異なります。これを使用すると、ログを読む人を混乱させ、彼らは頭をぶつけてしまいます。

渡すexc_info=True呼び出しをログに記録することだけでは不適切です。ただし、回復可能なエラーをキャッチし、それらINFOをトレースバックで(たとえば、レベルを使用して)ログに記録する場合にも役立ちます。log.exception 1つのレベルのログしか生成しないです- ERROR

そしてsys.exc_info、あなたはできる限り手をいじることを避けなければなりません。これは単にパブリックインターフェイスではなく、内部インターフェイスです。自分が何をしているのかが明確にわかっている場合は、それ使用できます。例外の印刷だけを目的としたものではありません。


4
また、そのままでは機能しません。それではない。私はまだ終わっていません。この答えは時間を浪費するだけです。
A. Rager

また、あなたができることも追加しますlogging.exception()。特別な要件がない限り、ログのインスタンスを作成する必要はありません。
Shital Shah、

9

@Aaron Hallの回答に加えて、ロギングしているがlogging.exception()(ERRORレベルでログを記録するため)使用したくない場合は、より低いレベルを使用してを渡すことができます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)

KeyboardInterruptCtrl-Cを使用してプログラムを強制終了できるように、s をキャッチして再レイズすることをお勧めします。ログは問題の範囲外ですが、適切なオプションはログです。sysおよびtracebackモジュールのドキュメント。


4
これはPython 3では機能せず、に変更する必要がありますdesired_trace = traceback.format_exc()sys.exc_info()引数として渡すことは決して正しいことではありませんが、Python 2では無視されますが、Python 3(とにかく3.6.4)では無視されます。
マルティノー

2
KeyboardInterruptから(直接的または間接的に)は派生しませんException。(どちらもから派生しBaseExceptionます。)これは、をexcept Exception:キャッチしないことを意味するKeyboardInterruptため、except KeyboardInterrupt: raiseは完全に不要です。
AJNeufeld

traceback.format_exc(sys.exc_info())私のためにpython 3.6.10で動作しない
ナムG VU

6

エラーが発生する可能性のある最も内側のループ内にtry / exceptを配置する必要があります。

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

... 等々

つまり、try / exceptで失敗する可能性のあるステートメントを、できるだけ内側のループでラップする必要があります。


6

発言この回答のコメントは:print(traceback.format_exc())よりも私のためのより良い仕事をしていませんtraceback.print_exc()。後者ではhello、両方がstdoutまたはstderrに同時に書き込み、奇妙な出力を生成する場合(少なくともテキストエディター内からビルドして出力を「ビルド結果」パネル)。

トレースバック(最新の呼び出しは最後):
ファイル "C:\ Users \ User \ Desktop \ test.py"、7行目、
hell 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

これが他の回答で言及されているとは思いません。何らかの理由でExceptionオブジェクトを渡している場合...

Python 3.5以降では、traceback.TracebackException.from_exception()を使用して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')

これはスタックの2つのレベルにすぎ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

4

例外オブジェクトから完全なトレースバックを文字列として取得します traceback.format_exception

例外オブジェクトしかない場合は、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

Errorオブジェクトが既にあり、全体を印刷したい場合は、次のように少し厄介な呼び出しを行う必要があります。

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

そうです、3つの位置引数をprint_exception取ります。例外のタイプ、実際の例外オブジェクト、および例外自体の内部トレースバックプロパティです。

Python 3.5以降では、type(err)はオプションですが、これは位置引数なので、その代わりに明示的にNoneを渡す必要があります。

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

なぜこれだけではないのかわかりませんtraceback.print_exception(err)。エラーとそのエラーに属するトレースバック以外のトレースバックを一緒に出力する理由は、私を超えています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.