上週公司組織Python方面的代碼review,其中提到一個問題就是沒有一個統一的異常日誌分析機制,都亂七八糟的,而後回頭看了一下本身項目的異常處理方面,感受對Python異常體系以及相關的工具模塊瞭解不是很深。有必要整理一下關於Python異常處理方面的一些基礎知識。 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
|
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
|
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()
|
若是平時開發喜歡基於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
|
也許你會問,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}
|
在使用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)# 更加嚴重的錯誤級別
|