【Python】loggerに渡した文字列を関数で変更してみた

Pythonでちょっと高級なAppを作るときに使用するlogger
loggerに渡した文字列に細工をしたいことありませんか？

今回はloggerにわたす文字列を加工して出力するloggerを作成しました

# コード
```Python

from logging import LogRecord, Logger, Formatter, StreamHandler, FileHandler, DEBUG, INFO

def change_message(s: str) -> str:
    if s == "\\\n":
        return "\n\n\n\n\n"

    s = s.upper().replace("\n", "\t")

    if "CAT" in s:
        s += "ฅ^•ω•^ฅﾆｬｰ"
    return s

class OriginalFormatter(Formatter):
    def format(self, record: LogRecord) -> str:
        record.message = change_message(record.getMessage())
        if self.usesTime():
            record.asctime = self.formatTime(record, self.datefmt)
        s = self.formatMessage(record)

        if record.exc_info:
            # Cache the traceback text to avoid converting it multiple times
            # (it's constant anyway)
            if not record.exc_text:
                record.exc_text = self.formatException(record.exc_info)
        if record.exc_text:
            if s[-1:] != "\n":
                s = s + "\n"
            s = s + record.exc_text
        if record.stack_info:
            if s[-1:] != "\n":
                s = s + "\n"
            s = s + self.formatStack(record.stack_info)
        return s

def init_logger(logger: Logger, file_name: str) -> Logger:
    log_format = OriginalFormatter("%(asctime)s %(name)s:%(lineno)s %(funcName)s [%(levelname)s]: %(message)s")

    st = StreamHandler()
    st.setFormatter(log_format)
    st.setLevel(INFO)

    fl = FileHandler(file_name)
    fl.setFormatter(log_format)
    fl.setLevel(DEBUG)

    logger.setLevel(DEBUG)
    logger.addHandler(st)
    logger.addHandler(fl)

    return logger
```

## change_message関数
ここに任意の文字列を加工する関数を書いてください

## OriginalFormatterクラス
loggerのFormat関数にchange_message関数をいれています

## init_logger関数
ここでOriginalFormatterを用いloggerの設定をしています

# お試し
```Python
if __name__=="__main__":
    from logging import getLogger
    import traceback
    logger = getLogger(__name__)
    logger = init_logger(logger, "test.log")

    logger.debug("logger debug")
    logger.info("logger info")
    # 2023-08-03 12:22:42,819 __main__:59 <module> [INFO]: LOGGER INFO
    logger.warning("logger warn")
    # 2023-08-03 12:22:42,819 __main__:60 <module> [WARNING]: LOGGER WARN
    logger.error("logger error")
    # 2023-08-03 12:22:42,819 __main__:61 <module> [ERROR]: LOGGER ERROR

```

loggerの設定と、実際に出力したものです
今回の関数は小文字を大文字にする機能がありますが、適切に変化しています


```Python
    try:
        raise("raise!")
    except:
        logger.exception("logger exception", exc_info=True) # traceback に関数を適用しない
        logger.error("except log\n" + traceback.format_exc()) # 適用する

    # 2023-08-03 12:22:42,819 __main__:66 <module> [ERROR]: LOGGER EXCEPTION
    # Traceback (most recent call last):
    #   File "/Users/~~~~~/original_logger.py", line 64, in <module>
    #     raise("raise!")
    # TypeError: exceptions must derive from BaseException
    # 2023-08-03 12:22:42,819 __main__:67 <module> [ERROR]: EXCEPT LOG        TRACEBACK (MOST RECENT CALL LAST):        FILE "/USERS/~~~~~/ORIGINAL_LOGGER.PY", LINE 64, IN <MODULE>       RAISE("RAISE!")     TYPEERROR: EXCEPTIONS MUST DERIVE FROM BASEEXCEPTION
```

logger.exceptionによって traceback を出力すると、traceback に関数は適用されません
下の方法だと適用されています
ここでも、大文字にする機能と、改行をタブに変換する機能が動作しているのを確認できます

```Python
    def test(logger: Logger):
        logger.debug("logger debug in test")
        logger.info("logger info in test")
        logger.warning("logger warn in test")
        logger.error("logger error in test")

        try:
            raise("raise in test!")
        except:
            logger.exception("logger exception in test", exc_info=True)
            logger.error("except log in test\n" + traceback.format_exc())
    
    test(logger)

    # 2023-08-03 12:22:42,820 __main__:71 test [INFO]: LOGGER INFO IN TEST
    # 2023-08-03 12:22:42,820 __main__:72 test [WARNING]: LOGGER WARN IN TEST
    # 2023-08-03 12:22:42,820 __main__:73 test [ERROR]: LOGGER ERROR IN TEST
    # 2023-08-03 12:22:42,820 __main__:78 test [ERROR]: LOGGER EXCEPTION IN TEST
    # Traceback (most recent call last):
    #   File "/Users/~~~~~/original_logger.py", line 76, in test
    #     raise("raise in test!")
    # TypeError: exceptions must derive from BaseException
    # 2023-08-03 12:22:42,820 __main__:79 test [ERROR]: EXCEPT LOG IN TEST    TRACEBACK (MOST RECENT CALL LAST):        FILE "/USERS/~~~~~/ORIGINAL_LOGGER.PY", LINE 76, IN TEST           RAISE("RAISE IN TEST!")     TYPEERROR: EXCEPTIONS MUST DERIVE FROM BASEEXCEPTION
```

関数内でも適切に動いています

```Python
    logger.info("\\\n")
    logger.info("cat")

    # 2023-08-03 12:22:42,820 __main__:83 <module> [INFO]: 





    # 2023-08-03 12:22:42,820 __main__:84 <module> [INFO]: CATฅ^•ω•^ฅﾆｬｰ
```

\\\\\nを改行5回に変換する機能も無事に動いています
cat | CAT という文字列があったとき、末尾にねこを追加する機能も動作しているようですฅ^•ω•^ฅﾆｬｰ

## ファイルハンドラ
今回は省略しますが、FileHandlerでも適切に関数が適用されていました
もし、FileとStreamで別々の関数を適用したい場合は、2つFormatterを用意すればよいと思います

# まとめ
これでloggerを便利に活用できます！！

# 参考文献
Pythonでprintを卒業してログ出力をいい感じにする - Qiita
https://qiita.com/FukuharaYohei/items/92795107032c8c0bfd19
[Tips] Pythonのロガーでユーザーが定義したキーを使って値を出力する方法 | DevelopersIO
https://dev.classmethod.jp/articles/python-logger-kwarg-extra/