マルチプロセスでのログ(python, 引数にlogger)

# 狙い
関数`multi`は関数`one`を並列的に実行する。
`multi`は`logger`を引数として受け取る。
`main.py`で`logger`を作成し，それを`multi`に渡すことで，`multi`や`one`のログを`main.py`で定義した`handler`で制御できる。
関数`multi`内で`handler`まで定義してしまう例が多くみられたので引数として`logger`を受け取る例を残す。

# コード
`multi`外で，`logger`の作成と`handler`の作成，追加をする。
```python:multiの外
# 内容は略している
logger = logging.getLogger()
h = logging.handler()
logger.addHandler(h)
multi(logger)
```

`multi`内では`in_logger`がマルチプロセスに対するキューを扱う。
キューを`multi`の外で定義した`logger`の`handler`たちに渡すことで`handler`の設定に応じたログが得られる。
```python:関数multi
listener = logging.handlers.QueueListener(que, *logger.handlers, respect_handler_level=True)
```

以下のコードでは随所に`logger`のアドレスを示すようにしている。
executorで渡された`in_logger`のアドレスが`multi`の定義のすぐ後に定義された`in_logger`と違うことに注目。
```python
import logging, logging.handlers
import concurrent.futures, multiprocessing
from pathlib import Path

def multi(logger):
    # キューを受け取るlogger
    # getLoggerの引数をloggerと同じにするとid(in_logger) = id(logger)になり問題が起きる。
    # in_logger = loggerならどうなるかみてみよう
    in_logger = logging.getLogger(__name__) 
    in_logger.setLevel(logging.DEBUG)
    logger.debug(f'passed logger:{id(logger)}')
    logger.debug(f'in_logger: {id(in_logger)}')
    
    with multiprocessing.Manager() as manager:
        que = manager.Queue(-1)
        qh = logging.handlers.QueueHandler(que)
        qh.setLevel(60) # qhは受け取りだけ
        in_logger.addHandler(qh)
        listener = logging.handlers.QueueListener(que, *logger.handlers, respect_handler_level=True)
        listener.start()
        
        # 関数oneのためだけ。demo用
        items = list(range(1000))
        try:
            max_workers = 6
            with concurrent.futures.ProcessPoolExecutor(
                    max_workers=max_workers,
                    initializer=log_initializer, initargs=(que,),
                ) as executor:
                for item in items:
                    executor.submit(func_wrapper,item,in_logger)            
        except:
            logger.exception()
        finally:
            listener.stop()
    
    
    return

def log_initializer(que):
    # 各 子プロセスで一度だけ実行される。
    # oneを実行するそれぞれのプロセスでloggerとqueueHandlerを作成
    p_logger = logging.getLogger()
    p_logger.setLevel(logging.DEBUG)
    qh = logging.handlers.QueueHandler(que) # NOTSET (0)
    qh.setLevel(logging.DEBUG)
    p_logger.addHandler(qh)
    p_logger.info('queue handler added')
    p_logger.info
    return

def func_wrapper(item, logger):
    logger.debug(f'logger in func_wrapper: {id(logger)}')
    one(item, logger)

def one(item, logger):
    logger.debug('item: %d', item)
    pass

if __name__ == '__main__':
    ## logger
    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)
    # stream
    sh = logging.StreamHandler()
    shf = logging.Formatter('%(asctime)s - %(levelname)8s -%(processName)s- %(module)20s - %(funcName)20s: %(message)s')
    sh.setFormatter(shf)
    sh.setLevel(logging.DEBUG)
    logger.addHandler(sh)
    # file
    logFileName = Path(__file__).parent.joinpath('log.log') # パス
    fh = logging.FileHandler(logFileName, encoding='utf-8', mode='w')
    fhf = logging.Formatter('%(asctime)s - %(levelname)8s -%(processName)20s - %(module)20s - %(funcName)20s: %(message)s')
    fh.setFormatter(fhf)
    fh.setLevel(logging.DEBUG)
    logger.addHandler(fh)
    logger.debug(f'first logger:  {id(logger)}')
    # run
    multi(logger)
```
一応，`main.py`で実行してうまくいくかを確認。
```python:main.py
import multi
import logging

if __name__ == '__main__':
    m_logger = logging.getLogger(__name__)
    m_logger.setLevel(logging.DEBUG)
    sh = logging.StreamHandler()
    m_logger.addHandler(sh)
    m_logger.debug(f'm_logger: {id(m_logger)}')
    multi(logger=m_logger)