[Python] 當Exception發生時,怎麼抓它發生的位置以及詳細原因?

  • 57171
  • 0
  • 2019-12-04

Python與C#不同,他不會預設就將完整的CallStack都夾帶在Exception物件裡面,而是需要透過其他類別來協助才能看到這些資訊。

寫好一段程式之後,通常都是在本機端做好單元測試或是整合測試才會上線。不過,使用Python撰寫的程式在上線之後,如果發生了意想不到的例外錯誤,要怎麼將它紀錄下來呢?

先說明一下Python的Exception機制,我們建立一支.py程式叫做testException.py,裡面放一個最簡單的try-catch,用1除以0:

# -*- coding: utf-8 -*-

try:
    a = 1 / 0
except Exception as e:
    print(e)

得到的錯誤訊息是:

division by zero

的確就是這樣沒錯,但是如果它發生在另外一支.py檔案裡面呢?我們先定義一支新的.py程式叫做test.py,定義一個方法叫做run,再把a = 1 / 0移到方法裡面:

# -*- coding: utf-8 -*-

def run():
    a = 1 / 0

原本的testException.py改成這樣:

# -*- coding: utf-8 -*-

import test

try:
    test.run()
except Exception as e:
    print(e)

得到的錯誤訊息還是一樣:

division by zero

這下問題來了,我要怎麼知道Exception是發生在哪一支程式的哪一行,錯誤類型以及詳細內容又是什麼???這時候就要依靠sys與traceback套件來做這些事情:

# -*- coding: utf-8 -*-

import sys
import traceback
import test

try:
    test.run()
except Exception as e:
#    print(e)
    error_class = e.__class__.__name__ #取得錯誤類型
    detail = e.args[0] #取得詳細內容
    cl, exc, tb = sys.exc_info() #取得Call Stack
    lastCallStack = traceback.extract_tb(tb)[-1] #取得Call Stack的最後一筆資料
    fileName = lastCallStack[0] #取得發生的檔案名稱
    lineNum = lastCallStack[1] #取得發生的行號
    funcName = lastCallStack[2] #取得發生的函數名稱
    errMsg = "File \"{}\", line {}, in {}: [{}] {}".format(fileName, lineNum, funcName, error_class, detail)
    print(errMsg)
    

大致上的流程是先取得Exception的類型以及內容,然後取得目前的Call Stack,再利用traceback套件來解開Stack內容,就可以取得檔名、行號、函數名稱等重要資訊。

[補充]
如果需要完整的CallStack,可以直接使用這樣來取得一個完整的CallStack字串(就像是把try-catch拿掉並在Spyder裡面執行的那種格式):

traceback.format_exc()

得到這些資訊之後,就可以再搭配Python內建的logging套件來做錯誤紀錄,並且使用SMTP發信通知系統維運人員~

Python Version:
3.6.6

Python Packages:
sys
traceback

Reference:
1. https://docs.python.org/3.6/library/stdtypes.html#instance.__class__
2. https://docs.python.org/3.6/tutorial/errors.html
3. https://docs.python.org/3.6/library/traceback.html