hotch-potch, Note to self

いろいろ作業記録

Python Loggerの設定をYAMLファイルで指定

1.はじめに

systemdなどから起動するバックグラウンド動作アプリを作る機会が増えてきて、ログ(logging)の扱い方を整理しました。

2.やりたいこと

  • loggingモジュールの挙動を、外部のyamlファイルから定義したい
  • ログファイルの肥大化を抑制したい(自動ローテートしたい
  • クラス・モジュールごとの表示レベルを調整したい
  • ログの出力先を、画面とログファイルそれぞれに保存したい

2.実験

(1)環境づくり

#作業ディレクトリに入る
$ cd ~/work

#プロジェクトのディレクトリ
$ mkdir loggeryaml
$ cd loggeryaml

#設定ファイルの置き場
$ mkdir etc

#仮想環境を立ち上げ
$ python3 -m venv env

#yamlファイルを扱うので必須です
$ pip3 install pyyaml

(2)yamlファイルの設定

参考 - https://kokiblog.com/2019/08/03/python_logging/ - https://blog.hiros-dot.net/?p=10297 - https://docs.python.org/ja/3/library/logging.handlers.html

ファイルを生成

$ touch etc/logging.yaml
$ code !$
# -*- coding: utf-8 -*-
# ---------------------------------------------------------
# ログ出力設定ファイル

version: 1

# ルートロガーの設定
root:
  # 出力対象のレベルを選択します。
  # DEBUG, INFO (default), WARNING, ERROR
  level: DEBUG
  #コンソール出力だけのときはこちらを有効に
  #handlers: [console]
  #コンソール、ファイル両方に出力のときはこちらを有効に
  handlers: [console, file]

# サブモジュールのロガー設定
loggers:
  # from xxx import yyy でインポートした特定のモジュールだけを制御
  # xxx.yyy:
  #  level: ERROR

# 書式
formatters:
  simple_fmt:
    # "〇s"のところは文字列を出力。マイナスがつくと右寄せ
    format: "%(asctime)s [%(levelname)-7s] (%(name)-20s) %(message)s"

# ハンドラ
handlers:
  # コンソール出力時
  console:
    class: logging.StreamHandler
    formatter: simple_fmt
    stream: ext://sys.stdout
  # ファイル出力時
  file:
    #ログローテート
    class: logging.handlers.RotatingFileHandler
    #ファイル名
    filename: var/log/app.log
    formatter: simple_fmt
    encoding: utf-8
    #ログの上限容量、これを超えるとローテーション
    maxBytes: 100000
    #ログを残す数量
    backupCount: 20

# ロガーの設定をロードしない場合の挙動
#    true: 現在のロガーを無効
#   false: ルートロガーの設定を引き継いで、現在のロガーを有効
disable_existing_loggers: false

(3)Python側の書き方

ファイル main.py

$ touch app/__main__.py
$ code !$
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import yaml
from logging import config, getLogger
logger = getLogger(__name__)

def main():
    """エントリーポイント
    """
    logger.debug(f"DEBUG")
    logger.info(f"INFO")
    logger.warning(f"WARNING")
    logger.error(f"ERROR")


if __name__ == "__main__":
    try:
        # ロガーの設定読み込み
        config.dictConfig(
            yaml.load(open("./etc/logging.yaml", encoding="utf-8").read(), Loader=yaml.SafeLoader))
        # メイン処理開始
        main()
    except KeyboardInterrupt:
        # [Ctrl-C]が押されたときの終了捕捉
        print("SIGINT - Exit")
    except SystemExit:
        # sys.exit関数による終了捕捉
        print("SystemExit - Exit")
    except:
        # 例外発生時にメッセージ
        import traceback
        traceback.print_exc()
    finally:
        pass

ファイル appclass.py

$ touch app/appclass.py
$ code !$
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# ---------------------------------------------------------------------------
# IMPORTS
# ---------------------------------------------------------------------------

#この部分は、各ファイルごとに書く必要があります。
#(__main__.pyで読み込んだ設定が引き継がれます)
import sys
from logging import config, getLogger
logger = getLogger(__name__)

# ---------------------------------------------------------------------------
# CLASSES
# ---------------------------------------------------------------------------


class AppClass_A:
    """クラスA"""

    def __init__(self):
        """コンストラクタ"""
        logger.debug(f"func: {sys._getframe().f_code.co_name}")

    def func_a(self):
        """A"""
        logger.debug(f"func: {sys._getframe().f_code.co_name}")
        logger.debug(f"message debug")


class AppClass_B:
    """クラスB"""

    def __init__(self):
        """コンストラクタ"""
        logger.debug(f"func: {sys._getframe().f_code.co_name}")

    def func_b(self):
        """B"""
        logger.debug(f"func: {sys._getframe().f_code.co_name}")
        logger.debug(f"message debug")

ここまで準備すると、このような構成になります。

(4)実行してみる

2022-06-29 16:16:40,312 [DEBUG  ] (__main__            ) message debug
2022-06-29 16:16:40,313 [INFO   ] (__main__            ) message info
/Users/miyake/gitwork/python/loggeryaml/app/__main__.py:24: DeprecationWarning: The 'warn' method is deprecated, use 'warning' instead
  logger.warn(f"message warning")
2022-06-29 16:16:40,313 [WARNING] (__main__            ) message warning
2022-06-29 16:16:40,313 [ERROR  ] (__main__            ) message error
2022-06-29 16:16:40,313 [DEBUG  ] (app.appclass        ) func: __init__
2022-06-29 16:16:40,314 [DEBUG  ] (app.appclass        ) func: func_a
2022-06-29 16:16:40,314 [DEBUG  ] (app.appclass        ) message debug
2022-06-29 16:16:40,314 [DEBUG  ] (app.appclass        ) func: __init__
2022-06-29 16:16:40,314 [DEBUG  ] (app.appclass        ) func: func_b
2022-06-29 16:16:40,314 [DEBUG  ] (app.appclass        ) message debug

ログの内容

$ less var/log/app.log
2022-06-29 16:16:40,312 [DEBUG  ] (__main__            ) message debug
2022-06-29 16:16:40,313 [INFO   ] (__main__            ) message info
2022-06-29 16:16:40,313 [WARNING] (__main__            ) message warning
2022-06-29 16:16:40,313 [ERROR  ] (__main__            ) message error
2022-06-29 16:16:40,313 [DEBUG  ] (app.appclass        ) func: __init__
2022-06-29 16:16:40,314 [DEBUG  ] (app.appclass        ) func: func_a
2022-06-29 16:16:40,314 [DEBUG  ] (app.appclass        ) message debug
2022-06-29 16:16:40,314 [DEBUG  ] (app.appclass        ) func: __init__
2022-06-29 16:16:40,314 [DEBUG  ] (app.appclass        ) func: func_b
2022-06-29 16:16:40,314 [DEBUG  ] (app.appclass        ) message debug

コンソールに出ていた内容が、ファイルに出力できています。

4.まとめ

yamlファイルを使ってのlogger制御の例が少なかったので、簡単にまとめてみました。

参考資料