web-dev-qa-db-ja.com

完全なトレースバックを取得

_func2_およびfunc関数の呼び出しを含む、次の場合に完全なトレースバックを取得するにはどうすればよいですか?

_import traceback

def func():
    try:
        raise Exception('Dummy')
    except:
        traceback.print_exc()

def func2():
    func()


func2()
_

これを実行すると、次のようになります。

_Traceback (most recent call last):
  File "test.py", line 5, in func
    raise Exception('Dummy')
Exception: Dummy
_

サードパーティのモジュールにtracebackオブジェクトを渡す必要があるため、traceback.format_stack()は私が望むものではありません。

私はこのケースに特に興味があります:

_import logging


def func():
    try:
        raise Exception('Dummy')
    except:
        logging.exception("Something awful happened!")


def func2():
    func()


func2()
_

この場合、私は得ています:

_ERROR:root:Something awful happened!
Traceback (most recent call last):
  File "test.py", line 9, in func
    raise Exception('Dummy')
Exception: Dummy
_
26
warvariuc

Mechmindが回答したように、スタックトレースは、例外が発生したサイトとtryブロックのサイトの間のフレームのみで構成されています。完全なスタックトレースが必要な場合は、運が悪いようです。

スタックエントリをトップレベルから現在のフレームに抽出することが明らかに可能であることを除いて、_traceback.extract_stack_はそれをうまく管理します。問題は、_traceback.extract_stack_によって取得される情報は、トレースバックオブジェクトを作成せずにスタックフレームを直接検査することから得られ、logging AP​​Iはトレースバック出力に影響を与えるためにトレースバックオブジェクトを必要とすることです。

幸い、loggingにはactualトレースバックオブジェクトは必要ありません。tracebackモジュールのフォーマットルーチンに渡すことができるオブジェクトが必要です。 tracebackもどちらでもかまいません。トレースバックの2つの属性、フレームと行番号のみを使用します。したがって、ダック型の偽トレースバックオブジェクトのリンクリストを作成し、それをトレースバックとして渡すことができるはずです。

_import sys

class FauxTb(object):
    def __init__(self, tb_frame, tb_lineno, tb_next):
        self.tb_frame = tb_frame
        self.tb_lineno = tb_lineno
        self.tb_next = tb_next

def current_stack(skip=0):
    try: 1/0
    except ZeroDivisionError:
        f = sys.exc_info()[2].tb_frame
    for i in xrange(skip + 2):
        f = f.f_back
    lst = []
    while f is not None:
        lst.append((f, f.f_lineno))
        f = f.f_back
    return lst

def extend_traceback(tb, stack):
    """Extend traceback with stack info."""
    head = tb
    for tb_frame, tb_lineno in stack:
        head = FauxTb(tb_frame, tb_lineno, head)
    return head

def full_exc_info():
    """Like sys.exc_info, but includes the full traceback."""
    t, v, tb = sys.exc_info()
    full_tb = extend_traceback(tb, current_stack(1))
    return t, v, full_tb
_

これらの関数を配置すると、コードに必要な変更は簡単になります。

_import logging

def func():
    try:
        raise Exception('Dummy')
    except:
        logging.error("Something awful happened!", exc_info=full_exc_info())

def func2():
    func()

func2()
_

...予想される出力を与えるには:

_ERROR:root:Something awful happened!
Traceback (most recent call last):
  File "a.py", line 52, in <module>
    func2()
  File "a.py", line 49, in func2
    func()
  File "a.py", line 43, in func
    raise Exception('Dummy')
Exception: Dummy
_

Faux-tracebackオブジェクトは、実際のスタックフレームへの参照が含まれているため、イントロスペクション(ローカル変数の表示またはpdb.post_mortem()への引数として)に完全に使用できることに注意してください。

33
user4815162342

スタックトレースは、例外が発生したときに収集されます。したがって、目的のスタックの上にトレースバックを出力する必要があります。

import traceback

def func():
    raise Exception('Dummy')

def func2():
    func()


try:
    func2()
except:
    traceback.print_exc()
3
mechmind

より完全なトレースバックを書き込むモジュールを作成しました

モジュールは here ドキュメントは docs です

(また、あなたはpypiからモジュールを取得することができます

Sudo pip install pd

例外をキャッチして鳴らすには、次のようにします。

import pd

try:
    <python code>
except BaseException:       
    pd.print_exception_ex( follow_objects = 1 )

スタックトレースは次のようになります。

Exception: got it

#1  def kuku2(self = {'a': 42, 'b': [1, 2, 3, 4]}, depth = 1) at      t  test_pd.py:29
Calls next frame at:
    raise Exception('got it') at: test_pd.py:29

#2  def kuku2(self = {'a': 42, 'b': [1, 2, 3, 4]}, depth = 2) at test_pd.py:28
Calls next frame at:
    self.kuku2( depth - 1 ) at: test_pd.py:28

#3  def kuku2(self = {'a': 42, 'b': [1, 2, 3, 4]}, depth = 3) at test_pd.py:28
Calls next frame at:
    self.kuku2( depth - 1 ) at: test_pd.py:28

#4  def kuku2(self = {'a': 42, 'b': [1, 2, 3, 4]}, depth = 4) at test_pd.py:28
Calls next frame at:
    self.kuku2( depth - 1 ) at: test_pd.py:28

#5  def kuku2(self = {'a': 42, 'b': [1, 2, 3, 4]}, depth = 5) at     test_pd.py:28
 Calls next frame at:
    self.kuku2( depth - 1 ) at: test_pd.py:28

#6  def kuku2(self = {'a': 42, 'b': [1, 2, 3, 4]}, depth = 6) at test_pd.py:28
Calls next frame at:
    self.kuku2( depth - 1 ) at: test_pd.py:28

#7  def main() at test_pd.py:44
Local variables:
n = {'a': 42, 'b': [1, 2, 3, 4]}
Calls next frame at:
    pd.print_exception_ex( follow_objects = 1 ) at: test_pd.py:44

follow_objects = 0は、オブジェクトの内容を出力しません(複雑なデータ構造では、follow_objectsには時間がかかる場合があります)。

1
MichaelMoser

これは@ user4815162342の回答に基づいていますが、もう少しミニマルです。

_import sys
import collections

FauxTb = collections.namedtuple("FauxTb", ["tb_frame", "tb_lineno", "tb_next"])

def full_exc_info():
    """Like sys.exc_info, but includes the full traceback."""
    t, v, tb = sys.exc_info()
    f = sys._getframe(2)
    while f is not None:
        tb = FauxTb(f, f.f_lineno, tb)
        f = f.f_back
    return t, v, tb
_

sys._getframe()の使用を必要とする代償として、ダミー例外のスローを回避します。スタックフレーム(_full_exc_info_と_full_exc_info_を呼び出す関数)になるため、例外がキャッチされたexcept句を使用していることを前提としています。発生コードを呼び出すため、元のトレースバックに既に含まれています)。

これにより、user4815162342の回答のコードと同じ出力が得られます。

書式のわずかな違いを気にしない場合は、

_import logging

def func():
    try:
        raise Exception('Dummy')
    except:
        logging.exception("Something awful happened!", stack_info=True)

def func2():
    func()

func2()
_

その結果

_ERROR:root:Something awful happened!
Traceback (most recent call last):
  File "test.py", line 5, in func
    raise Exception('Dummy')
Exception: Dummy
Stack (most recent call last):
  File "test.py", line 12, in <module>
    func2()
  File "test.py", line 10, in func2
    func()
  File "test.py", line 7, in func
    logging.exception("Something awful happened!", stack_info=True)
_

この場合、試行から例外まで、そしてルート呼び出しからロギング呼び出しの場所まで1秒でトレースを取得します。

1
ngc92