【Python】concurrent.futures の ProcessPoolExecutor でログを記録するコード例(プロセスごとに分ける)

Python

Python の concurrent.futures.ProcessPoolExecutor でロギングするコード例を書きました。

プロセスごとにログファイルを分けて記録する Python コード例です。

ProcessPoolExecutor で並列処理しているときに、logging でログを記録する方法です。

環境は Windows 10 Pro (64 bit) で、Python 3.8.6 (64 bit) を使用しました。

説明

おやプロセスのロガーとかくプロセスのロガーに、それぞれ独立した FileHandler を取り付けます。

これで、プロセスごとにログファイルを分けて記録することができました。

【Python】logging でログを記録する方法のドキュメントの場所(コメント付き)

メインモジュールをロギングする

メインモジュールを ProcessPoolExecutor で並列実行するときのコード例です。

コード例

メインモジュールのコードです。

app_main.py

import logging, pathlib
import concurrent.futures, multiprocessing
logger = logging.getLogger(__name__) # NOTSET (0)
# 親プロセスの名前付きロガー または 各子プロセスの名前付きロガー

def main():
    """メイン関数です。"""
    root = logging.getLogger() # 親プロセスのルートロガー
    root.setLevel(logging.INFO) # WARNING (30) ⇒ INFO (20)

    sh = logging.StreamHandler() # NOTSET (0)
    sh.setFormatter(logging.Formatter( # %()-14s: 14 文字分のスペースで左寄せ。
        '%(processName)-14s %(name)-11s %(levelname)-8s '
        '%(module)s.py %(funcName)s(): %(message)s'))
    root.addHandler(sh)

    log_dir = pathlib.Path(r'F:\apps\log')
    log_txt = log_dir.joinpath(f'cf_fh-{__name__}.txt')
    fh = logging.FileHandler(log_txt, encoding='utf-8', mode='a') # NOTSET (0)
    fh.setFormatter(logging.Formatter(
        '%(processName)-14s %(name)-11s %(levelname)-8s '
        '%(module)s.py %(funcName)s(): %(message)s'))
    root.addHandler(fh)

    root.info('start')
    root.info(f'root.level: {logging.getLevelName(root.level)} ({root.level})')
    root.info(f'sh.level: {logging.getLevelName(sh.level)} ({sh.level})')
    root.info(f'fh.level: {logging.getLevelName(fh.level)} ({fh.level})')
    logger.debug('デバッグログ') # 今は出ません root: INFO (20)
    logger.info('インフォログ')
    logger.warning('ウォーニングログ')
    logger.error('エラーログ')
    logger.critical('クリティカルログ')

    logger.info('executor.map() を実行します。')
    max_workers = 2
    logger.info(f'プロセス数 max_workers: {max_workers}')
    logger.info('各子プロセスのログは個別にファイルに記録中...')
    args_list = [
        [100, 'あいうえお'],
        [200, 'かきくけこ'],
        [300, 'さしすせそ'],
        [400, 'たちつてと'],
        [500, 'なにぬねの'],
    ]
    datas = []
    with concurrent.futures.ProcessPoolExecutor(
            max_workers=max_workers,
            initializer=jisaku_initializer, initargs=(log_dir,),
        ) as executor:
        # type(executor): <class 'concurrent.futures.process.ProcessPoolExecutor'>
        results  = executor.map(jisaku_func_wrapper, args_list)
        for result in results: # type(results): <class 'generator'>
            datas.append(result)

    logger.info('executor.map() の結果を表示します。')
    for data in datas:
        logger.info(data)
    root.info('end')
    return

def jisaku_initializer(log_dir):
    """各子プロセスで最初に 1 回だけ実行される関数です。"""
    root = logging.getLogger() # 各子プロセスのルートロガー
    root.setLevel(logging.DEBUG) # WARNING (30) ⇒ DEBUG (10)
    cp = multiprocessing.current_process()
    # type(cp): <class 'multiprocessing.context.SpawnProcess'>
    log_txt = log_dir.joinpath(f'cf-fh-{__name__}-{cp.name}.txt')
    fh = logging.FileHandler(log_txt, encoding='utf-8', mode='a') # NOTSET (0)
    fh.setFormatter(logging.Formatter(
        '%(processName)-14s %(name)-11s %(levelname)-8s '
        '%(module)s.py %(funcName)s(): %(message)s'))
    root.addHandler(fh)
    root.info('(ファイルハンドラ追加完了)')
    root.info(f'root.level: {logging.getLevelName(root.level)} ({root.level})')
    root.info(f'logger.level: {logging.getLevelName(logger.level)} ({logger.level})')
    root.info(f'fh.level: {logging.getLevelName(fh.level)} ({fh.level})')
    return

def jisaku_func_wrapper(args):
    return jisaku_func(*args)

def jisaku_func(number, text):
    """自作関数です。"""
    logger.debug(f'デバッグログ {number} {text}')
    logger.info(f'インフォログ {number} {text}')
    logger.warning(f'ウォーニングログ {number} {text}')
    logger.error(f'エラーログ {number} {text}')
    logger.critical(f'クリティカルログ {number} {text}')
    return [number, text, 'OK']

if __name__ == '__main__':
    main()

実行結果 (max_workers=2)

メインモジュールmax_workers=2 で実行しました。

実行結果の画面表示です。

MainProcess    root        INFO     app_main.py main(): start
MainProcess    root        INFO     app_main.py main(): root.level: INFO (20)
MainProcess    root        INFO     app_main.py main(): sh.level: NOTSET (0)
MainProcess    root        INFO     app_main.py main(): fh.level: NOTSET (0)
MainProcess    __main__    INFO     app_main.py main(): インフォログ
MainProcess    __main__    WARNING  app_main.py main(): ウォーニングログ
MainProcess    __main__    ERROR    app_main.py main(): エラーログ
MainProcess    __main__    CRITICAL app_main.py main(): クリティカルログ
MainProcess    __main__    INFO     app_main.py main(): executor.map() を実行します。
MainProcess    __main__    INFO     app_main.py main(): プロセス数 max_workers: 2
MainProcess    __main__    INFO     app_main.py main(): 各子プロセスのログは個別にファイルに記録中...
MainProcess    __main__    INFO     app_main.py main(): executor.map() の結果を表示します。
MainProcess    __main__    INFO     app_main.py main(): [100, 'あいうえお', 'OK']
MainProcess    __main__    INFO     app_main.py main(): [200, 'かきくけこ', 'OK']
MainProcess    __main__    INFO     app_main.py main(): [300, 'さしすせそ', 'OK']
MainProcess    __main__    INFO     app_main.py main(): [400, 'たちつてと', 'OK']
MainProcess    __main__    INFO     app_main.py main(): [500, 'なにぬねの', 'OK']
MainProcess    root        INFO     app_main.py main(): end

ログファイルの内容です。

cf_fh-__main__.txt

MainProcess    root        INFO     app_main.py main(): start
MainProcess    root        INFO     app_main.py main(): root.level: INFO (20)
MainProcess    root        INFO     app_main.py main(): sh.level: NOTSET (0)
MainProcess    root        INFO     app_main.py main(): fh.level: NOTSET (0)
MainProcess    __main__    INFO     app_main.py main(): インフォログ
MainProcess    __main__    WARNING  app_main.py main(): ウォーニングログ
MainProcess    __main__    ERROR    app_main.py main(): エラーログ
MainProcess    __main__    CRITICAL app_main.py main(): クリティカルログ
MainProcess    __main__    INFO     app_main.py main(): executor.map() を実行します。
MainProcess    __main__    INFO     app_main.py main(): プロセス数 max_workers: 2
MainProcess    __main__    INFO     app_main.py main(): 各子プロセスのログは個別にファイルに記録中...
MainProcess    __main__    INFO     app_main.py main(): executor.map() の結果を表示します。
MainProcess    __main__    INFO     app_main.py main(): [100, 'あいうえお', 'OK']
MainProcess    __main__    INFO     app_main.py main(): [200, 'かきくけこ', 'OK']
MainProcess    __main__    INFO     app_main.py main(): [300, 'さしすせそ', 'OK']
MainProcess    __main__    INFO     app_main.py main(): [400, 'たちつてと', 'OK']
MainProcess    __main__    INFO     app_main.py main(): [500, 'なにぬねの', 'OK']
MainProcess    root        INFO     app_main.py main(): end

cf-fh-__mp_main__-SpawnProcess-1.txt

SpawnProcess-1 root        INFO     app_main.py jisaku_initializer(): (ファイルハンドラ追加完了)
SpawnProcess-1 root        INFO     app_main.py jisaku_initializer(): root.level: DEBUG (10)
SpawnProcess-1 root        INFO     app_main.py jisaku_initializer(): logger.level: NOTSET (0)
SpawnProcess-1 root        INFO     app_main.py jisaku_initializer(): fh.level: NOTSET (0)
SpawnProcess-1 __mp_main__ DEBUG    app_main.py jisaku_func(): デバッグログ 100 あいうえお
SpawnProcess-1 __mp_main__ INFO     app_main.py jisaku_func(): インフォログ 100 あいうえお
SpawnProcess-1 __mp_main__ WARNING  app_main.py jisaku_func(): ウォーニングログ 100 あいうえお
SpawnProcess-1 __mp_main__ ERROR    app_main.py jisaku_func(): エラーログ 100 あいうえお
SpawnProcess-1 __mp_main__ CRITICAL app_main.py jisaku_func(): クリティカルログ 100 あいうえお
SpawnProcess-1 __mp_main__ DEBUG    app_main.py jisaku_func(): デバッグログ 400 たちつてと
SpawnProcess-1 __mp_main__ INFO     app_main.py jisaku_func(): インフォログ 400 たちつてと
SpawnProcess-1 __mp_main__ WARNING  app_main.py jisaku_func(): ウォーニングログ 400 たちつてと
SpawnProcess-1 __mp_main__ ERROR    app_main.py jisaku_func(): エラーログ 400 たちつてと
SpawnProcess-1 __mp_main__ CRITICAL app_main.py jisaku_func(): クリティカルログ 400 たちつてと

cf-fh-__mp_main__-SpawnProcess-2.txt

SpawnProcess-2 root        INFO     app_main.py jisaku_initializer(): (ファイルハンドラ追加完了)
SpawnProcess-2 root        INFO     app_main.py jisaku_initializer(): root.level: DEBUG (10)
SpawnProcess-2 root        INFO     app_main.py jisaku_initializer(): logger.level: NOTSET (0)
SpawnProcess-2 root        INFO     app_main.py jisaku_initializer(): fh.level: NOTSET (0)
SpawnProcess-2 __mp_main__ DEBUG    app_main.py jisaku_func(): デバッグログ 200 かきくけこ
SpawnProcess-2 __mp_main__ INFO     app_main.py jisaku_func(): インフォログ 200 かきくけこ
SpawnProcess-2 __mp_main__ WARNING  app_main.py jisaku_func(): ウォーニングログ 200 かきくけこ
SpawnProcess-2 __mp_main__ ERROR    app_main.py jisaku_func(): エラーログ 200 かきくけこ
SpawnProcess-2 __mp_main__ CRITICAL app_main.py jisaku_func(): クリティカルログ 200 かきくけこ
SpawnProcess-2 __mp_main__ DEBUG    app_main.py jisaku_func(): デバッグログ 300 さしすせそ
SpawnProcess-2 __mp_main__ INFO     app_main.py jisaku_func(): インフォログ 300 さしすせそ
SpawnProcess-2 __mp_main__ WARNING  app_main.py jisaku_func(): ウォーニングログ 300 さしすせそ
SpawnProcess-2 __mp_main__ ERROR    app_main.py jisaku_func(): エラーログ 300 さしすせそ
SpawnProcess-2 __mp_main__ CRITICAL app_main.py jisaku_func(): クリティカルログ 300 さしすせそ
SpawnProcess-2 __mp_main__ DEBUG    app_main.py jisaku_func(): デバッグログ 500 なにぬねの
SpawnProcess-2 __mp_main__ INFO     app_main.py jisaku_func(): インフォログ 500 なにぬねの
SpawnProcess-2 __mp_main__ WARNING  app_main.py jisaku_func(): ウォーニングログ 500 なにぬねの
SpawnProcess-2 __mp_main__ ERROR    app_main.py jisaku_func(): エラーログ 500 なにぬねの
SpawnProcess-2 __mp_main__ CRITICAL app_main.py jisaku_func(): クリティカルログ 500 なにぬねの

自作ライブラリをロギングする

自作ライブラリを concurrent.futures.ProcessPoolExecutor で並列実行するときのコード例です。

コード例

メインモジュールのコードです。

app_main.py

import logging, pathlib
import concurrent.futures
import jisaku
logger = logging.getLogger(__name__) # NOTSET (0)
# 親プロセスの名前付きロガー

def main():
    """メイン関数です。"""
    root = logging.getLogger() # 親プロセスのルートロガー
    root.setLevel(logging.INFO) # WARNING (30) ⇒ INFO (20)

    sh = logging.StreamHandler() # NOTSET (0)
    sh.setFormatter(logging.Formatter( # %()-14s: 14 文字分のスペースで左寄せ。
        '%(processName)-14s %(name)-11s %(levelname)-8s '
        '%(module)s.py %(funcName)s(): %(message)s'))
    root.addHandler(sh)

    log_dir = pathlib.Path(r'F:\apps\log')
    log_txt = log_dir.joinpath(f'cf-fh-lib-{__name__}.txt')
    fh = logging.FileHandler(log_txt, encoding='utf-8', mode='a') # NOTSET (0)
    fh.setFormatter(logging.Formatter(
        '%(processName)-14s %(name)-11s %(levelname)-8s '
        '%(module)s.py %(funcName)s(): %(message)s'))
    root.addHandler(fh)

    root.info('start')
    root.info(f'root.level: {logging.getLevelName(root.level)} ({root.level})')
    root.info(f'sh.level: {logging.getLevelName(sh.level)} ({sh.level})')
    root.info(f'fh.level: {logging.getLevelName(fh.level)} ({fh.level})')
    logger.debug('デバッグログ') # 今は出ません root: INFO (20)
    logger.info('インフォログ')
    logger.warning('ウォーニングログ')
    logger.error('エラーログ')
    logger.critical('クリティカルログ')

    logger.info('executor.map() を実行します。')
    max_workers = 2
    logger.info(f'プロセス数 max_workers: {max_workers}')
    logger.info('各子プロセスのログは個別にファイルに記録中...')
    args_list = [
        [100, 'あいうえお'],
        [200, 'かきくけこ'],
        [300, 'さしすせそ'],
        [400, 'たちつてと'],
        [500, 'なにぬねの'],
    ]
    datas = []
    with concurrent.futures.ProcessPoolExecutor(
            max_workers=max_workers,
            initializer=jisaku.jisaku_initializer, initargs=(log_dir,),
        ) as executor:
        # type(executor): <class 'concurrent.futures.process.ProcessPoolExecutor'>
        results  = executor.map(jisaku.jisaku_func_wrapper, args_list)
        for result in results: # type(results): <class 'generator'>
            datas.append(result)

    logger.info('executor.map() の結果を表示します。')
    for data in datas:
        logger.info(data)
    root.info('end')
    return

if __name__ == '__main__':
    main()

自作ライブラリのコードです。

jisaku.py

import logging
import multiprocessing
logger = logging.getLogger(__name__) # NOTSET (0)
# 親プロセスの名前付きロガー または 各子プロセスの名前付きロガー

def jisaku_initializer(log_dir):
    """各子プロセスで最初に 1 回だけ実行される関数です。"""
    root = logging.getLogger() # 各子プロセスのルートロガー
    root.setLevel(logging.DEBUG) # WARNING (30) ⇒ DEBUG (10)
    cp = multiprocessing.current_process()
    # type(cp): <class 'multiprocessing.context.SpawnProcess'>
    log_txt = log_dir.joinpath(f'cf-fh-lib-{__name__}-{cp.name}.txt')
    fh = logging.FileHandler(log_txt, encoding='utf-8', mode='a') # NOTSET (0)
    fh.setFormatter(logging.Formatter(
        '%(processName)-14s %(name)-11s %(levelname)-8s '
        '%(module)s.py %(funcName)s(): %(message)s'))
    root.addHandler(fh)
    root.info('(ファイルハンドラ追加完了)')
    root.info(f'root.level: {logging.getLevelName(root.level)} ({root.level})')
    root.info(f'logger.level: {logging.getLevelName(logger.level)} ({logger.level})')
    root.info(f'fh.level: {logging.getLevelName(fh.level)} ({fh.level})')
    return

def jisaku_func_wrapper(args):
    return jisaku_func(*args)

def jisaku_func(number, text):
    """自作関数です。"""
    logger.debug(f'デバッグログ {number} {text}')
    logger.info(f'インフォログ {number} {text}')
    logger.warning(f'ウォーニングログ {number} {text}')
    logger.error(f'エラーログ {number} {text}')
    logger.critical(f'クリティカルログ {number} {text}')
    return [number, text, 'OK']

実行結果 (max_workers=2)

自作ライブラリmax_workers=2 で実行しました。

実行結果の画面表示です。

MainProcess    root        INFO     app_main.py main(): start
MainProcess    root        INFO     app_main.py main(): root.level: INFO (20)
MainProcess    root        INFO     app_main.py main(): sh.level: NOTSET (0)
MainProcess    root        INFO     app_main.py main(): fh.level: NOTSET (0)
MainProcess    __main__    INFO     app_main.py main(): インフォログ
MainProcess    __main__    WARNING  app_main.py main(): ウォーニングログ
MainProcess    __main__    ERROR    app_main.py main(): エラーログ
MainProcess    __main__    CRITICAL app_main.py main(): クリティカルログ
MainProcess    __main__    INFO     app_main.py main(): executor.map() を実行します。
MainProcess    __main__    INFO     app_main.py main(): プロセス数 max_workers: 2
MainProcess    __main__    INFO     app_main.py main(): 各子プロセスのログは個別にファイルに記録中...
MainProcess    __main__    INFO     app_main.py main(): executor.map() の結果を表示します。
MainProcess    __main__    INFO     app_main.py main(): [100, 'あいうえお', 'OK']
MainProcess    __main__    INFO     app_main.py main(): [200, 'かきくけこ', 'OK']
MainProcess    __main__    INFO     app_main.py main(): [300, 'さしすせそ', 'OK']
MainProcess    __main__    INFO     app_main.py main(): [400, 'たちつてと', 'OK']
MainProcess    __main__    INFO     app_main.py main(): [500, 'なにぬねの', 'OK']
MainProcess    root        INFO     app_main.py main(): end

ログファイルの内容です。

cf-fh-lib-__main__.txt

MainProcess    root        INFO     app_main.py main(): start
MainProcess    root        INFO     app_main.py main(): root.level: INFO (20)
MainProcess    root        INFO     app_main.py main(): sh.level: NOTSET (0)
MainProcess    root        INFO     app_main.py main(): fh.level: NOTSET (0)
MainProcess    __main__    INFO     app_main.py main(): インフォログ
MainProcess    __main__    WARNING  app_main.py main(): ウォーニングログ
MainProcess    __main__    ERROR    app_main.py main(): エラーログ
MainProcess    __main__    CRITICAL app_main.py main(): クリティカルログ
MainProcess    __main__    INFO     app_main.py main(): executor.map() を実行します。
MainProcess    __main__    INFO     app_main.py main(): プロセス数 max_workers: 2
MainProcess    __main__    INFO     app_main.py main(): 各子プロセスのログは個別にファイルに記録中...
MainProcess    __main__    INFO     app_main.py main(): executor.map() の結果を表示します。
MainProcess    __main__    INFO     app_main.py main(): [100, 'あいうえお', 'OK']
MainProcess    __main__    INFO     app_main.py main(): [200, 'かきくけこ', 'OK']
MainProcess    __main__    INFO     app_main.py main(): [300, 'さしすせそ', 'OK']
MainProcess    __main__    INFO     app_main.py main(): [400, 'たちつてと', 'OK']
MainProcess    __main__    INFO     app_main.py main(): [500, 'なにぬねの', 'OK']
MainProcess    root        INFO     app_main.py main(): end

cf-fh-lib-jisaku-SpawnProcess-1.txt

SpawnProcess-1 root        INFO     jisaku.py jisaku_initializer(): (ファイルハンドラ追加完了)
SpawnProcess-1 root        INFO     jisaku.py jisaku_initializer(): root.level: DEBUG (10)
SpawnProcess-1 root        INFO     jisaku.py jisaku_initializer(): logger.level: NOTSET (0)
SpawnProcess-1 root        INFO     jisaku.py jisaku_initializer(): fh.level: NOTSET (0)
SpawnProcess-1 jisaku      DEBUG    jisaku.py jisaku_func(): デバッグログ 100 あいうえお
SpawnProcess-1 jisaku      INFO     jisaku.py jisaku_func(): インフォログ 100 あいうえお
SpawnProcess-1 jisaku      WARNING  jisaku.py jisaku_func(): ウォーニングログ 100 あいうえお
SpawnProcess-1 jisaku      ERROR    jisaku.py jisaku_func(): エラーログ 100 あいうえお
SpawnProcess-1 jisaku      CRITICAL jisaku.py jisaku_func(): クリティカルログ 100 あいうえお
SpawnProcess-1 jisaku      DEBUG    jisaku.py jisaku_func(): デバッグログ 400 たちつてと
SpawnProcess-1 jisaku      INFO     jisaku.py jisaku_func(): インフォログ 400 たちつてと
SpawnProcess-1 jisaku      WARNING  jisaku.py jisaku_func(): ウォーニングログ 400 たちつてと
SpawnProcess-1 jisaku      ERROR    jisaku.py jisaku_func(): エラーログ 400 たちつてと
SpawnProcess-1 jisaku      CRITICAL jisaku.py jisaku_func(): クリティカルログ 400 たちつてと

cf-fh-lib-jisaku-SpawnProcess-2.txt

SpawnProcess-2 root        INFO     jisaku.py jisaku_initializer(): (ファイルハンドラ追加完了)
SpawnProcess-2 root        INFO     jisaku.py jisaku_initializer(): root.level: DEBUG (10)
SpawnProcess-2 root        INFO     jisaku.py jisaku_initializer(): logger.level: NOTSET (0)
SpawnProcess-2 root        INFO     jisaku.py jisaku_initializer(): fh.level: NOTSET (0)
SpawnProcess-2 jisaku      DEBUG    jisaku.py jisaku_func(): デバッグログ 200 かきくけこ
SpawnProcess-2 jisaku      INFO     jisaku.py jisaku_func(): インフォログ 200 かきくけこ
SpawnProcess-2 jisaku      WARNING  jisaku.py jisaku_func(): ウォーニングログ 200 かきくけこ
SpawnProcess-2 jisaku      ERROR    jisaku.py jisaku_func(): エラーログ 200 かきくけこ
SpawnProcess-2 jisaku      CRITICAL jisaku.py jisaku_func(): クリティカルログ 200 かきくけこ
SpawnProcess-2 jisaku      DEBUG    jisaku.py jisaku_func(): デバッグログ 300 さしすせそ
SpawnProcess-2 jisaku      INFO     jisaku.py jisaku_func(): インフォログ 300 さしすせそ
SpawnProcess-2 jisaku      WARNING  jisaku.py jisaku_func(): ウォーニングログ 300 さしすせそ
SpawnProcess-2 jisaku      ERROR    jisaku.py jisaku_func(): エラーログ 300 さしすせそ
SpawnProcess-2 jisaku      CRITICAL jisaku.py jisaku_func(): クリティカルログ 300 さしすせそ
SpawnProcess-2 jisaku      DEBUG    jisaku.py jisaku_func(): デバッグログ 500 なにぬねの
SpawnProcess-2 jisaku      INFO     jisaku.py jisaku_func(): インフォログ 500 なにぬねの
SpawnProcess-2 jisaku      WARNING  jisaku.py jisaku_func(): ウォーニングログ 500 なにぬねの
SpawnProcess-2 jisaku      ERROR    jisaku.py jisaku_func(): エラーログ 500 なにぬねの
SpawnProcess-2 jisaku      CRITICAL jisaku.py jisaku_func(): クリティカルログ 500 なにぬねの

関連記事

concurrent.futures でログを記録するコード例。

multiprocessing でログを記録するコード例。

普通のシングルプロセスでログを記録するコード例。

並列処理のコード例。

以上です。

スポンサーリンク
シェアする(押すとSNS投稿用の『編集ページ』に移動します)
フォローする(RSSフィードに移動します)
スポンサーリンク
シラベルノート
タイトルとURLをコピーしました