web-dev-qa-db-ja.com

デバッグ情報とともにPythonエラーを記録するにはどうすればよいですか?

私はlogging.errorを使ってPython例外メッセージをログファイルに出力しています。

import logging
try:
    1/0
except ZeroDivisionError as e:
    logging.error(e)  # ERROR:root:division by zero

例外文字列だけでなく、例外とそれを生成したコードに関するより詳細な情報を印刷することは可能ですか?行番号やスタックトレースのようなものは素晴らしいでしょう。

379

logger.exception はエラーメッセージとともにスタックトレースを出力します。

例えば:

import logging
try:
    1/0
except ZeroDivisionError as e:
    logging.exception("message")

出力:

ERROR:root:message
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: integer division or modulo by zero

@ Paulo Check 注、「Python 3ではexcept部分のすぐ内側でlogging.exceptionメソッドを呼び出す必要があることに注意してください。奇妙な例外が発生する可能性がある場所です。ドキュメントはそれについて警告しています。」

603
SiggyF

SiggyFの答え が表示されないというlogging.exceptionのいいところは、あなたが任意のメッセージを渡すことができることです。

import logging
try:
    1/0
except ZeroDivisionError:
    logging.exception("Deliberate divide by zero traceback")

デフォルトの(最近のバージョンでは)エラーをsys.stderrに出力するというロギング動作では、これは次のようになります。

>>> import logging
>>> try:
...     1/0
... except ZeroDivisionError:
...     logging.exception("Deliberate divide by zero traceback")
... 
ERROR:root:Deliberate divide by zero traceback
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: integer division or modulo by zero
167
ncoghlan

エラーレベルを選択できるようにするには、exc_infoオプションを使用することをお勧めします(exceptionを使用する場合は、常にerrorが表示されます)。

try:
    # do something here
except Exception as e:
    logging.fatal(e, exc_info=True)  # log exception info at FATAL log level
104
flycee

引用符

loggingモジュールを使用しないで、アプリケーションが他の方法でログを取る場合はどうなりますか?

さて、ここでtracebackを使うことができます。

import traceback

def log_traceback(ex, ex_traceback=None):
    if ex_traceback is None:
        ex_traceback = ex.__traceback__
    tb_lines = [ line.rstrip('\n') for line in
                 traceback.format_exception(ex.__class__, ex, ex_traceback)]
    exception_logger.log(tb_lines)
  • Python 2で使用してください。

    try:
        # your function call is here
    except Exception as ex:
        _, _, ex_traceback = sys.exc_info()
        log_traceback(ex, ex_traceback)
    
  • Python 3で使用してください。

    try:
        x = get_number()
    except Exception as ex:
        log_traceback(ex)
    
29
zangw

プレーンログを使用する場合 - すべてのログレコードはこの規則に一致する必要があります:one record = one line。この規則に従って、あなたはあなたのログファイルを処理するためにgrepと他のツールを使うことができます。

しかしトレースバック情報は複数行です。だから私の答えはこのスレッドで上記の zangw によって提案された解決策の拡張版です。問題は、トレースバック行の内部に\nがある可能性があるため、この行末を削除するには追加の作業を行う必要があることです。

import logging


logger = logging.getLogger('your_logger_here')

def log_app_error(e: BaseException, level=logging.ERROR) -> None:
    e_traceback = traceback.format_exception(e.__class__, e, e.__traceback__)
    traceback_lines = []
    for line in [line.rstrip('\n') for line in e_traceback]:
        traceback_lines.extend(line.splitlines())
    logger.log(level, traceback_lines.__str__())

その後(ログを分析するときに)、ログファイルから必要なトレースバック行をコピー&ペーストして、これを実行できます。

ex_traceback = ['line 1', 'line 2', ...]
for line in ex_traceback:
    print(line)

利益!

12
doomatel

この答えは上記の優れたものから成り立っています。

ほとんどのアプリケーションでは、logging.exception(e)を直接呼び出すことはありません。たぶんあなたはこのようにあなたのアプリケーションまたはモジュールに特定のカスタムロガーを定義しました:

# Set the name of the app or module
my_logger = logging.getLogger('NEM Sequencer')
# Set the log level
my_logger.setLevel(logging.INFO)

# Let's say we want to be fancy and log to a graylog2 log server
graylog_handler = graypy.GELFHandler('some_server_ip', 12201)
graylog_handler.setLevel(logging.INFO)
my_logger.addHandler(graylog_handler)

この場合、ロガーを使用して次のように例外(e)を呼び出します。

try:
    1/0
except ZeroDivisionError, e:
    my_logger.exception(e)
9
Will

ほんの少しのデコレータ処理(たぶんモナドと持ち上げに触発された) Python 3.6の型注釈を安全に削除して、古いメッセージフォーマットスタイルを使用することができます。

fallible.py

from functools import wraps
from typing import Callable, TypeVar, Optional
import logging


A = TypeVar('A')


def fallible(*exceptions, logger=None) \
        -> Callable[[Callable[..., A]], Callable[..., Optional[A]]]:
    """
    :param exceptions: a list of exceptions to catch
    :param logger: pass a custom logger; None means the default logger, 
                   False disables logging altogether.
    """
    def fwrap(f: Callable[..., A]) -> Callable[..., Optional[A]]:

        @wraps(f)
        def wrapped(*args, **kwargs):
            try:
                return f(*args, **kwargs)
            except exceptions:
                message = f'called {f} with *args={args} and **kwargs={kwargs}'
                if logger:
                    logger.exception(message)
                if logger is None:
                    logging.exception(message)
                return None

        return wrapped

    return fwrap

デモ:

In [1] from fallible import fallible

In [2]: @fallible(ArithmeticError)
    ...: def div(a, b):
    ...:     return a / b
    ...: 
    ...: 

In [3]: div(1, 2)
Out[3]: 0.5

In [4]: res = div(1, 0)
ERROR:root:called <function div at 0x10d3c6ae8> with *args=(1, 0) and **kwargs={}
Traceback (most recent call last):
  File "/Users/user/fallible.py", line 17, in wrapped
    return f(*args, **kwargs)
  File "<ipython-input-17-e056bd886b5c>", line 3, in div
    return a / b

In [5]: repr(res)
'None'

この解決法を修正して、None部分からexceptよりも少し意味のある何かを返すようにすることもできます(あるいは、この戻り値をfallibleの引数に指定することによって、解決策を一般化することさえできます)。

3
Eli Korvigo