Pythonでログ出力用プログラムを作ってみた

標準ライブラリのloggingを使ってログを管理できる汎用的なプログラムを作ってみました。プログラム名はapp_logging.pyとしておきます。
目標としては、各プログラム内でいちいちloggingモジュールをimportして設定を変えてといったことをせずに、app_logging.pyをimportすることで、全プログラムに共通で使用できるログ管理プログラムを用意することです。

loggingレベルには以下の6つがデフォルトで存在しています。

レベル:数値
CRITICAL:50
ERROR:40
WARNING:30
INFO:20
DEBUG:10
NOTSET:0

各レベルの用途はどうやら厳密に決まってるわけではないらしいので、独自のルールでレベル分けしてよいようです。(逆に言うと、きちんと設計しないと訳が分からなくなる。)ここでは以下のようなレベル分けを定義したいと思います。

CRITICAL:使わない。(使う用途が今のところ見当たらない)
ERROR:エラー発生時に、エラーメッセージを出力するとき。
WARNING:エラーではないが想定した結果ではないとき。
INFO:各処理の開始・終了情報を出力するとき。
DEBUG:変数の値や読み込みファイルの中身に関する情報を出力するとき。
NOTSET:使わない。

正直、網羅できている気はしないが、使っていくうちに洗練させていく予定。上記の定義はdocstringに記載しておけばソースコードだけで管理できます。以下サンプルコード。

import logging
import datetime

class App_log:
   """
   このクラスのインスタンスを作成すると、
   名前付きのlogging.Loggerインスタンスが作成され、
   指定したファイルにログを出力できる。
   """
   logger = None
   def __init__(self,module_name,isLibrary=False,isTest=False):
       """
       module_name:str型、pythonのプログラム名を想定。
       is_library:bool型、defaultはfalse、ライブラリモジュールの時はTrueにする。
       """
       dt = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
       if not isLibrary:
           logging.basicConfig(
               level=logging.DEBUG,
               format='[%(levelname)s] %(name)s %(asctime)s %(message)s',
               filename="/pywork/logs/{module_name}_{dt}.log".format(module_name=module_name,dt=dt)
           )
       self.logger = self._get_module_logger(module_name,isTest)

   def _get_module_logger(self,module_name,isTest):
       """
       ret:logging.Logger
       handlerの種類は2つ
       error_handler:warning以上のレベルのログを全プログラム共通のファイルに出力する。
       stream_handler:すべてのレベルのログをコマンドライン上に出力する。
       """
       logger = logging.getLogger(module_name)
       day = datetime.date.today().strftime("%Y%m%d")
       error_handler = logging.FileHandler(filename="/pywork/logs/aplog_{day}.log".format(day=day))
       #handlerにログレベルを指定する。
       error_handler.setLevel("WARNING")
       #loggerインスタンスにhandlerを追加する。
       if isTest:
           stream_handler = logging.StreamHandler()
           logger.addHandler(stream_handler)
       logger.addHandler(error_handler)
       return logger

   def debug_log(self,msg):
       """
       変数の値などをログに残す際に使う。
       """
       self.logger.debug(msg)
   
   def info_log(self,msg):
       """
       各処理の開始や終了の際に使う。
       """
       self.logger.info(msg)
   
   def warning_log(self,msg):
       """
       エラーではないが期待した処理結果でない可能性があるときに使う。
       """
       self.logger.warning(msg)
   
   def error_log(self,msg):
       """
       例外が発生したときに使う。
       except節の中でのみ使う。
       """
       self.logger.exception(msg)

詰まったこと
①Pythonチュートリアルに書かれているloggingの使い方が、推奨されるべき使い方と異なっていたこと。
②Logging HOWTOの基本 logging チュートリアルも①と同様の使い方を示していたこと。(初心者は普通基本だけ読んで使うじゃん......)

工夫ポイント
①個人作成ライブラリでもそれをインポートした親プログラムでも使えるようにしたかった。ファイルは親プログラムの名前で作られるようにしたかった。そこでislibrary引数を作って、その値の応じて親プログラムの時だけログファイルの指定をした。
②テスト中はいちいちログファイルを見に行くより、コマンドライン上で見たいという希望に添えるように、isTest引数を作った。

以下、現時点での私の理解をまとめておく。間違っていたらすいません。
①logging.debug()などを使うと、すべてrootでログを管理することになってしまい、ログの管理で本来あるべき、プログラム単位でログを管理するありかたを実装できない。
②よって、プログラムごとにLoggerクラスのインスタンスを作り、そこでログを個別に管理すればプログラム単位でログを管理することができる。Loggerクラスのインスタンスはlogging.getLogger()で作成しなければならない。
③loggerインスタンスにはhandlerを複数追加することができる。このhandler単位でログを出力する先(ファイルのパスやコマンドライン上)を指定したり、ロギングのレベル、フォーマットを指定できる。

一応、当初の目標は達成できたかなと思います。

おかしいところ、作りがいまいちなところあったら指摘してくれると喜びます。

以下参考文献


この記事が気に入ったらサポートをしてみませんか?