Pythonには標準でLoggerというログ出力を行うためのクラスがあります。
このLoggerに独自のハンドラを追加することで、たとえばログの出力をGUIに表示するなどといったことができるようになります。
まずはHandlerを継承したクラスを作る
まずはHandlerを継承したクラスを作りましょう。
class GUILogHandler(logging.Handler):
def __init__(self, window, level=logging.NOTSET):
logging.Handler.__init__(self, level=level)
self._window = window
def emit(self, record):
if len(record.msg) > 0:
msg = self.format(record)
else:
msg = ""
try:
js = "let l = document.getElementById('log_area');"
js += "let item = document.createElement('div');"
js += "item.classList.add('log_item');"
js += "item.classList.add('{0}');".format(record.levelname)
js += "item.textContent = '{0}';".format(escape_string(msg))
js += "l.appendChild(item);"
js += "item.scrollIntoView({behavior: 'smooth', block: 'end', inline: 'nearest'});"
self._window.evaluate_js(js)
except Exception:
self.handleError(record)
ここでは、emit()
というメソッドだけが、オーバーライド必要なメソッドとなります。
ログの出力処理などが行われた際、このメソッドが呼び出されますので、self.format()
メソッドを使用してフォーマッタを通したログを、出力するという処理を行います(このコードでは、PyWebViewを使って作成したUIの、log_area
というIDのdivに、ログを出力しようとしています)。
このフォーマッタを格納する領域は、Handlerの__init__()
で作成されています。このため、もし独自の初期化処理を行う場合は、必ずlogging.Handler.__init__(self, level=level)
として、スーパークラスの__init__()
を呼ぶ必要があります。
emit()
で引き渡されるrecord
パラメータの各値の意味
emit()
メソッドで引き渡されるrecord
には、ログメッセージだけでなく、様々なログ情報が含まれます。
すべてのリストは、公式のマニュアルに載っていますので、そちらを参照してみてください。
ざっくりと、良く使うであろうパラメータは以下の通り。
- asctime: 事象が発生したときの時刻。ミリ秒単位
- filename: ログ出力を行ったファイルの名前
- funcName: ログ出力を行った関数の名前
- levelname: ログレベル(DEBUG, INFOなどの値)
- message: ログのメッセージ
- processName: プロセス名
- threadName: スレッド名
ハンドラの追加と、余計なハンドラの削除について
このようにして作成したハンドラは、logger.addHandler()
メソッドで追加可能です。
import logging
# ...
logger = logging.getLogger(self.__class__.__name__)
logger.addHandler(GUILogHandler(window))
ただし、このようにしてLoggerに追加したハンドラは、logger内に登録されるため、上記のコードを二回以上実行するときなど、重複してハンドラが登録されてしまう場合があります。
そのような場合は、ロガーに登録されているハンドラを一度すべて消去する必要があります。
# ハンドラーリストを動的に書き換えてしまうことになるので、forで回すと上手くループできません。
while len(logger.handlers) > 0:
logger.removeHandler(logger.handlers[0])