【Python3】開発で使えるシリーズ　その②　ログメッセージ

# テーマ：ログメッセージ
```
【ポイント】
1. IDからメッセージを取得
2. メッセージに値を埋め込むことが可能
3. メッセージに値を辞書形式で埋め込むことが可能
```

# ディレクトリ
```
common/
  └ __init__.py
  └ message/
       └ message.py
       └ log_message.csv

tools/
  └ __init__.py
  └ test.py

settings/
  └ __init__.py
  └ settings.py
```

# message/log_message.csv
```
ID,MESSAGE
D10001,{}処理-START
D10002,{}処理-END
I10001,{}処理-START
I10002,{}処理-END
W10001,{}バージョン以降、使用できなくなります。
E10001,予期せぬエラーが発生しました。{}
I10003,{}第一引数[{param1}]/第二引数[{param2}]を見直してください。
I10004,名前は {last_name} {first_name}. {first_name} {last_name}
```

# settings/settings.py
```
# メッセージファイルの管理場所を変更したい場合、定義する
MESSAGE_DIR_PATH = None
```

# message/message.py
```
import os

import pandas as pd


class MessageNotFoundError(Exception):
    def __init__(self, message_id):
        self.message_id = message_id

    def __str__(self):
        return 'メッセージIDが存在しません。[{}]'.format(self.message_id)


class Message(object):
    _message_file_name = 'log_message.csv'

    @classmethod
    def _get_message_file_dir_path(cls):
        """メッセージファイルの絶対パスを取得

        Args:
            None
        Returns:
            file_path: (str): メッセージファイルが存在する絶対パス
        Raise:
            ImportError: setting.pyが作成されていない場合
            AttributeError: setting.pyにMESSAGE_DIR_PATHAが定義されていない場合
            FileNotFoundError: メッセージファイルが存在しない場合
        """
        file_dir_path = None

        try:
            from settings import settings
            if settings.MESSAGE_DIR_PATHA:
                file_dir_path = settings.MESSAGE_DIR_PATH
        except ImportError:
            pass
        except AttributeError:
            pass

        if not file_dir_path:
            file_dir_path = os.path.dirname(os.path.abspath(__file__))

        file_path = os.path.join(file_dir_path, cls._message_file_name)
        if not os.path.exists(file_path):
            raise FileNotFoundError
        return file_path

    @classmethod
    def search_message(cls, message_id):
        """メッセージファイルからmessage_idを探す

        Args:
            message_id: (str): メッセージID
        Resturns:
            message: (str): メッセージIDに紐づくテキスト
        Raise:
            MessageNotFoundError: メッセージIDがメッセージファイルに存在しない場合
        Example:
            >>> # 存在するメッセージIDを指定
            >>> m =Message
            >>> m.search_message('D10001')
            '{}処理-START'

            >>> # 存在しないメッセージを指定
            >>> m.search_message('hogehoge')
            Traceback (most recent call last):
            ...
            MessageNotFoundError: メッセージIDが存在しません。[hogehoge]

        """
        file_path = cls._get_message_file_dir_path()
        messages = pd.read_csv(file_path)
        if message_id not in messages.values:
            raise MessageNotFoundError(message_id)

        row = messages[messages.ID == message_id].MESSAGE
        message = row[row.index[0]]

        del messages
        return message

    @classmethod
    def action_log_message(cls, message_id, *args, **kwargs):
        """ログメッセージを作成する
           引数(*args)が存在する場合、メッセージに引数の値を埋め込む

        Args:
            message_id: (str): log_message.csvで管理されているメッセージID
            *args: (tuple): メッセージに埋め込む用の引数
            **kwargs: (dict): キーバリューで渡ってくるメッセージ埋め込む用の引数
        Returns:
            log_message: (str): ログに出力するメッセージ
        Example:
            >>> m = Message
            >>> # D10001,{}処理-START
            >>> m.action_log_message('D10001', 'Hoge')
            'Hoge処理-START'

            >>> # I10004,名前は {last_name} {first_name}. {first_name} {last_name}
            >>> m.action_log_message('I10004', last_name='Taro', first_name='Tanaka')
            '名前は Taro Tanaka. Tanaka Taro'

            >>> # I10003,{}第一引数[{param1}]/第二引数[{param2}]を見直してください。
            >>> m.action_log_message('I10003', '引数チェックエラー。', param1='1', param2='2')
            '引数チェックエラー。第一引数[1]/第二引数[2]を見直してください。'

        """
        message = cls.search_message(message_id)
        if len(args) > 0 and len(kwargs) > 0:
            message = message.format(*args, **kwargs)
        elif len(args) > 0:
            message = message.format(*args)
        elif len(kwargs) > 0:
            message = message.format(**kwargs)

        return message


if __name__ == '__main__':
    import doctest
    doctest.testmod()
```

# common/__init__.py
```
from common.message.message import MessageNotFoundError
from common.message.message import Message as m
```

# tools/test.py
```
import logging
from common import m

formatter = '%(asctime)s %(processName)s %(threadName)s %(message)s'
logging.basicConfig(level=logging.DEBUG, format=formatter)

# D10001,{}処理-START
logging.debug(m.action_log_message('I10001', '○○'))
>>> 2019-09-14 00:59:25,559 MainProcess MainThread ○○処理-START

```