📝

【Python】loggre ログ出力を、JSON形式で設定する

2021/12/30に公開

はじめに

ログ出力するHandler を複数設定する方法をまとめていきます。

やりたいことは、「コンソール出力・ファイル出力」を同時にログ出力することです !!

経緯

実運用するときは、ログ出力のパターンを複数設定する必要性があります。

異常検知で調査する時、意図した実行処理ができているか監視する時などです。

  • コンソール出力: StreamHandler
  • ファイル出力: FileHandler

他にも、SysLogHandler, SocketHandler, TimedRotatingFileHandler などがあるようです。

手順

公式ドキュメントを参考に、StreamHandler, FileHandler の handler を設定できるように調整します。

コードに直接記述する方法もありますが、今回は、設定ファイルで作成しています。

メリットとしては、呼び出し側のファイル名を変えるだけで、任意の設定ファイルを使えることですかね。

  1. 設定ファイルをログ出力したい設定に記述する
  2. 呼び出し側で、logger 名を指定
  3. ログが、意図した結果で出力できるか検証

詳細

フォーマット設定ファイル ( conf.json )
{
  "version": 1,
  "formatters": {                                 ... 1
    "detailed": {
      "class": "logging.Formatter",
      "format": "%(asctime)s %(name)-15s %(levelname)-13s %(message)s",
      "datefmt": "%Y-%m-%d %H:%M:%S"
    }
  },
  "handlers": {
    "console": {                                 ... 2
      "class": "logging.StreamHandler",
      "level": "INFO",
      "formatter": "detailed"
    },
    "file": {
      "class": "logging.FileHandler",
      "filename": "log/mplog.log",
      "mode": "a",
      "formatter": "detailed"
    },
    "testfile": {
      "class": "logging.FileHandler",
      "filename": "log/testfile.log",
      "level": "ERROR",
      "mode": "a",
      "formatter": "detailed"
    },
    "errors": {                                  ... 3
      "class": "logging.FileHandler",
      "filename": "log/mplog-errors.log",
      "mode": "a",
      "level": "ERROR",
      "formatter": "detailed"
     }
  },
  "loggers": {                                   ... 4
    "testfile": {
      "handlers": ["testfile"]
    }
  },
  "root": {                                                             ... 5
    "level": "DEBUG",
    "handlers": ["console", "file", "errors"]
  }
}

1. formatters : 出力するフォーマットを設定

  • detailed: 好きな名前をつけてformatter 名をつけれます

  • class: Formatter インスタンスを指定

  • format: ログ出力するフォーマットパターン

  • datefmt: %(asctime)s の表示を調整

      "formatters": {
        "detailed": {
          "class": "logging.Formatter",
          "format": "%(asctime)s %(name)-15s %(levelname)-13s %(message)s",
          "datefmt": "%Y-%m-%d %H:%M:%S"
        }
    

2. handlers: 出力する項目を設定( StreamHandler )

  • console: 好きな handler 名

  • level: 出力レベルを設定

  • formatter: formatters で指定したい フォーマット名

      "handlers": {
        "console": {
          "class": "logging.StreamHandler",
          "level": "INFO",
          "formatter": "detailed"
        },
    

3. handlers: 出力する項目を設定 ( FileHandler )

  • errors: 好きな handler 名

  • filename: <ディレクトリパス>/< ファイル出力名>

  • mode: 書き込むモード w: 新規書込み、a: 追記 など

    "errors": {
        "class": "logging.FileHandler",
        "filename": "log/mplog-errors.log",
        "mode": "a",
        "level": "ERROR",
        "formatter": "detailed"
       }
    

4. loggers: logger 名に指定

logger 名を 指定すると、出力反映されます。以下の設定だと testfile の handler が選択されます。

  • testfile: 好きな名前をつける

  • handlers: handlers でつけた名前の設定を指定

      "loggers": {
        "testfile": {
          "handlers": ["testfile"]
        }
    

    呼び出し側の logging で指定することで、"testfile" が採用されます

    👇こんな感じ

    import logging
    
    logger = logging.getLogger("testfile")
    

5. root: 設定

  • handlers に ログ出力したい対象名を、リスト格納することで、常にログ出力に反映されます

      "root": {
        "level": "DEBUG",
        "handlers": ["console", "file", "errors"]
      }
    

ログ生成・出力

main() で呼び出し処理を書いています。細かい説明は割愛します。

main.py
import configparser
import os
import json
import logging

import logging.config

# 実行ファイル名を取得
_EXEC_FILE_NAME = os.path.basename(__file__)[:-3]

def read_conf_file(conf_file='conf/conf.json'):
    """ 設定ファイルの読み込む """
    with open(conf_file, 'r', encoding='utf-8') as f:
        f_ = json.load(f)
        logging.config.dictConfig(f_)

def get_logger(logger_='simpleDefault'):
    """ ロガー生成 """
    return logging.getLogger(logger_)

def set_conf(conf_file='conf/msg_format.ini'):
    """ Read the format file you set. """
    conf = configparser.ConfigParser()
    conf.read(conf_file)
    return conf

def get_conf(conf, sec: str, option: str, format1=None, format2=None):
    """ Get the section name and key. """
    conf = conf.get(section=sec, option=option)
    return conf.format(format1, format2)

def main():
    """ 実行ファイル """

    # formater ファイル呼び出し
    read_conf_file(conf_file='conf/conf.json')

    # logger 名を設定
    logger = get_logger(logger_=_EXEC_FILE_NAME)

    # メッセージ一覧設定ファイルの読み込み
    set_conf_ = set_conf(conf_file='conf/msg_format.ini')

    # ------------------------
    # ログ出力
    # ------------------------

    # Debug
   msg1 = get_conf(conf=set_conf_, sec='message', option='DAP0001', format1=[1, 2, 3], format2=[1, 2, 3])
    logger.debug(msg1)

    # Info
    msg2 = get_conf(conf=set_conf_, sec='message', option='IAP0001', format1=[1, 2, 3], format2=[1, 2, 3])
    logger.info(msg2)

    # Warning
    msg3 = get_conf(conf=set_conf_, sec='message', option='WAP0001', format1=[1, 2, 3], format2=[1, 2, 3])
    logger.warning(msg3)

    # Error
    msg4 = get_conf(conf=set_conf_, sec='message', option='EAP0001', format1=[1, 2, 3], format2=[1, 2, 3])
    logger.error(msg4)

    # Critical
    msg5 = get_conf(conf=set_conf_, sec='message', option='CAP0001', format1=[1, 2, 3], format2=[1, 2, 3])
    logger.critical(msg5)

if __name__ == '__main__':
    main()

  • ログ出力. メッセージ一覧の設定ファイル
msg_format.ini
[message]
DAP0001=Debug Message user_id: {0[0]} tr_id: {0[1]} param_id: {0[2]} err_id: {1[0]}
DAP0002=Debug Message user_id: {0[0]} tr_id: {0[1]} param_id: {0[2]}
DAP0003=Debug Message user_id: {0[0]} tr_id: {0[1]}

IAP0001=Info Message user_id: {0[0]} tr_id: {0[1]} param_id: {0[2]} err_id: {1[0]}
IAP0002=Info Message user_id: {0[0]} tr_id: {0[1]} param_id: {0[2]}
IAP0003=Info Message user_id: {0[0]} tr_id: {0[1]}

WAP0001=Warning Message user_id: {0[0]} tr_id: {0[1]} param_id: {0[2]} err_id: {1[0]}
WAP0002=Warning Message user_id: {0[0]} tr_id: {0[1]} param_id: {0[2]}
WAP0003=Warning Message user_id: {0[0]} tr_id: {0[1]}

EAP0001=Error Message user_id: {0[0]} tr_id: {0[1]} param_id: {0[2]} err_id: {1[0]}
EAP0002=Error Message user_id: {0[0]} tr_id: {0[1]} param_id: {0[2]}
EAP0003=Error Message user_id: {0[0]} tr_id: {0[1]}

CAP0001=Critical Message user_id: {0[0]} tr_id: {0[1]} param_id: {0[2]} err_id: {1[0]}
CAP0002=Critical Message user_id: {0[0]} tr_id: {0[1]} param_id: {0[2]}
CAP0003=Critical Message user_id: {0[0]} tr_id: {0[1]}

実行結果

やりたい事ができているようです!

console: INFO レベル以上をコンソール出力

2021-12-11 20:12:40 format          INFO          Info Message user_id: 1 tr_id: 2 param_id: 3 err_id: 1
2021-12-11 20:12:40 format          WARNING       Warning Message user_id: 1 tr_id: 2 param_id: 3 err_id: 1
2021-12-11 20:12:40 format          ERROR         Error Message user_id: 1 tr_id: 2 param_id: 3 err_id: 1
2021-12-11 20:12:40 format          CRITICAL      Critical Message user_id: 1 tr_id: 2 param_id: 3 err_id: 1

mplog.log: DEBUG レベル以上をファイル出力

2021-12-11 20:12:40 format          DEBUG         Debug Message user_id: 1 tr_id: 2 param_id: 3 err_id: 1
2021-12-11 20:12:40 format          INFO          Info Message user_id: 1 tr_id: 2 param_id: 3 err_id: 1
2021-12-11 20:12:40 format          WARNING       Warning Message user_id: 1 tr_id: 2 param_id: 3 err_id: 1
2021-12-11 20:12:40 format          ERROR         Error Message user_id: 1 tr_id: 2 param_id: 3 err_id: 1
2021-12-11 20:12:40 format          CRITICAL      Critical Message user_id: 1 tr_id: 2 param_id: 3 err_id: 1

mplog-error.log: ERROR 以上をファイル出力

2021-12-11 20:12:40 format          ERROR         Error Message user_id: 1 tr_id: 2 param_id: 3 err_id: 1
2021-12-11 20:12:40 format          CRITICAL      Critical Message user_id: 1 tr_id: 2 param_id: 3 err_id: 1

まとめ

今回でログ出力設定に関しては理解できたかなっと思います。
後は、以下を進めていくと良さそうです!!

  • クラス化して使いやすく改変
  • 運用しやすいように的確な箇所に、ログ出力を設定

以上になります。ありがとうございました!

参考

GitHubで編集を提案

Discussion