株式会社CenterWaveでは、Webからの情報収集などをSeleniumなどを使って、自動化しています。
その際、定期実行するためのCUIでの起動と、起動してから細かい設定を気楽にいじれるGUIでの起動をサポートすることが多いです。
GUIは手軽なのでtkinterを利用しています。
その際、様々なエラーが起きます。
社内アプリとはいえ、より高いクオリティを目指すためには、定期実行した際に起きたエラーや、自分以外の人間が起きたエラーをキャッチしてログを残したり、メールやチャットなどで、製作者に送る必要があります。
我が社では情報共有にChatWorkを利用しているので、https://tonari-it.com/python-chatwork/を参考に、ChatWorkのAPIに送信するクラスChatBotを作成し、製作者にチャットルームにログを送っています。
しかし、tkinterはデフォルトでは、内部で起きた例外をターミナルに出力して、握りつぶしてしまいます。
つまり、
from tkinter import *
from chatbot import ChatBot
bot = ChatBot("APIキー", "ルームID")
try:
root = Tk()
root.mainloop()
except:
import traceback
import sys
bot.send(" ",join(sys.argv) + "\n" + traceback.format_exc())
raise
としても、mainloop内で例外が起きても何も起きません。
tkinterにはCallWrapperというクラスがあり、これがtkinter内での関数のコールをすべてラップして、アプリ終了以外の例外を握りつぶしているのです。
これに関してWeb上の日本語情報は見つからず、英語情報もPython 2時代の頃のものばかりでした。
そこで、http://mgltools.scripps.edu/api/DejaVu/Tkinter-pysrc.html#CallWrapperで見つけたpython 2時代のコードを書きます。
class CallWrapper:
"""Internal class. Stores function to call when some user
defined Tcl function is called e.g. after an event occurred."""
def __init__(self, func, subst, widget):
"""Store FUNC, SUBST and WIDGET as members."""
self.func = func
self.subst = subst
self.widget = widget
def __call__(self, *args):
"""Apply first function SUBST to arguments, than FUNC."""
try:
if self.subst:
args = self.subst(*args)
return self.func(*args)
except SystemExit, msg:
raise SystemExit, msg
except:
self.widget._report_exception()
これを自作クラスで上書きする必要があります。
python 3で書いたコードが次です。
import tkinter as tk
from tkinter import *
from chatbot import ChatBot
bot = ChatBot("APIキー", "ルームID")
class Catcher:
"""例外の情報をChatWorkに送る"""
def __init__(self, func, subst, widget):
self.func = func
self.subst = subst
self.widget = sidget
def __call__(self, *args):
try:
if self.subst:
args = self.subst(*args)
return self.func(*args)
except SystemExit as e:
raise e
except:
import traceback
import sys
bot.send(" ",join(sys.argv) + "\n" + traceback.format_exc())
self.widget._report_exception()
tk.CallWrapper = Catcher
root = Tk()
root.mainloop()
これでmainloop内でアプリ終了例外が発生すると、ChatWorkに起動時のコマンドと例外のスタックトレースを送信します。
pythonについて調べようとすると、python 2の情報ばかり出てくるのは困りものですね。