搞清楚 Python traceback

上週公司組織Python方面的代碼review,其中提到一個問題就是沒有一個統一的異常日誌分析機制,都亂七八糟的,而後回頭看了一下本身項目的異常處理方面,感受對Python異常體系以及相關的工具模塊瞭解不是很深。有必要整理一下關於Python異常處理方面的一些基礎知識。 python

1. Python中的異常棧跟蹤

以前在作Java的時候,異常對象默認就包含stacktrace相關的信息,經過異常對象的相關方法printStackTrace()和getStackTrace()等方法就能夠取到異常棧信息,能打印到log輔助調試或者作一些別的事情。可是到了Python,在2.x中,異常對象能夠是任何對象,常常看到不少代碼是直接raise一個字符串出來,所以就不能像Java那樣方便的獲取異常棧了,由於異常對象和異常棧是分開的。而多數Python語言的書籍上重點在於描述Python中如何構造異常對象和raise try except finally這些的使用,對調試程序起關鍵做用的stacktrace每每基本上不怎麼涉及。 git

python中用於處理異常棧的模塊是traceback模塊,它提供了print_exception、format_exception等輸出異常棧等經常使用的工具函數。 python2.7

?
1
2
3
4
5
6
7
8
9
10
deffunc(a, b):
    returna/b
if__name__=='__main__':
    importsys
    importtraceback
    try:
        func(1,0)
    exceptException as e:
        print"print exc"
        traceback.print_exc(file=sys.stdout)



輸出結果:
?
1
2
3
4
5
6
print exc
Traceback (most recent call last):
  File"./teststacktrace.py", line 7,in<module>
    func(1, 0)
  File"./teststacktrace.py", line 2,infunc
    returna / b



其實traceback.print_exc()函數只是traceback.print_exception()函數的一個簡寫形式,而它們獲取異常相關的數據都是經過sys.exc_info()函數獲得的。
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
deffunc(a, b):
    returna/b
if__name__=='__main__':
    importsys
    importtraceback
    try:
        func(1,0)
    exceptException as e:
        print"print_exception()"
        exc_type, exc_value, exc_tb=sys.exc_info()
        print'the exc type is:', exc_type
        print'the exc value is:', exc_value
        print'the exc tb is:', exc_tb
        traceback.print_exception(exc_type, exc_value, exc_tb)



輸出結果:
?
1
2
3
4
5
6
7
8
9
10
print_exception()
the exctypeis: <type'exceptions.ZeroDivisionError'>
the exc value is: integer division or modulo by zero
the exc tb is: <traceback object at 0x104e7d4d0>
Traceback (most recent call last):
  File"./teststacktrace.py", line 7,in<module>
    func(1, 0)
  File"./teststacktrace.py", line 2,infunc
    returna / b
ZeroDivisionError: integer division or modulo by zero



sys.exc_info()返回的值是一個元組,其中第一個元素,exc_type是異常的對象類型,exc_value是異常的值,exc_tb是一個traceback對象,對象中包含出錯的行數、位置等數據。而後經過print_exception函數對這些異常數據進行整理輸出。

traceback模塊提供了extract_tb函數來更加詳細的解釋traceback對象所包含的數據: 函數

?
1
2
3
4
5
6
7
8
9
10
11
deffunc(a, b):
    returna/b
if__name__=='__main__':
    importsys
    importtraceback
    try:
        func(1,0)
    except:
        _, _, exc_tb=sys.exc_info()
        forfilename, linenum, funcname, sourceintraceback.extract_tb(exc_tb):
            print"%-23s:%s '%s' in %s()"%(filename, linenum, source, funcname)



輸出結果:
?
1
2
3
samchimac:tracebacktest samchi$ python ./teststacktrace.py
./teststacktrace.py    :7'func(1, 0)'in<module>()
./teststacktrace.py    :2'return a / b'infunc()



2. 使用cgitb來簡化異常調試

若是平時開發喜歡基於log的方式來調試,那麼可能常常去作這樣的事情,在log裏面發現異常以後,由於信息不足,那麼會再去額外加一些debug log來把相關變量的值輸出。調試完畢以後再把這些debug log去掉。其實不必這麼麻煩,Python庫中提供了cgitb模塊來幫助作這些事情,它可以輸出異常上下文全部相關變量的信息,沒必要每次本身再去手動加debug log。 工具

cgitb的使用簡單的不能想象: ui

?
1
2
3
4
5
6
7
8
deffunc(a, b):
        returna/b
if__name__=='__main__':
        importcgitb
        cgitb.enable(format='text')
        importsys
        importtraceback
        func(1,0)



運行以後就會獲得詳細的數據:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
A problem occurredina Python script.  Here is the sequence of
functioncalls leading up to the error,inthe order they occurred.
 
 /Users/samchi/Documents/workspace/tracebacktest/teststacktrace.pyin<module>()
    4  importcgitb
    5   cgitb.enable(format='text')
    6  importsys
    7  importtraceback
    8   func(1, 0)
func = <functionfunc>
 
 /Users/samchi/Documents/workspace/tracebacktest/teststacktrace.pyinfunc(a=1, b=0)
    2  returna / b
    3if__name__ =='__main__':
    4  importcgitb
    5   cgitb.enable(format='text')
    6  importsys
a = 1
b = 0



徹底沒必要再去log.debug("a=%d" % a)了,我的感受cgitb在線上環境不適合使用,適合在開發的過程當中進行調試,很是的方便。

也許你會問,cgitb爲何會這麼屌?能獲取這麼詳細的出錯信息?其實它的工做原理同它的使用方式同樣的簡單,它只是覆蓋了默認的sys.excepthook函數,sys.excepthook是一個默認的全局異常攔截器,能夠嘗試去自行對它修改: spa

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
deffunc(a, b):
        returna/b
defmy_exception_handler(exc_type, exc_value, exc_tb):
        print"i caught the exception:", exc_type
        whileexc_tb:
                print"the line no:", exc_tb.tb_lineno
                print"the frame locals:", exc_tb.tb_frame.f_locals
                exc_tb=exc_tb.tb_next
 
if__name__=='__main__':
        importsys
        sys.excepthook=my_exception_handler
        importtraceback
        func(1,0)



輸出結果:
?
1
2
3
4
5
i caught the exception: <type'exceptions.ZeroDivisionError'>
the line no:14
the framelocals: {'my_exception_handler': <function my_exception_handler at0x100e04aa0>,'__builtins__': <module'__builtin__'(built-in)>,'__file__':'./teststacktrace.py','traceback': <module'traceback'from'/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/traceback.pyc'>,'__package__':None,'sys': <module'sys'(built-in)>,'func': <function func at0x100e04320>,'__name__':'__main__','__doc__':None}
the line no:2
the framelocals: {'a':1,'b':0}



看到沒有?沒有什麼神奇的東西,只是從stack frame對象中獲取的相關變量的值。frame對象中還有不少神奇的屬性,就不一一探索了。

3. 使用logging模塊來記錄異常

在使用Java的時候,用log4j記錄異常很簡單,只要把Exception對象傳遞給log.error方法就能夠了,可是在Python中就不行了,若是直接傳遞異常對象給log.error,那麼只會在log裏面出現一行異常對象的值。 .net

在Python中正確的記錄Log方式應該是這樣的: debug

?
1
2
3
logging.exception(ex)
logging.error(ex, exc_info=1)# 指名輸出棧蹤影, logging.exception的內部也是包了一層此作法
logging.critical(ex, exc_info=1)# 更加嚴重的錯誤級別
相關文章
相關標籤/搜索