ryotankの備考録日記

趣味の電子工作についての備考録などなど

シリアル通信GUIその6

Pythonがログを出力する仕組みってどんな感じ??

グレコードとは?(logging.LogRecord)
ログ出力されるメッセージそのものの情報をもつもの
ロガー間でやり取りされるものはログレコード
詳しく説明するとログのメッセージ本文や
グレコードの生成時刻、設定されたログレベル、
呼び出されたソースのファイル名、ソース中の行番号、
スタックトレースの情報を持っていて、自動的に記録される

重要なポイントとしてログレコードを生成するのは
ロガーの役目、出力するのは、ハンドラーの役目

ロガーとは
グレコードを生成したり、他のロガーに受け渡す
受け取ったログレコードをハンドラに渡す

ハンドラとは
ロガーにひもづけられ、受け取ったログレコードを出力する

フィルタとは
ロガーやハンドラにひもづけられ受け取ったレコードを
処理するか否かを決定する


フォーマッター:ハンドラにひもづけられ、
ハンドラが受け取ったログレコードをフォーマット(整形)する役割を持つ
フォーマッターのコンストラクタにフォーマット文字列を
渡すことでログレコードに含まれる時刻とメッセージの書式設定を
変更する事が出来る


ロガーは実際にロガーにひもづけられたハンドラー(logging.Handler)。
ロガーは各開発者が自分で定義・生成することができます。ロガーはそれぞれ名前(Logger.name)を持ち、
各開発者がロガーを生成するときに、そのモジュール名を
名前に設定することが一般的。

# getLogger関数の第一引数に設定された文字列が、そのロガーの名前になる
logger = logging.getLogger(__name__)

print(__name__)
print(logger.name)

ハンドラーは、ログレコードを出力する

import logging
import sys

logger = logging.getLogger(__name__)

# 標準出力(コンソール)にログを出力するハンドラを生成する
sh = logging.StreamHandler(sys.stdout)
sh.setLevel(logging.WARNING)

# ハンドラをロガーに紐づける
logger.addHandler(sh)

logger.warning("unauthorized.")

出力例

unauthorized.


デフォルトのフォーマッタはテキスト形式の出力を想定したものですが、
logging.Formatterクラスを継承して独自のフォーマッタを作成し、
JSON形式でログ出力をすることなども可能です。※1

import logging
import sys

logger = logging.getLogger(__name__)
sh = logging.StreamHandler(sys.stdout)
sh.setLevel(logging.WARNING)

# フォーマッタを定義する(第一引数はメッセージのフォーマット文字列、
#第二引数は日付時刻のフォーマット文字列)
fmt = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s", "%Y-%m-%dT%H:%M:%S")

# フォーマッタをハンドラに紐づける
sh.setFormatter(fmt)

logger.addHandler(sh)
logger.warning("unauthorized.")
2021-02-17T00:05:13 - __main__ - WARNING - unauthorized

フィルタ(logging.Filter)

ロガーやハンドラが、受け取ったログレコードを
処理するかしないかを決定する条件を定義します。

定義したフィルタは、ロガーやハンドラに紐づけて使用します。

フィルタオブジェクトは、これまで挙げたloggingモジュールのオブジェクトとは
異なり、デフォルトの状態では使われることはありません。
ログレベルだけではコントロールし切れないような、
任意の条件でログのフィルタリングを行いたい場合に
開発者が定義して使用することができます

フィルタサンプルコード

import logging
import sys

# 指定された文字列がメッセージに含まれている場合のみ処理させるフィルタの例
class TextFilter:
    def __init__(self, text=""):
        self.text = text

    # フィルタメソッド
    # 処理したい条件ではTrue、処理したくない条件ではFalseを返すような関数を作成。
  # 引数recordには、ログレコード(logging.LogRecord)が渡される。
    def filter(self, record):
        if self.text in record.getMessage():
            return True
        return False

logger = logging.getLogger(__name__)
sh = logging.StreamHandler(sys.stdout)
sh.setLevel(logging.WARNING)

# フィルタをハンドラに紐づける
sh.addFilter(TextFilter("internal server error"))

logger.addHandler(sh)
logger.warning("unauthorized.") # フィルター条件にマッチしないので出力されない。
logger.warning("internal server error.")

出力例

internal server error.

filter()メソッドを持つオブジェクトを渡すと、
グレコードをfilter()メソッドのパラメータに入れて呼んでくれます

つまりロガーが階層構造を持っていると
どのような利点があるかと言えば

【ログの出し分けがしやすい】事
「出し分け」は、出力先をファイルとコンソールで
分けたり、出力自体の有無の切り替えをしたりする事

次回は、シリアル通信にあったロガーの命名を行う