cgitb – 詳細なトレースバックレポート

目的:cgitb は traceback よりも詳細なトレースバック情報を提供する
利用できるバージョン:2.2 以上

もともと cgitb は、web アプリケーションのエラーとデバッグ情報の表示を目的に設計されていました。後になってプレーンテキストの出力も同様に含めるように拡張されましたが、残念ながら名前は変更されませんでした。この名前は分かり難いので、このモジュールはあまり使われていません。そうとは言え cgitb は標準ライブラリの優れたデバッグツールです。

標準トレースバックの出力

Python のデフォルトの例外処理の動作は、エラーが発生した箇所へ至るコールスタックと トレースバック を標準エラー出力に表示します。この基本的な出力は、例外が発生した原因を理解して、その不具合を修正するのに十分な情報を提供します。

def func1(arg1):
    local_var = arg1 * 2
    return func2(local_var)

def func2(arg2):
    local_var = arg2 + 2
    return func3(local_var)

def func3(arg3):
    local_var = arg2 / 2
    return local_var

func1(1)

上述したサンプルプログラムは func3() に分かり難いエラーがあります。

$ python cgitb_basic_traceback.py
Traceback (most recent call last):
  File "cgitb_basic_traceback.py", line 22, in <module>
    func1(1)
  File "cgitb_basic_traceback.py", line 12, in func1
    return func2(local_var)
  File "cgitb_basic_traceback.py", line 16, in func2
    return func3(local_var)
  File "cgitb_basic_traceback.py", line 19, in func3
    local_var = arg2 / 2
NameError: global name 'arg2' is not defined

詳細なトレースバックを有効にする

標準のトレースバックはエラーが発生した箇所を見つけるのに十分な情報を提供しますが、 cgitb を有効にすると sys.excepthook をさらに詳細なトレースバック情報を出力するように拡張した関数に置き換えてくれます。

import cgitb
cgitb.enable(format='text')

def func1(arg1):
    local_var = arg1 * 2
    return func2(local_var)

def func2(arg2):
    local_var = arg2 + 2
    return func3(local_var)

def func3(arg3):
    local_var = arg2 / 2
    return local_var

func1(1)

ご覧のように、このエラーレポートはかなり広範囲に及びます。スタックの各フレームが次の内容にそって表示されます。

  • ファイル名だけではなく、ソースファイルのフルパスを表示する
  • スタックの各関数への引数の値を表示する
  • エラーパス周辺のソースコンテキストを数行表示する
  • エラーを発生させた式の変数の値を表示する
$ python cgitb_extended_traceback.py
<type 'exceptions.NameError'>
Python 2.7.2: /Users/dhellmann/Envs/pymotw-ja/bin/python
Sun Feb 17 11:32:15 2013

A problem occurred in a Python script.  Here is the sequence of
function calls leading up to the error, in the order they occurred.

 /Users/dhellmann/Devel/pymotw-ja/t2y/PyMOTW/cgitb/cgitb_extended_traceback.py in <module>()
   21 def func3(arg3):
   22     local_var = arg2 / 2
   23     return local_var
   24
   25 func1(1)
func1 = <function func1>

 /Users/dhellmann/Devel/pymotw-ja/t2y/PyMOTW/cgitb/cgitb_extended_traceback.py in func1(arg1=1)
   13 def func1(arg1):
   14     local_var = arg1 * 2
   15     return func2(local_var)
   16
   17 def func2(arg2):
global func2 = <function func2>
local_var = 2

 /Users/dhellmann/Devel/pymotw-ja/t2y/PyMOTW/cgitb/cgitb_extended_traceback.py in func2(arg2=2)
   17 def func2(arg2):
   18     local_var = arg2 + 2
   19     return func3(local_var)
   20
   21 def func3(arg3):
global func3 = <function func3>
local_var = 4

 /Users/dhellmann/Devel/pymotw-ja/t2y/PyMOTW/cgitb/cgitb_extended_traceback.py in func3(arg3=4)
   20
   21 def func3(arg3):
   22     local_var = arg2 / 2
   23     return local_var
   24
local_var undefined
arg2 undefined
<type 'exceptions.NameError'>: global name 'arg2' is not defined
    __class__ = <type 'exceptions.NameError'>
    __delattr__ = <method-wrapper '__delattr__' of exceptions.NameError object>
    __dict__ = {}
    __doc__ = 'Name not found globally.'
    __format__ = <built-in method __format__ of exceptions.NameError object>
    __getattribute__ = <method-wrapper '__getattribute__' of exceptions.NameError object>
    __getitem__ = <method-wrapper '__getitem__' of exceptions.NameError object>
    __getslice__ = <method-wrapper '__getslice__' of exceptions.NameError object>
    __hash__ = <method-wrapper '__hash__' of exceptions.NameError object>
    __init__ = <method-wrapper '__init__' of exceptions.NameError object>
    __new__ = <built-in method __new__ of type object>
    __reduce__ = <built-in method __reduce__ of exceptions.NameError object>
    __reduce_ex__ = <built-in method __reduce_ex__ of exceptions.NameError object>
    __repr__ = <method-wrapper '__repr__' of exceptions.NameError object>
    __setattr__ = <method-wrapper '__setattr__' of exceptions.NameError object>
    __setstate__ = <built-in method __setstate__ of exceptions.NameError object>
    __sizeof__ = <built-in method __sizeof__ of exceptions.NameError object>
    __str__ = <method-wrapper '__str__' of exceptions.NameError object>
    __subclasshook__ = <built-in method __subclasshook__ of type object>
    __unicode__ = <built-in method __unicode__ of exceptions.NameError object>
    args = ("global name 'arg2' is not defined",)
    message = "global name 'arg2' is not defined"

The above is a description of an error in a Python program.  Here is
the original traceback:

Traceback (most recent call last):
  File "cgitb_extended_traceback.py", line 25, in <module>
    func1(1)
  File "cgitb_extended_traceback.py", line 15, in func1
    return func2(local_var)
  File "cgitb_extended_traceback.py", line 19, in func2
    return func3(local_var)
  File "cgitb_extended_traceback.py", line 22, in func3
    local_var = arg2 / 2
NameError: global name 'arg2' is not defined

この出力の最後の方に、(message 以外のデバッグに役立つ属性を持つ場合は)例外オブジェクトの全ての詳細とオリジナルのトレースバック出力も含みます。

トレースバックのローカル変数

エラースタックで実行された変数へアクセスできることは、実際に例外が発生した行よりもスタックの高いところで起こる論理的なエラーを見つけるのに役立ちます。

import cgitb
cgitb.enable(format='text')

def func2(a, divisor):
    return a / divisor

def func1(a, b):
    c = b - 5
    return func2(a, c)

func1(1, 5)

ZeroDivisionError を発生させるこのサンプルコードでは、 func2() で使用される値の計算よりも func1()c の値の計算でこの問題が発生することが分かります。

$ python cgitb_local_vars.py
<type 'exceptions.ZeroDivisionError'>
Python 2.7.2: /Users/dhellmann/Envs/pymotw-ja/bin/python
Sun Feb 17 11:32:15 2013

A problem occurred in a Python script.  Here is the sequence of
function calls leading up to the error, in the order they occurred.

 /Users/dhellmann/Devel/pymotw-ja/t2y/PyMOTW/cgitb/cgitb_local_vars.py in <module>()
   16 def func1(a, b):
   17     c = b - 5
   18     return func2(a, c)
   19
   20 func1(1, 5)
func1 = <function func1>

 /Users/dhellmann/Devel/pymotw-ja/t2y/PyMOTW/cgitb/cgitb_local_vars.py in func1(a=1, b=5)
   16 def func1(a, b):
   17     c = b - 5
   18     return func2(a, c)
   19
   20 func1(1, 5)
global func2 = <function func2>
a = 1
c = 0

 /Users/dhellmann/Devel/pymotw-ja/t2y/PyMOTW/cgitb/cgitb_local_vars.py in func2(a=1, divisor=0)
   12
   13 def func2(a, divisor):
   14     return a / divisor
   15
   16 def func1(a, b):
a = 1
divisor = 0
<type 'exceptions.ZeroDivisionError'>: integer division or modulo by zero
    __class__ = <type 'exceptions.ZeroDivisionError'>
    __delattr__ = <method-wrapper '__delattr__' of exceptions.ZeroDivisionError object>
    __dict__ = {}
    __doc__ = 'Second argument to a division or modulo operation was zero.'
    __format__ = <built-in method __format__ of exceptions.ZeroDivisionError object>
    __getattribute__ = <method-wrapper '__getattribute__' of exceptions.ZeroDivisionError object>
    __getitem__ = <method-wrapper '__getitem__' of exceptions.ZeroDivisionError object>
    __getslice__ = <method-wrapper '__getslice__' of exceptions.ZeroDivisionError object>
    __hash__ = <method-wrapper '__hash__' of exceptions.ZeroDivisionError object>
    __init__ = <method-wrapper '__init__' of exceptions.ZeroDivisionError object>
    __new__ = <built-in method __new__ of type object>
    __reduce__ = <built-in method __reduce__ of exceptions.ZeroDivisionError object>
    __reduce_ex__ = <built-in method __reduce_ex__ of exceptions.ZeroDivisionError object>
    __repr__ = <method-wrapper '__repr__' of exceptions.ZeroDivisionError object>
    __setattr__ = <method-wrapper '__setattr__' of exceptions.ZeroDivisionError object>
    __setstate__ = <built-in method __setstate__ of exceptions.ZeroDivisionError object>
    __sizeof__ = <built-in method __sizeof__ of exceptions.ZeroDivisionError object>
    __str__ = <method-wrapper '__str__' of exceptions.ZeroDivisionError object>
    __subclasshook__ = <built-in method __subclasshook__ of type object>
    __unicode__ = <built-in method __unicode__ of exceptions.ZeroDivisionError object>
    args = ('integer division or modulo by zero',)
    message = 'integer division or modulo by zero'

The above is a description of an error in a Python program.  Here is
the original traceback:

Traceback (most recent call last):
  File "cgitb_local_vars.py", line 20, in <module>
    func1(1, 5)
  File "cgitb_local_vars.py", line 18, in func1
    return func2(a, c)
  File "cgitb_local_vars.py", line 14, in func2
    return a / divisor
ZeroDivisionError: integer division or modulo by zero

エラーを引き起こすスタックフレームで使用される変数を調べる cgitb のコードは、オブジェクトの属性を評価して表示するのにも十分実用できます。

import cgitb
cgitb.enable(format='text')

class BrokenClass(object):
    """This class has an error.
    """
    
    def __init__(self, a, b):
        """Be careful passing arguments in here.
        """
        self.a = a
        self.b = b
        self.c = self.a * self.b
        self.d = self.a / self.b
        return

o = BrokenClass(1, 0)

ここでは self.aself.b が、エラーの発生し易いコードで実行されます。

$ python cgitb_with_classes.py
<type 'exceptions.ZeroDivisionError'>
Python 2.7.2: /Users/dhellmann/Envs/pymotw-ja/bin/python
Sun Feb 17 11:32:15 2013

A problem occurred in a Python script.  Here is the sequence of
function calls leading up to the error, in the order they occurred.

 /Users/dhellmann/Devel/pymotw-ja/t2y/PyMOTW/cgitb/cgitb_with_classes.py in <module>()
   24         return
   25
   26 o = BrokenClass(1, 0)
   27
   28
o undefined
BrokenClass = <class '__main__.BrokenClass'>

 /Users/dhellmann/Devel/pymotw-ja/t2y/PyMOTW/cgitb/cgitb_with_classes.py in __init__(self=<__main__.BrokenClass object>, a=1, b=0)
   21         self.b = b
   22         self.c = self.a * self.b
   23         self.d = self.a / self.b
   24         return
   25
self = <__main__.BrokenClass object>
self.d undefined
self.a = 1
self.b = 0
<type 'exceptions.ZeroDivisionError'>: integer division or modulo by zero
    __class__ = <type 'exceptions.ZeroDivisionError'>
    __delattr__ = <method-wrapper '__delattr__' of exceptions.ZeroDivisionError object>
    __dict__ = {}
    __doc__ = 'Second argument to a division or modulo operation was zero.'
    __format__ = <built-in method __format__ of exceptions.ZeroDivisionError object>
    __getattribute__ = <method-wrapper '__getattribute__' of exceptions.ZeroDivisionError object>
    __getitem__ = <method-wrapper '__getitem__' of exceptions.ZeroDivisionError object>
    __getslice__ = <method-wrapper '__getslice__' of exceptions.ZeroDivisionError object>
    __hash__ = <method-wrapper '__hash__' of exceptions.ZeroDivisionError object>
    __init__ = <method-wrapper '__init__' of exceptions.ZeroDivisionError object>
    __new__ = <built-in method __new__ of type object>
    __reduce__ = <built-in method __reduce__ of exceptions.ZeroDivisionError object>
    __reduce_ex__ = <built-in method __reduce_ex__ of exceptions.ZeroDivisionError object>
    __repr__ = <method-wrapper '__repr__' of exceptions.ZeroDivisionError object>
    __setattr__ = <method-wrapper '__setattr__' of exceptions.ZeroDivisionError object>
    __setstate__ = <built-in method __setstate__ of exceptions.ZeroDivisionError object>
    __sizeof__ = <built-in method __sizeof__ of exceptions.ZeroDivisionError object>
    __str__ = <method-wrapper '__str__' of exceptions.ZeroDivisionError object>
    __subclasshook__ = <built-in method __subclasshook__ of type object>
    __unicode__ = <built-in method __unicode__ of exceptions.ZeroDivisionError object>
    args = ('integer division or modulo by zero',)
    message = 'integer division or modulo by zero'

The above is a description of an error in a Python program.  Here is
the original traceback:

Traceback (most recent call last):
  File "cgitb_with_classes.py", line 26, in <module>
    o = BrokenClass(1, 0)
  File "cgitb_with_classes.py", line 23, in __init__
    self.d = self.a / self.b
ZeroDivisionError: integer division or modulo by zero

もっとコンテキストを追加する

たくさんのコメント、スペース、余分なコードがある長い関数があると仮定してください。その場合、デフォルトで表示される5行のコンテキストだと、ウィンドウに表示されるコードから関数の主要部分が押し出されて必要な情報がないかもしれません。 cgitb を利用するときに context に大きな値をセットすると、そういった問題に対応できます。

import cgitb
import sys

context_length = int(sys.argv[1])
cgitb.enable(format='text', context=context_length)

def func2(a, divisor):
    return a / divisor

def func1(a, b):
    c = b - 5
    # 本当に
    # 長い
    # コメントが
    # ここに
    # 記述されます
    return func2(a, c)

func1(1, 5)

トレースバックに表示されるコード量を制御するために contextenable() に渡します。

$ python cgitb_more_context.py 5
<type 'exceptions.ZeroDivisionError'>
Python 2.7.2: /Users/dhellmann/Envs/pymotw-ja/bin/python
Sun Feb 17 11:32:15 2013

A problem occurred in a Python script.  Here is the sequence of
function calls leading up to the error, in the order they occurred.

 /Users/dhellmann/Devel/pymotw-ja/t2y/PyMOTW/cgitb/cgitb_more_context.py in <module>()
   24     # ここに
   25     # 記述されます
   26     return func2(a, c)
   27
   28 func1(1, 5)
func1 = <function func1>

 /Users/dhellmann/Devel/pymotw-ja/t2y/PyMOTW/cgitb/cgitb_more_context.py in func1(a=1, b=5)
   24     # ここに
   25     # 記述されます
   26     return func2(a, c)
   27
   28 func1(1, 5)
global func2 = <function func2>
a = 1
c = 0

 /Users/dhellmann/Devel/pymotw-ja/t2y/PyMOTW/cgitb/cgitb_more_context.py in func2(a=1, divisor=0)
   15
   16 def func2(a, divisor):
   17     return a / divisor
   18
   19 def func1(a, b):
a = 1
divisor = 0
<type 'exceptions.ZeroDivisionError'>: integer division or modulo by zero
    __class__ = <type 'exceptions.ZeroDivisionError'>
    __delattr__ = <method-wrapper '__delattr__' of exceptions.ZeroDivisionError object>
    __dict__ = {}
    __doc__ = 'Second argument to a division or modulo operation was zero.'
    __format__ = <built-in method __format__ of exceptions.ZeroDivisionError object>
    __getattribute__ = <method-wrapper '__getattribute__' of exceptions.ZeroDivisionError object>
    __getitem__ = <method-wrapper '__getitem__' of exceptions.ZeroDivisionError object>
    __getslice__ = <method-wrapper '__getslice__' of exceptions.ZeroDivisionError object>
    __hash__ = <method-wrapper '__hash__' of exceptions.ZeroDivisionError object>
    __init__ = <method-wrapper '__init__' of exceptions.ZeroDivisionError object>
    __new__ = <built-in method __new__ of type object>
    __reduce__ = <built-in method __reduce__ of exceptions.ZeroDivisionError object>
    __reduce_ex__ = <built-in method __reduce_ex__ of exceptions.ZeroDivisionError object>
    __repr__ = <method-wrapper '__repr__' of exceptions.ZeroDivisionError object>
    __setattr__ = <method-wrapper '__setattr__' of exceptions.ZeroDivisionError object>
    __setstate__ = <built-in method __setstate__ of exceptions.ZeroDivisionError object>
    __sizeof__ = <built-in method __sizeof__ of exceptions.ZeroDivisionError object>
    __str__ = <method-wrapper '__str__' of exceptions.ZeroDivisionError object>
    __subclasshook__ = <built-in method __subclasshook__ of type object>
    __unicode__ = <built-in method __unicode__ of exceptions.ZeroDivisionError object>
    args = ('integer division or modulo by zero',)
    message = 'integer division or modulo by zero'

The above is a description of an error in a Python program.  Here is
the original traceback:

Traceback (most recent call last):
  File "cgitb_more_context.py", line 28, in <module>
    func1(1, 5)
  File "cgitb_more_context.py", line 26, in func1
    return func2(a, c)
  File "cgitb_more_context.py", line 17, in func2
    return a / divisor
ZeroDivisionError: integer division or modulo by zero

値を増やして再度実行すると、コード内の問題を見つけるのに十分な関数の情報を取得できます。

$ python cgitb_more_context.py 10
<type 'exceptions.ZeroDivisionError'>
Python 2.7.2: /Users/dhellmann/Envs/pymotw-ja/bin/python
Sun Feb 17 11:32:15 2013

A problem occurred in a Python script.  Here is the sequence of
function calls leading up to the error, in the order they occurred.

 /Users/dhellmann/Devel/pymotw-ja/t2y/PyMOTW/cgitb/cgitb_more_context.py in <module>()
   19 def func1(a, b):
   20     c = b - 5
   21     # 本当に
   22     # 長い
   23     # コメントが
   24     # ここに
   25     # 記述されます
   26     return func2(a, c)
   27
   28 func1(1, 5)
func1 = <function func1>

 /Users/dhellmann/Devel/pymotw-ja/t2y/PyMOTW/cgitb/cgitb_more_context.py in func1(a=1, b=5)
   19 def func1(a, b):
   20     c = b - 5
   21     # 本当に
   22     # 長い
   23     # コメントが
   24     # ここに
   25     # 記述されます
   26     return func2(a, c)
   27
   28 func1(1, 5)
global func2 = <function func2>
a = 1
c = 0

 /Users/dhellmann/Devel/pymotw-ja/t2y/PyMOTW/cgitb/cgitb_more_context.py in func2(a=1, divisor=0)
   12
   13 context_length = int(sys.argv[1])
   14 cgitb.enable(format='text', context=context_length)
   15
   16 def func2(a, divisor):
   17     return a / divisor
   18
   19 def func1(a, b):
   20     c = b - 5
   21     # 本当に
a = 1
divisor = 0
<type 'exceptions.ZeroDivisionError'>: integer division or modulo by zero
    __class__ = <type 'exceptions.ZeroDivisionError'>
    __delattr__ = <method-wrapper '__delattr__' of exceptions.ZeroDivisionError object>
    __dict__ = {}
    __doc__ = 'Second argument to a division or modulo operation was zero.'
    __format__ = <built-in method __format__ of exceptions.ZeroDivisionError object>
    __getattribute__ = <method-wrapper '__getattribute__' of exceptions.ZeroDivisionError object>
    __getitem__ = <method-wrapper '__getitem__' of exceptions.ZeroDivisionError object>
    __getslice__ = <method-wrapper '__getslice__' of exceptions.ZeroDivisionError object>
    __hash__ = <method-wrapper '__hash__' of exceptions.ZeroDivisionError object>
    __init__ = <method-wrapper '__init__' of exceptions.ZeroDivisionError object>
    __new__ = <built-in method __new__ of type object>
    __reduce__ = <built-in method __reduce__ of exceptions.ZeroDivisionError object>
    __reduce_ex__ = <built-in method __reduce_ex__ of exceptions.ZeroDivisionError object>
    __repr__ = <method-wrapper '__repr__' of exceptions.ZeroDivisionError object>
    __setattr__ = <method-wrapper '__setattr__' of exceptions.ZeroDivisionError object>
    __setstate__ = <built-in method __setstate__ of exceptions.ZeroDivisionError object>
    __sizeof__ = <built-in method __sizeof__ of exceptions.ZeroDivisionError object>
    __str__ = <method-wrapper '__str__' of exceptions.ZeroDivisionError object>
    __subclasshook__ = <built-in method __subclasshook__ of type object>
    __unicode__ = <built-in method __unicode__ of exceptions.ZeroDivisionError object>
    args = ('integer division or modulo by zero',)
    message = 'integer division or modulo by zero'

The above is a description of an error in a Python program.  Here is
the original traceback:

Traceback (most recent call last):
  File "cgitb_more_context.py", line 28, in <module>
    func1(1, 5)
  File "cgitb_more_context.py", line 26, in func1
    return func2(a, c)
  File "cgitb_more_context.py", line 17, in func2
    return a / divisor
ZeroDivisionError: integer division or modulo by zero

例外プロパティ

各スタックフレームのローカル変数に加えて、 cgitb は例外オブジェクトの全てのプロパティを表示します。拡張プロパティをもつカスタム例外を扱う場合、それはエラーレポートの一部に表示されます。

import cgitb
cgitb.enable(format='text')

class MyException(Exception):
    """Add extra properties to a special exception
    """
    
    def __init__(self, message, bad_value):
        self.bad_value = bad_value
        Exception.__init__(self, message)
        return

raise MyException('Normal message', bad_value=99)

このサンプルでは、 bad_value プロパティが標準の messageargs の間にあります。

$ python cgitb_exception_properties.py
<class '__main__.MyException'>
Python 2.7.2: /Users/dhellmann/Envs/pymotw-ja/bin/python
Sun Feb 17 11:32:15 2013

A problem occurred in a Python script.  Here is the sequence of
function calls leading up to the error, in the order they occurred.

 /Users/dhellmann/Devel/pymotw-ja/t2y/PyMOTW/cgitb/cgitb_exception_properties.py in <module>()
   18         self.bad_value = bad_value
   19         Exception.__init__(self, message)
   20         return
   21
   22 raise MyException('Normal message', bad_value=99)
MyException = <class '__main__.MyException'>
bad_value undefined
<class '__main__.MyException'>: Normal message
    __class__ = <class '__main__.MyException'>
    __delattr__ = <method-wrapper '__delattr__' of MyException object>
    __dict__ = {'bad_value': 99}
    __doc__ = 'Add extra properties to a special exception\n    '
    __format__ = <built-in method __format__ of MyException object>
    __getattribute__ = <method-wrapper '__getattribute__' of MyException object>
    __getitem__ = <method-wrapper '__getitem__' of MyException object>
    __getslice__ = <method-wrapper '__getslice__' of MyException object>
    __hash__ = <method-wrapper '__hash__' of MyException object>
    __init__ = <bound method MyException.__init__ of MyException('Normal message',)>
    __module__ = '__main__'
    __new__ = <built-in method __new__ of type object>
    __reduce__ = <built-in method __reduce__ of MyException object>
    __reduce_ex__ = <built-in method __reduce_ex__ of MyException object>
    __repr__ = <method-wrapper '__repr__' of MyException object>
    __setattr__ = <method-wrapper '__setattr__' of MyException object>
    __setstate__ = <built-in method __setstate__ of MyException object>
    __sizeof__ = <built-in method __sizeof__ of MyException object>
    __str__ = <method-wrapper '__str__' of MyException object>
    __subclasshook__ = <built-in method __subclasshook__ of type object>
    __unicode__ = <built-in method __unicode__ of MyException object>
    __weakref__ = None
    args = ('Normal message',)
    bad_value = 99
    message = 'Normal message'

The above is a description of an error in a Python program.  Here is
the original traceback:

Traceback (most recent call last):
  File "cgitb_exception_properties.py", line 22, in <module>
    raise MyException('Normal message', bad_value=99)
MyException: Normal message

トレースバックをロギングする

よくある環境では、トレースバック情報の詳細は標準エラー出力に表示することが最も良い方法です。しかし、本番環境では、エラーをロギングする方がもっと良いです。 enable() は、エラーロギングを有効にする logdir というオプション引数を取ります。ディレクトリ名を指定すると、それぞれの例外はそのディレクトリのログファイルにロギングされます。

import cgitb
import os

cgitb.enable(logdir=os.path.join(os.path.dirname(__file__), 'LOGS'),
             display=False,
             format='text',
             )

def func2(a, divisor):
    return a / divisor

def func1(a, b):
    c = b - 5
    return func2(a, c)

func1(1, 5)

エラーの出力は抑制されますが、そのエラーメッセージはエラーログを探しに行くと見つけられます。

$ python cgitb_log_exception.py
<p>A problem occurred in a Python script.
<p> /Users/dhellmann/Devel/pymotw-ja/t2y/PyMOTW/cgitb/LOGS/tmpGqH25H.txt contains the description of this error.

$ ls LOGS
tmpGqH25H.txt

$ cat LOGS/*.txt
<type 'exceptions.ZeroDivisionError'>
Python 2.7.2: /Users/dhellmann/Envs/pymotw-ja/bin/python
Sun Feb 17 11:32:15 2013

A problem occurred in a Python script.  Here is the sequence of
function calls leading up to the error, in the order they occurred.

 /Users/dhellmann/Devel/pymotw-ja/t2y/PyMOTW/cgitb/cgitb_log_exception.py in <module>()
   21 def func1(a, b):
   22     c = b - 5
   23     return func2(a, c)
   24
   25 func1(1, 5)
func1 = <function func1>

 /Users/dhellmann/Devel/pymotw-ja/t2y/PyMOTW/cgitb/cgitb_log_exception.py in func1(a=1, b=5)
   21 def func1(a, b):
   22     c = b - 5
   23     return func2(a, c)
   24
   25 func1(1, 5)
global func2 = <function func2>
a = 1
c = 0

 /Users/dhellmann/Devel/pymotw-ja/t2y/PyMOTW/cgitb/cgitb_log_exception.py in func2(a=1, divisor=0)
   17
   18 def func2(a, divisor):
   19     return a / divisor
   20
   21 def func1(a, b):
a = 1
divisor = 0
<type 'exceptions.ZeroDivisionError'>: integer division or modulo by zero
    __class__ = <type 'exceptions.ZeroDivisionError'>
    __delattr__ = <method-wrapper '__delattr__' of exceptions.ZeroDivisionError object>
    __dict__ = {}
    __doc__ = 'Second argument to a division or modulo operation was zero.'
    __format__ = <built-in method __format__ of exceptions.ZeroDivisionError object>
    __getattribute__ = <method-wrapper '__getattribute__' of exceptions.ZeroDivisionError object>
    __getitem__ = <method-wrapper '__getitem__' of exceptions.ZeroDivisionError object>
    __getslice__ = <method-wrapper '__getslice__' of exceptions.ZeroDivisionError object>
    __hash__ = <method-wrapper '__hash__' of exceptions.ZeroDivisionError object>
    __init__ = <method-wrapper '__init__' of exceptions.ZeroDivisionError object>
    __new__ = <built-in method __new__ of type object>
    __reduce__ = <built-in method __reduce__ of exceptions.ZeroDivisionError object>
    __reduce_ex__ = <built-in method __reduce_ex__ of exceptions.ZeroDivisionError object>
    __repr__ = <method-wrapper '__repr__' of exceptions.ZeroDivisionError object>
    __setattr__ = <method-wrapper '__setattr__' of exceptions.ZeroDivisionError object>
    __setstate__ = <built-in method __setstate__ of exceptions.ZeroDivisionError object>
    __sizeof__ = <built-in method __sizeof__ of exceptions.ZeroDivisionError object>
    __str__ = <method-wrapper '__str__' of exceptions.ZeroDivisionError object>
    __subclasshook__ = <built-in method __subclasshook__ of type object>
    __unicode__ = <built-in method __unicode__ of exceptions.ZeroDivisionError object>
    args = ('integer division or modulo by zero',)
    message = 'integer division or modulo by zero'

The above is a description of an error in a Python program.  Here is
the original traceback:

Traceback (most recent call last):
  File "cgitb_log_exception.py", line 25, in <module>
    func1(1, 5)
  File "cgitb_log_exception.py", line 23, in func1
    return func2(a, c)
  File "cgitb_log_exception.py", line 19, in func2
    return a / divisor
ZeroDivisionError: integer division or modulo by zero

HTML 出力

cgitb は、もともと web アプリケーションの例外を処理するために開発されていたので、HTML 出力のサンプルを紹介しないわけにはいきません。

import cgitb
cgitb.enable()

def func1(arg1):
    local_var = arg1 * 2
    return func2(local_var)

def func2(arg2):
    local_var = arg2 + 2
    return func3(local_var)

def func3(arg3):
    local_var = arg2 / 2
    return local_var

func1(1)

format 引数を取り除く(または html を指定する)ことで、トレースバック情報の出力フォーマットを HTML に変更します。

../_images/html_error.png

See also

cgitb
本モジュールの標準ライブラリドキュメント
traceback
トレースバック情報を扱う標準ライブラリ
inspect
スタックを調べる関数を提供する inspect モジュール
sys
カレントの実行値と例外が発生したときに実行される excepthook ハンドラへのアクセスを提供する sys モジュール
Improved traceback module
traceback モジュールの改善と開発者が普段行っている機能拡張に関する Python-dev の議論
Bookmark and Share