pythonのloggingが便利だと知った話

# 記事の対象者
- printでlogを出している
- pythonの標準モジュールloggingを使ってみたい

# printでのlog出力
例えばログに以下の内容を含めたいとします
- logレベル(debug, info, warn, error, critical)
- 時間
- モジュール名
- 関数名
- 任意のメッセージ

printを使った場合、以下のような関数実装になるかなと思います
```
from datetime import datetime as dt

def print_log(log_level: str, module_name: str, func_name: str, message: Optional[str] = None):
    """printでlogを出す関数

    Args:
        log_level (str): logレベル
        module_name (str): モジュール名
        func_name (str): 関数名
        message (Optional[str]): logに出力したいメッセージ
    """
    if message is not None:
        print("{time} {level} {name} {func} {meg}".format(time=dt.now().strftime('%Y/%m/%d %H:%M:%S'), level=log_level, name=module_name, func=func_name, meg=message))
        return
    
    print("{time} {level} {name} {func}".format(time=dt.now().strftime('%Y/%m/%d %H:%M:%S'), level=log_level, name=module_name, func=func_name))

```

↑の関数を使う場合
```
import print_log

def print_log_run():
    print_log('info', __name__, print_log_run.__name__, 'test')


if __name__ == '__main__':
    print_log_run()

# 結果
2024/06/29 10:43:52 info __main__ print_log_run test
```

出せてはいますが引数多いし、なんか汚いですよね。
messageを工夫したい時もさらに関数が複雑になりそうな予感が現段階でします。
さらにlogfileを出力したいと言われたら、更に複雑な関数になりそうです。

# loggingでのlog出力
この問題を簡単に解決できるのがpythonの標準モジュールである「logging 」です。
使い方は更新サイトのものを載せます
```
import logging
logger = logging.getLogger(__name__)
logging.basicConfig(filename='example.log', encoding='utf-8', level=logging.DEBUG)
logger.debug('This message should go to the log file')
logger.info('So should this')
logger.warning('And this, too')
logger.error('And non-ASCII stuff, too, like Øresund and Malmö')

# 以下のような出力となる
DEBUG:__main__:This message should go to the log file
INFO:__main__:So should this
WARNING:__main__:And this, too
ERROR:__main__:And non-ASCII stuff, too, like Øresund and Malmö
```

loggingの使い方は更新サイトや分かりやすく記事があるので紹介します
- [公式サイト](https://docs.python.org/ja/3/howto/logging.html)
- [とほほのPython入門 - ロギング](https://www.tohoho-web.com/python/logging.html)
- [Pythonでprintを卒業してログ出力をいい感じにする](https://qiita.com/FukuharaYohei/items/92795107032c8c0bfd19)

# オレオレlogging関数
loggingをいい感じに使える関数を作ったので載せます。

↓オレオレlogging関数
```
import logging

def get_logger(name: str, log_level: str, log_export_path: str):
    """ロガーの取得

    Args:
        name (str): モジュール名
        log_level (str): ログレベル
        log_export_path (str): ログの出力先(名前も含む)

    Returns:
        _type_: loggingクラス
    """
    if(log_level == 'debug'):
        level = logging.DEBUG
    else:
        level = logging.INFO
        
    logging.basicConfig(level=level)
    logger = logging.getLogger(name=name)
    handler = logging.FileHandler(log_export_path)
    handler.setLevel(level=level)

    format = '%(asctime)s %(levelname)s [%(name)s] %(funcName)s %(message)s'
    formatter = logging.Formatter(format)
    
    handler.setFormatter(formatter)
    logger.addHandler(handler)

    return logger
```

関数の他にもget_message関数やmessageListを定義しておくとlog messageも簡単に設定することができます

```
message_list = {
    'TEST_01': 'Start Process {0}',
    'TEST_02': 'End Process',
    'TEST_03': 'Fail {}'
}

def get_message(log_id: str, *args: str):
    """configのログのmessageリストからメッセージを取得する

    Args:
        log_id (str): log_id -> message.pyを参照
        *args (str): logに出す可変長変数

    Returns:
        _type_: str
    """
    log_message = message_list[log_id]

    if(len(args) == 0):
        return log_message

    return log_message.format(*args)

```

↑を使用する場合
```
import get_logger, get_message

def logger_run():
    logger = get_logger(name=__name__, log_level='info', log_export_path='./log/test_log.log')
    logger.info(msg=get_message('TEST_01', 'test'))
    logger.info(msg=get_message('TEST_02'))

if __name__ == '__main__':
    logger_run()
```
log/test_log.logには以下のような形式で出力されている
```
2024-06-29 10:09:10,736 INFO run [__main__] Start Process test
2024-06-29 10:09:10,736 INFO run [__main__] End Process
```