web-dev-qa-db-ja.com

トレースバック付きのログ例外

Pythonエラーをログに記録するにはどうすればよいですか?

try:
    do_something()
except:
    # How can I log my exception here, complete with its traceback?
115
TIMEX

logging.exception fromをexcept:ハンドラーと共に使用して、メッセージを先頭に付けて現在の例外をログに記録します。

import logging
LOG_FILENAME = '/tmp/logging_example.out'
logging.basicConfig(filename=LOG_FILENAME, level=logging.DEBUG)

logging.debug('This message should go to the log file')

try:
    run_my_stuff()
except:
    logging.exception('Got exception on main handler')
    raise

ログファイル、/tmp/logging_example.outを見てください:

DEBUG:root:This message should go to the log file
ERROR:root:Got exception on main handler
Traceback (most recent call last):
  File "/tmp/teste.py", line 9, in <module>
    run_my_stuff()
NameError: name 'run_my_stuff' is not defined
171
nosklo

exc_infoオプションを使用すると、警告またはエラータイトルのままになります。

try:
    # coode in here
except Exception as e:
    logging.error(e, exc_info=True)
108
flycee

私の仕事は最近、アプリケーションからのすべてのトレースバック/例外を記録することを私に任せました。上記のような他の人がオンラインで投稿したものの、異なるアプローチに落ち着いた数多くのテクニックを試しました。 traceback.print_exceptionをオーバーライドしています。

http://www.bbarrows.com/ に書き込みがあります。これは読みやすくなりますが、ここにも貼り付けます。

ソフトウェアが実際に遭遇する可能性のあるすべての例外をログに記録するタスクを課したとき、python例外トレースバックをログに記録するためにさまざまな手法を試しました。最初は、pythonシステム例外フック、sys.excepthookがロギングコードを挿入するのに最適な場所になると考えました。私は次のようなものを試していました:

import traceback
import StringIO
import logging
import os, sys

def my_excepthook(excType, excValue, traceback, logger=logger):
    logger.error("Logging an uncaught exception",
                 exc_info=(excType, excValue, traceback))

sys.excepthook = my_excepthook  

これはメインスレッドでは機能しましたが、プロセスが開始した新しいスレッドにはsys.excepthookが存在しないことがすぐにわかりました。ほとんどすべてがこのプロジェクトのスレッドで発生するため、これは大きな問題です。

グーグルでたくさんのドキュメントを読んだ後、私が見つけた最も役立つ情報はPython Issue trackerからでした。

スレッドの最初の投稿は、スレッド間で持続しないsys.excepthookの動作例を示しています(以下を参照)。どうやらこれは予想される動作です。

import sys, threading

def log_exception(*args):
    print 'got exception %s' % (args,)
sys.excepthook = log_exception

def foo():
    a = 1 / 0

threading.Thread(target=foo).start()

このPython Issueスレッドのメッセージは、実際に2つの推奨されるハックをもたらします。 Threadをサブクラス化して、例外をキャッチしてログに記録するためにブロックを除く独自のtryでrunメソッドをラップするか、または例外をブロックしてログに記録するthreading.Thread.runモンキーパッチを実行します.

Threadをサブクラス化する最初の方法は、ログスレッドを作成したいすべての場所でカスタムThreadクラスをインポートして使用する必要があるため、コードがエレガントではないようです。コードベース全体を検索し、すべての通常のThreadsをこのカスタムThreadに置き換える必要があるため、これは面倒です。ただし、このThreadが何をしているのかは明確であり、カスタムロギングコードに問題が発生した場合、誰かが診断およびデバッグしやすくなります。カスタムロギングスレッドは次のようになります。

class TracebackLoggingThread(threading.Thread):
    def run(self):
        try:
            super(TracebackLoggingThread, self).run()
        except (KeyboardInterrupt, SystemExit):
            raise
        except Exception, e:
            logger = logging.getLogger('')
            logger.exception("Logging an uncaught exception")

モンキーパッチthreading.Thread.runの2番目の方法は、__main__の直後に一度だけ実行し、すべての例外でロギングコードをインスツルメントすることができるため、素晴らしいです。モンキーパッチは、予想される機能を変更するため、デバッグするのが面倒です。 Python Issue trackerからの推奨パッチは次のとおりです。

def installThreadExcepthook():
    """
    Workaround for sys.excepthook thread bug
    From
http://spyced.blogspot.com/2007/06/workaround-for-sysexcepthook-bug.html

(https://sourceforge.net/tracker/?func=detail&atid=105470&aid=1230540&group_id=5470).
    Call once from __main__ before creating any threads.
    If using psyco, call psyco.cannotcompile(threading.Thread.run)
    since this replaces a new-style class method.
    """
    init_old = threading.Thread.__init__
    def init(self, *args, **kwargs):
        init_old(self, *args, **kwargs)
        run_old = self.run
        def run_with_except_hook(*args, **kw):
            try:
                run_old(*args, **kw)
            except (KeyboardInterrupt, SystemExit):
                raise
            except:
                sys.excepthook(*sys.exc_info())
        self.run = run_with_except_hook
    threading.Thread.__init__ = init

例外ログのテストを開始するまで、私はそれがすべて間違っていることに気付きました。

テストするために私は

raise Exception("Test")

私のコードのどこかに。ただし、このメソッドを呼び出したメソッドをラップすることは、トレースバックを出力して例外を飲み込んだブロックを除いた試行でした。これは、トレースバックブリングがSTDOUTに出力されるのに記録されないので、非常にイライラしました。トレースバックを記録するはるかに簡単な方法は、すべてのpythonコードがトレースバック自体を出力するために使用するメソッドtraceback.print_exceptionにパッチを当てることだけであると判断しました。私は次のようなものになりました:

def add_custom_print_exception():
    old_print_exception = traceback.print_exception
    def custom_print_exception(etype, value, tb, limit=None, file=None):
        tb_output = StringIO.StringIO()
        traceback.print_tb(tb, limit, tb_output)
        logger = logging.getLogger('customLogger')
        logger.error(tb_output.getvalue())
        tb_output.close()
        old_print_exception(etype, value, tb, limit=None, file=None)
    traceback.print_exception = custom_print_exception

このコードは、トレースバックを文字列バッファーに書き込み、ログ記録エラーに記録します。 ERRORレベルのログを取得し、分析のためにそれらを家に送る「customLogger」ロガーを設定したカスタムロギングハンドラーがあります。

52
Brad Barrows

ハンドラーを sys.excepthook に割り当てることにより、おそらくPythonのロギング関数の exc_infoパラメーターを使用して、メインスレッドでキャッチされなかったすべての例外を記録できます

import sys
import logging

logging.basicConfig(filename='/tmp/foobar.log')

def exception_hook(exc_type, exc_value, exc_traceback):
    logging.error(
        "Uncaught exception",
        exc_info=(exc_type, exc_value, exc_traceback)
    )

sys.excepthook = exception_hook

raise Exception('Boom')

ただし、プログラムでスレッドを使用する場合は、 threading.Thread を使用して作成されたスレッドは、notsys.excepthookをトリガーすることに注意してください。 Pythonの課題追跡の Issue 1230540 に記載されているように、キャッチされない例外がそれらの内部で発生します。サルをパッチしてThread.__init__を上書きし、オリジナルをrunブロックでラップし、tryブロック内からself.runを呼び出す別のexceptメソッドでsys.excepthookを上書きするなど、この制限を回避するためのハッキングがいくつか提案されています。または、各スレッドのエントリポイントを手動でtry/exceptにラップすることもできます。

7
Mark Amery

キャッチされない例外メッセージはSTDERRに送られるため、Python自体でログを実装する代わりに、Pythonスクリプトを実行するために使用しているシェルを使用してSTDERRをファイルに送信できます。 Bashスクリプトでは、 BASHガイドで説明 のように、出力リダイレクトでこれを行うことができます。

エラーをファイルに追加し、その他の出力を端末に追加します。

./test.py 2>> mylog.log

インターリーブされたSTDOUTおよびSTDERR出力でファイルを上書きします。

./test.py &> mylog.log
3
panchicore

私が探していたもの:

import sys
import traceback

exc_type, exc_value, exc_traceback = sys.exc_info()
traceback_in_var = traceback.format_tb(exc_traceback)

見る:

1
Martin Thoma

それほどスタイリッシュではないかもしれませんが、簡単です:

#!/bin/bash
log="/var/log/yourlog"
/path/to/your/script.py 2>&1 | (while read; do echo "$REPLY" >> $log; done)
0
Hugo Walter