これは何?
先日、livedoorニュースコーパスをcsvファイル形式で取得するという記事で公開するスクリプトを書いた。
それに加え、最近ちょくちょくモジュールを自作する機会があり、それらの経験を通して学んだloggingについての覚え書き。
loggingを使うモチベーション
- 開発者側のモチベーション:
メンテナンスを考えてコードがちゃんと動いているか、問題があるとすればどこで問題が発生したかを追跡したい。 - ユーザー側のモチベーション:
実行結果のログをファイル出力したり、煩わしいログ表示をオフにしたりしたい。
このあたりの要望に応えるのが標準ライブラリのloggingだ、と私は認識している。(printだとユーザー側で表示レベルを制御したりはできない)
このloggingの使い方について、以下では開発者側の使い方・ユーザー側の使い方と分けて記載する。
開発者側とユーザー側
スクリプト内をif __name__ == ’__main__‘:というif文ブロックで開発者側とユーザー側を切り分けする。
ベタ書き部分が開発者側、if __name__ == ’__main__‘:ブロック内の処理がユーザー側になる。
汎用的な関数やクラスを定義しているのが開発者側で、引数を与えて関数やクラスを利用するのがユーザー側、と考えるとイメージしやすい。
補足:if __name__ == ’__main__について
このif __name__ == ’__main__‘:は、スクリプト実行時とimportされたときの挙動を分けるために存在する。たとえば、hoge.pyファイルをスクリプトとして実行(ターミナルからpython hoge.py)するとそのファイルの__name__属性は ’__main__‘という値を持つようになり、if __name__ == ’__main__‘:のブロックが実行される。一方、モジュールとしてimportするとモジュール名(ファイル名)のhogeという値を持つようになり、このブロックは実行されない。
参考記事:
logging:開発者側
開発者側はコードの実行結果を追跡するために、レベル別(DEBUG:詳細情報~CRITICAL:重大なエラー まで存在)のログメッセージをコード内に仕込んでおきたい。
なので、やることはログメッセージの仕込み。
logger = logging.getLogger(__name__)
モジュール開発者側のロガー(ログ出力用インスタンス)呼び出しはlogger = logging.getLogger(__name__)
と__name__を渡して名付けてやると良い。
こうするとユーザー側がモジュール毎に出力を制御できるようになる。
以下公式ドキュメントから引用。
ロガーに名前をつけるときの良い習慣は、ロギングを使う各モジュールに、以下のように名付けられた、モジュールレベルロガーを使うことです:
logger = logging.getLogger(__name__)
出典:https://docs.python.org/ja/3/howto/logging.html#logging-advanced-tutorial
先日公開したスクリプトはモジュールとしてimportされることは想定していないが、このロガーの呼び方が定型文みたいなものなのでそのまま使った。
logger.info 他
ロガーインスタンスを使って表示したいメッセージをコードに仕込む。
使い方は、log出力したい箇所にlogger.info(msg)
とかlogger.debug(msg)
とprint文のように挿入していくだけ。
ちなみに仕込むログのレベルはこんな感じで使い分ける。
レベル | いつ使うか |
DEBUG | おもに問題を診断するときにのみ関心があるような、詳細な情報。 |
INFO | 想定された通りのことが起こったことの確認。 |
WARNING | 想定外のことが起こった、または問題が近く起こりそうである (例えば、‘disk space low’) ことの表示。 |
ERROR | より重大な問題により、ソフトウェアがある機能を実行できないこと。 |
CRITICAL | プログラム自体が実行を続けられないことを表す、重大なエラー。 |
logging:ユーザー側
ユーザー側はログの出力を制御したいので、やることは出力制御。出力レベルや、出力フォーマット、出力先を定義する。
logger = logging.getlogger(name_mod)
ユーザー側でもロガーインスタンスを呼び出す1。
先日公開したスクリプトはモジュール毎に分けて表示制御しないので、モジュール名を与えずインスタンスを呼び出し、rootLoggerと命名している。
モジュール毎に表示をコントロールする方法は参考記事の内容を参照。
参考記事:
logger.setlevel
ログの出力レベルを制御できる。
使い方としては、logger.setLevel(logging.HOGE)
としてHOGEレベル以上の出力をするように設定する。
先日公開したスクリプトでは正常終了を示すINFOレベル以上を表示する形にしている。
logging.Formatter
ログで表示されるメッセージの出力形式を制御できる。
LogRecord 属性を使って色々出力内容をコントロールできるが、正直私は一度用意したものを使いまわしている。
使い方としてはins_fmt = logging.Formatter(fmt)
でフォーマッターインスタンスを作成して、ロガーのsetFormatterメソッドに渡し(logger.setFormatter(ins_fmt)
)としてロガーに設定を反映する。
logging.StreamHandler / FileHandler
ログの出力先を制御できる。
設定できる出力先はたくさんあるので公式を参照。
使い方としてはins_hdl = logging.HogeHandler()
でハンドラーインスタンスを作成して、必要に応じて出力レベルやフォーマットを変更後、ロガーのaddHandlerメソッドに渡し(logger.addHandler(ins_hdl)
)としてロガーに設定を反映する。
先日公開したスクリプトではコンソールへの表示+コマンドラインからログファイルパスを指定した場合のみ、ファイルへの出力としている。
感想
一般公開するスクリプトをはじめて書いてみたけど、今までなぁなぁの理解で済ませていたことを調べ直す機会になっていいですね。
-
先日公開したスクリプトでは開発者側がヘルパー関数としてget_loggerを定義してユーザー側に使わせている(if __name__ == ’__main__‘:ブロック内で関数を呼び出している)が、基本はユーザー側がこの関数の内容の出力制御をするものだと思ってください。
↩