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


回答:


203

ハンドラー/ブロックlogging.exception内から使用して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

1
このためのdjangoコードを調べましたが、答えはノーだと思いますが、トレースバックを特定の量の文字または深さに制限する方法はありますか?問題は、大きなトレースバックの場合、かなり時間がかかることです。
Eduard Luca

10
ロガーを定義する場合は、これを機能さlogger = logging.getLogger('yourlogger')せるために書き込む必要があることに注意してくださいlogger.exception('...')...
576i

メッセージをログレベルINFOで出力するようにこれを変更できますか?
NM

Azureインサイトなどの特定の外部アプリの場合、トラックバックはログに保存されないことに注意してください。次に、以下に示すように、メッセージ文字列に明示的に渡す必要があります。
エドガーH

138

使用exc_infoオプションはより良いかもしれませんが、警告またはエラーのタイトルのままです:

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

exc_info=クワーグが何と呼ばれていたか思い出せない。ありがとう!
ベルト2017年

4
これは、型が重複して2回ログに記録されることを除いて、logging.exceptionと同じです。エラー以外のレベルが必要でない限り、logging.exceptionを使用してください。
Wyrmwood 2017年

@Wyrmwoodそれはあなたがメッセージを提供しなければならないので同一ではありませんlogging.exception
ピーター・ウッド

57

私の仕事は最近、アプリケーションからのすべてのトレースバック/例外をログに記録するように命じられました。上記のような他の人がオンラインで投稿したさまざまな手法を試しましたが、別の方法で解決しました。オーバーライド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トラッカーからのものでした。

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

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例外をキャッチしてログに記録するためにrunメソッドをサブクラス化して、独自のtry exceptブロックにラップするか、独自のtry exceptブロックで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")

サルのパッチ適用の2番目の方法は、threading.Thread.run直後に一度だけ実行して__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'ロガーをセットアップして、エラーレベルのログを取得し、分析のために家に送ります。


2
かなり興味深いアプローチ。1つの質問- add_custom_print_exceptionリンク先のサイトにはないようです。代わりに、まったく異なる最終コードがいくつかあります。あなたはどちらがより良い/より最終的であると思いますか、そしてなぜですか?ありがとう!
2014

ありがとう、素晴らしい答えです!
101:

カットアンドペーストのタイプミスがあります。old_print_exceptionへの委任された呼び出しでは、制限とファイルはなしではなく制限とファイルに渡される必要があります-old_print_exception(etype、value、tb、limit、file)
Marvin

最後のコードブロックでは、StringIOを初期化してそれに例外を出力するのではなく、単に呼び出すことができますlogger.error(traceback.format_tb())(例外情報も必要な場合はformat_exc()を呼び出します)。
ジェームズ

8

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

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する場合、Pythonの課題追跡の課題1230540に記載されているように、キャッチされない例外が内部で発生しても、を使用して作成されたスレッドトリガーされないことsys.excepthookに注意してください。元のコードをブロックにラップしてブロック内から呼び出す代替メソッドでThread.__init__上書きself.runするモンキーパッチなど、この制限を回避するためにいくつかのハックが提案されています。または、各スレッドのエントリポイントを手動で/ 自分でラップすることもできます。runtrysys.excepthookexcepttryexcept


3

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

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

./test.py 2>> mylog.log

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

./test.py &> mylog.log


1

ロガーを使用して、任意のレベル(DEBUG、INFO、...)でトレースバックを取得できます。を使用するlogging.exceptionと、レベルはERRORになることに注意してください。

# test_app.py
import sys
import logging

logging.basicConfig(level="DEBUG")

def do_something():
    raise ValueError(":(")

try:
    do_something()
except Exception:
    logging.debug("Something went wrong", exc_info=sys.exc_info())
DEBUG:root:Something went wrong
Traceback (most recent call last):
  File "test_app.py", line 10, in <module>
    do_something()
  File "test_app.py", line 7, in do_something
    raise ValueError(":(")
ValueError: :(

編集:

これも機能します(Python 3.6を使用)

logging.debug("Something went wrong", exc_info=True)

1

これはsys.excepthookを使用するバージョンです

import traceback
import sys

logger = logging.getLogger()

def handle_excepthook(type, message, stack):
     logger.error(f'An unhandled exception occured: {message}. Traceback: {traceback.format_tb(stack)}')

sys.excepthook = handle_excepthook

{traceback.format_exc()}代わりに使用してみ{traceback.format_tb(stack)}ませんか?
変数

0

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

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

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