Python, logging での loop 処理、注意事項と実運用

# log 難しかった (∩´﹏`∩)
## 基本

```python:main.py
import logging
import os

from my_modules import my_func1, my_func2


if __name__ == '__main__':
    save_dir = '/mnt/c/Users/me/repo/my_repo'

    logging.basicConfig(level=logging.DEBUG,
                        format="%(asctime)s %(levelname)s %(name)s %(message)s",
                        datefmt="[%X]",
                        handlers=[logging.FileHandler(filename=os.path.join(save_dir, "log.txt"), mode="w")])

    logger = logging.getLogger(__name__)
    logger.debug("aaa")
    logger.info("bbb")

    my_func1()
    my_func2()
```

```python:my_modules
from logging import getLogger


logger = getLogger("__main__").getChild("my_modules")

def my_func1():
    logger.info("ccc")

def my_func2():
    logger.info("ddd")
```

## loop 毎に別の log.txt 作りたい
前提: **basicConfig() は１回しか使っちゃダメ**

```python:loop_log_example.py
import logging
import os

save_dir = '/mnt/c/Users/me/repo/my_repo'
os.makedirs(save_dir, exist_ok=True)

# basicConfig で全体のフォーマット・レベル設定
# 絶対１回だけ！loop 内に書かないで！
logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s %(levelname)s %(message)s",
    datefmt="[%X]",
    )

logger = logging.getLogger("my_logger")

for i in range(3):
    log_path = os.path.join(save_dir, f"log_{i}.txt")
    handler = logging.FileHandler(log_path, mode="w")
    formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s", datefmt="[%X]")
    handler.setFormatter(formatter)
    logger.addHandler(handler)
    logger.info(f"{i}回目のログ！")
    logger.removeHandler(handler)
    handler.close()    # 忘れずにクローズしておく
```

とか

## 今回作ったやーつ

### Archtecture
- **my_repo**
    - **config**
        - config.yaml
    - **src**
        - \_\_init\_\_.py
        - main.py
            - **modules**
                - \_\_init\_\_.py
                - config.py (config 読むためのファイル (内容は割愛))
                - my_function.py

### 各ファイルの具体的な内容

```yaml:config.yaml
modes:
    mode1:
        input_dir: /mnt/c/Users/my_name/my_repo/dataset/original/mode1
        save_dir: /mnt/c/Users/my_name/my_repo/dataset/preprocess/mode1
    mode2:
        input_dir: /mnt/c/Users/my_name/my_repo/dataset/original/mode2
        save_dir: /mnt/c/Users/my_name/my_repo/dataset/preprocess/mode2
```

```python:main.py
import argparse
import logging
import os

from modules import func


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='')
    parser.add_argument('--config', dest='config_path',
                        action='store', required=True,
                        help='path to the config.yaml')
    args = parser.parse_args()

    config_path = args.config_path

    config = load_config(config_path)

    modes = config['modes']

    logging.basicConfig(level=logging.DEBUG,
                        format="%(asctime)s %(levelname)s %(name)s %(message)s",
                        datefmt="[%X]"
                        )

    for mode in modes:
        input_dir = modes[mode]['input_dir']
        save_dir = modes[mode]['save_dir']

        os.makedirs(save_dir, exist_ok=True)

        logger = logging.getLogger(__name__)
        logger.setLevel(logging.DEBUG)
        if logger.hasHandlers():    # もし古い handler が残っていたら
            logger.handlers.clear()    # 削除する
        handler = logging.FileHandler(filename=os.path.join(save_dir, "log.txt"), mode="w")
        handler.setLevel(logging.DEBUG)
        formatter = logging.Formatter("%(asctime)s %(levelname)s %(name)s %(message)s", datefmt="[%X]")
        handler.setFormatter(formatter)
        logger.addHandler(handler)

        logger.info(f"mode: {mode}")

        input_files = sorted(glob.glob(os.path.join(input_dir, "*.mp4")))
        logger.info(f"num files: {len(input_files)}")

        for input_file in input_files:
            res = func(input_file, save_dir)
            logger.info(f"{res}")    # 文字列化

        handler.close()    # loop 毎に handler 閉じる
```

```python:modules/__init__.py
from .my_function import func
```

```python:modules/my_function.py
from logging import getLogger


logger = getLogger("__main__").getChild("my_func")

def func(input_file, save_dir):
    # do something
    logger.info("何か書くとか")
    logger.info(f"{save_dir} 表示してみたり？")
    return res
```

#### 実行

```bash
python src/main.py --config config/config.yaml
```

これで、mode1 の save_dir と mode2 の save_dir 内に各々 log.txt が出来ているはずっ！
Yay!

## 付録
困ったときは、これらの情報を見たら何かのヒントになるかもね！

```python
print("logger name:", logger.name)
print("logger level:", logger.level)
print("logger propagate:", logger.propagate)
print("logger handlers:", logger.handlers)
```