如何為Python新增遠端除錯能力而不修改系統程式碼

潘家邦發表於2016-08-15

最近寫了一些關於 Python 遠端除錯的扯淡向博文,第一篇是「遠端除錯你的 Python 程式碼」,第二篇是「使用 VS Code 遠端除錯 Python 程式」。前些日子開了一個叫做「第八個手藝人」的微信公眾號,本想混個原創,騙點零花錢,於是把這些文章首發在公眾號上了。可惜微信始終不給我原創標記,微信文章的閱讀量也上不去,我也就漸漸失去了玩公眾號興致。

後來看到耗子叔的新博文「為什麼我不在微信公眾號上寫文章」,想想自己寫部落格的初心,果然還是不要整公眾號這些么蛾子了,回到我的部落格,回到我這個可以被 Google 爬取、索引,被同行輕易搜尋到的部落格。

我所熱愛的網際網路,是一個開放、共享的網際網路,而不是現在這樣一個個圍牆越來越高的花園。

晚上看到有同行在我的博文「遠端除錯你的 Python 程式碼」下面留言,希望得知我在文末挖下的坑該如何去填。

當時我沒有立刻回覆,於是就有了這篇博文。下面進入正題,如何在不改動一行系統程式碼的情況下,實現 Python 應用的開啟除錯和關閉除錯。這篇博文裡我不會給出實現程式碼,因為讀者知道了實現原理之後,自己動手實現一下,也許就是幾十分鐘的事情。需要強調的是,這裡的「系統程式碼」,其實是「業務系統程式碼」的意思,也就是我們維護的應用的程式碼。

我們知道,要想使用 ptvsd 為 Python 伺服器開啟遠端除錯功能,需要在程式碼的入口處 import ptvsd,並呼叫 ptvsd.settrace 方法啟動 debug server。具體用法見「使用 VS Code 遠端除錯 Python 程式」,當時我是在程式碼中硬編碼了對 ptvsd 的呼叫。我們這裡需要做的,就是將這種硬編碼的呼叫,從業務程式碼中剝離。

先理一下需求:

  1. 在業務程式碼啟動之前,完成對 ptvsd 的呼叫
  2. 對 ptvsd 的呼叫,不出現在業務系統的程式碼中

在 Python 中,是否能做到在執行一個 py 檔案之前,先執行一點別的程式碼呢?如果可以,那麼我們就能把對 ptvsd 的呼叫,作為這「一點別的程式碼」了。

答案是肯定的。與此相關的知識點是 sitecustomize.py

After these path manipulations, an attempt is made to import a module named sitecustomize, which can perform arbitrary site-specific customizations. It is typically created by a system administrator in the site-packages directory. If this import fails with an ImportError exception, it is silently ignored. If Python is started without output streams available, as with pythonw.exe on Windows (which is used by default to start IDLE), attempted output from sitecustomize is ignored. Any exception other than ImportError causes a silent and perhaps mysterious failure of the process.

上面這段文件來自 Python 標準庫的 site 模組,勉強算是解釋了 sitecustomize 的用途,以及載入時機。反正基本上只要知道當我們執行 python a.py 時,sitecustomize.py 的程式碼會在 Python 直譯器啟動之後,py 檔案執行之前執行就好了。也正因為這樣子,我們把 ptvsd 放到 sitecustomize.py 中呼叫之後,千萬千萬要注意以下幾點:

  1. 不要拋異常,以免影響 py 檔案的正常執行
  2. 不要輸出任何內容到 stdout,以免影響程式之間的互動
  3. 在同一個環境中啟動多個 Python 程式的時候,要注意 debug 埠的分配,以及埠重複時的容錯和提示
  4. 其它我沒想到但是會影響預期行為的點

關於 1,我們可以通過一個巨大的 try catch,把所有異常吞掉,然後輸出異常資訊到日誌檔案或者 stderr,這樣子就避免了我們在 sitecustomize.py 裡不小心寫出的 bug 影響到目標 py 檔案的執行。畢竟 debug 開啟不了事小,檔案執行不了事大。

關於 2,我是曾經掉到坑裡的。VS Code 的 Python 外掛,是呼叫 Python 直譯器去實現智慧提示功能的,早些時候我修改了 sitecustomize.py,將一些資訊輸出到了 stdout,導致 VS Code 的 Python 智慧提示全廢了,排查這個問題費了一番功夫。

關於 3,我們可能需要引入一些稍複雜的方法,需要寫幾十行程式碼。打個比方,我們要從同一個虛擬環境中啟動兩個伺服器程式,那麼我們需要為這兩個程式分配不同的除錯埠。一種可行的方式是使用配置,比如用 ConfigParser 解析一個 ini 檔案,從 ini 中讀取到為指定程式名稱配置的除錯埠, 以及是否開啟除錯等資訊,然後用 psutil 或者類似的類庫,獲取當前程式資訊,和配置資訊做個比對之後,決定當前程式是否需要開啟除錯,除錯埠號是多少。

然後我們在開發或者整合環境中部署遠端除錯的時候,只需要把這個萬年不變的 sitecustomize.py、根據不同環境稍作修改的 ini 檔案推到目標機器的虛擬環境中就好,Python 應用的程式碼無需為了遠端除錯做任何修改。

基本上就是這樣子了,攤開來說也沒啥稀奇的,無非就是 sitecustomize.py 這個鉤子而已,希望對讀者有所啟發。

Happy hacking!


相關文章