当サイトは、アフィリエイト広告を利用しています

【Python】loggingの基本をまとめてみた

作成日:2024月08月03日
更新日:2024年09月04日

Pythonでログ出力を行いたい場合はloggingを使うことになるので
loggingの基本的な使い方や用語をまとめて忘備録として残しておく

Pythonのversionは3.12で試す。

printとloggingについて

pythonでログを出す方法としては大きく

  • printを使う
  • 標準ライブラリであるloggingモジュールを使う

の2つの方法がある。

printを使う

pythonでプログラムの実行結果当を出す方法としては
下記のような感じでprintを使う。

print
def main():
val = 1 + 1
print(f'1+1の計算結果は{val}です')
if __name__ == "__main__":
main()
# 実行結果
# 1+1の計算結果は2です

ただこれだと結果がターミナルで出力されるだけなので
開発時に動作を確認するには使えるが物足りない。
※ファイルに出すこともできるがそれならloggingを使ったほうがいいので割愛。

loggingモジュールを使う

Python標準のloggingモジュールを使うと下記のようになる

logging
import logging
# ロガーを作成
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
# コンソールハンドラーを作成してロガーに追加
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
# フォーマッターを作成してコンソールハンドラーに追加
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
console_handler.setFormatter(formatter)
# コンソールハンドラーをロガーに追加
logger.addHandler(console_handler)
def main():
val = 1 + 1
logger.info(f'1+1の計算結果は{val}です')
if __name__ == "__main__":
main()
# 実行結果
# 2024-07-30 17:15:38,752 - INFO - 1+1の計算結果は2です

少し設定は増えるが利点は多い。
loggingモジュールを使うと下記のようなことができるようになる

ログレベルの設定

loggingモジュールではログメッセージに対して異なる
重要度レベル

  • DEBUG
  • INFO
  • WARNING
  • ERROR
  • CRITICAL

を設定できるので重要度に応じたログフィルタリングや処理を行える。

ログのフォーマット

loggingモジュールではログメッセージのフォーマットを柔軟に設定できる
日時、ログレベル、メッセージ内容などを含むカスタムフォーマットを指定可能

ログの出力先

loggingモジュールではログの出力先を簡単に設定できる
※ファイル、コンソール、ネットワーク、メールなどなど

printとloggingどっちがいいか?

システムやアプリケーションにログ機能を実装する場合は
上記のような利点があるためloggingモジュールを使う。

printは基本的にはデバッグや簡単なスクリプトを実行する際に
使うようにする。

ログレベルって何?

loggingモジュールを使うとログレベルを設定する必要がある。
ログレベルは

  • DEBUG
  • INFO
  • WARNING
  • ERROR
  • CRITICAL

があり、重要度の順序が

DEBUG < INFO < WARNING < ERROR < CRITICAL

となっている。
例えばログレベルを「WARNING」にした場合

ログレベル
import logging
# ロガーを作成
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
# コンソールハンドラーを作成してロガーに追加
console_handler = logging.StreamHandler()
# ログレベルを設定
console_handler.setLevel(logging.WARNING)
# フォーマッターを作成してコンソールハンドラーに追加
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
console_handler.setFormatter(formatter)
# コンソールハンドラーをロガーに追加
logger.addHandler(console_handler)
def main():
logger.debug('ログレベル:debug')
logger.info('ログレベル:info')
logger.warning('ログレベル:warning')
logger.error('ログレベル:error')
logger.critical('ログレベル:critical')
if __name__ == "__main__":
main()
# 実行結果
# 2024-07-30 17:23:56,375 - WARNING - ログレベル:warning
# 2024-07-30 17:23:56,376 - ERROR - ログレベル:error
# 2024-07-30 17:23:56,376 - CRITICAL - ログレベル:critical

のように設定レベル以上が出力される。

下記に動作確認したサンプルをのせる

ログレベルサンプル

loggingの初期設定

loggingの使い方を整理していく。

loggingをpythonの標準ライブラリのためpipでのインストールは必要ない

loggingを使うまでの流れ

loggingを使うまでの流れは下記のようになる

  1. ロガーの取得
  2. ロガーのレベルを設定
  3. ハンドラーの作成と設定
  4. フォーマッターの作成と設定
  5. ハンドラーをロガーに追加
  6. ログメッセージを記録

loggingの設定方法

また設定方法としては主に

  • プログラム内で直接設定
  • logging.config.fileConfigで設定
  • logging.config.dictConfigで設定
  • JSONファイルを読み込んでで設定
  • basicConfigで設定

のパターンがある。

それぞれのパターンでサンプルを作ってみる。

プログラム内で直接設定

プログラム内で直接記述する方法はシンプルで使いやすく、またプログラムなので
拡張性も高い。

サンプルとしては下記になる。

ログレベル
import logging
# 1.ロガーを取得
logger = logging.getLogger(__name__)
# 2.ロガーのレベルを設定
logger.setLevel(logging.DEBUG)
# 3.ハンドラーの作成と設定
# コンソールハンドラーを作成してロガーに追加
console_handler = logging.StreamHandler()
# ログレベルを設定
console_handler.setLevel(logging.WARNING)
# ファイルハンドラーの作成と設定
file_handler = logging.FileHandler('app.log', encoding='utf-8')
# ログレベルを設定
file_handler.setLevel(logging.INFO) # ファイルにはINFO以上のメッセージを出力
# 4.フォーマッターの作成と設定
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)
# 5.ハンドラーをロガーに追加
logger.addHandler(console_handler)
logger.addHandler(file_handler)
def main():
# 6.ログメッセージを記録
logger.debug('ログレベル:debug')
logger.info('ログレベル:info')
logger.warning('ログレベル:warning')
logger.error('ログレベル:error')
logger.critical('ログレベル:critical')
if __name__ == "__main__":
main()
# 実行結果(コンソール)
# 2024-07-30 17:23:56,375 - WARNING - ログレベル:warning
# 2024-07-30 17:23:56,376 - ERROR - ログレベル:error
# 2024-07-30 17:23:56,376 - CRITICAL - ログレベル:critical
# 実行結果(ログふぃいる)
# 2024-07-30 17:23:56,375 - INFO - ログレベル:info
# 2024-07-30 17:23:56,376 - WARNING - ログレベル:warning
# 2024-07-30 17:23:56,376 - ERROR - ログレベル:error
# 2024-07-30 17:23:56,376 - CRITICAL - ログレベル:critical

細かく解説する

1. ロガーの取得

ロガーの取得
import logging
# 1.ロガーを取得
logger = logging.getLogger(__name__)

logging.getLoggerでloggerインスタンスを作成する。
引数はloggerを識別するために設定する。
同じ名前で作ると同じインスタンスと判定される。

逆にいうと同じ名前空間であれば同じ名前を指定すれば
同じloggerインスタンスを取得できる。

実はlogging.getLoggerでインスタンスを作らなくとも

禁止
import logging
logging.basicConfig(level=logging.DEBUG)
logging.debug('This is a debug message')

のようにすればログ出力はできるのだが基本的にはしないほうがいい。
必ずlogging.getLoggerでloggerインスタンスを作成して使うこと。

logging.basicConfig()はルートロガーの設定であるため、他のloggerインスタンスにも
デフォルト設定を適用していまい、アプリケーション全体のロギング設定に影響する

2. ロガーのレベルを設定

ロガーのレベルを設定
# 2.ロガーのレベルを設定
logger.setLevel(logging.DEBUG)

ロガーのログレベルを設定する。
設定したログレベル以上のログが出力さえるようになる。

3. ハンドラーの作成と設定

ハンドラーの作成と設定
# 3.ハンドラーの作成と設定
# コンソールハンドラーを作成してロガーに追加
console_handler = logging.StreamHandler()
# ログレベルを設定
console_handler.setLevel(logging.WARNING)
# ファイルハンドラーの作成と設定
file_handler = logging.FileHandler('app.log', encoding='utf-8')
# ログレベルを設定
file_handler.setLevel(logging.INFO) # ファイルにはINFO以上のメッセージを出力

ハンドラーは、ログメッセージをどこに出力するかを設定する。
一般的なハンドラーとして

  • コンソールに出力するためのStreamHandler
  • ファイルに出力するためのFileHandler

などがある。

またハンドラーでもログレベルを設定しているのはログの出し分けを可能にするため。
下記を行っているイメージ。

  • ロガーのログレベルでメッセージのフィルタリング → DEBUG以上のメッセージを記録対象にする
  • コンソールハンドラーのログレベルで出力のフィルタリング → WARNING以上をコンソールに出力
  • ファイルーのログレベルで出力のフィルタリング → INFO以上をファイルに出力

DEBUGのログは記録対象にはなっているが、ハンドラーの設定がないため 出力はされない。

4. フォーマッターの作成と設定

フォーマッターの作成と設定
# 4.フォーマッターの作成と設定
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)

フォーマッターは、ログメッセージの出力形式を定義する。
logging.Formatterを使ってフォーマットを設定し、それをハンドラーに追加する

出力形式の詳しくは下記で

5. ハンドラーをロガーに追加

ハンドラーをロガーに追加
# 5.ハンドラーをロガーに追加
logger.addHandler(console_handler)
logger.addHandler(file_handler)

作成したハンドラーをロガーに追加する
ロガーはログメッセージを指定されたハンドラーを通じて出力するようになる。

6. ログメッセージを記録

py
def main():
# 6.ログメッセージを記録
logger.debug('ログレベル:debug')
logger.info('ログレベル:info')
logger.warning('ログレベル:warning')
logger.error('ログレベル:error')
logger.critical('ログレベル:critical')
if __name__ == "__main__":
main()

loggerインスタンスのメソッド(debug, info, warning, error, critical)を使って
ログメッセージを記録する。

この場合、コンソールには

  • warning
  • error
  • critical

のログが出力され
ファイルには

  • INFOg
  • warning
  • error
  • critical

のログが出力される

logging.config.fileConfigで設定

INI形式のファイルにログ設定を記述し読み込む方法。
メリットとしては

  • ログ設定をコードから分離できるため、設定の管理が容易になる。
  • iniファイルなのでを他のプロジェクトで再利用が可能。

な点がある。

デメリットとしては

  • INI形式はシンプルですが、複雑な設定はできない
  • 実行時に設定を変更するのが難しい

点がある。

logconf.ini
[loggers]
keys=root,exampleLogger
[handlers]
keys=consoleHandler,fileHandler
[formatters]
keys=exampleFormatter
[logger_root]
level=DEBUG
handlers=consoleHandler
[logger_exampleLogger]
level=DEBUG
handlers=consoleHandler,fileHandler
qualname=__main__
propagate=0
[handler_consoleHandler]
class=StreamHandler
level=WARNING
formatter=exampleFormatter
args=(sys.stdout,)
[handler_fileHandler]
class=FileHandler
level=INFO
formatter=exampleFormatter
args=('app.log', 'a', 'utf-8')
[formatter_exampleFormatter]
format=%(asctime)s - %(levelname)s - %(message)s
datefmt=%Y-%m-%d %H:%M:%S

直接プログラムで書いたいてものをiniファイルに書く。

[loggers]

使用するロガーの名前をカンマ区切りで列挙
上記の場合はroot,exampleLoggerの二つのloggerを記載

[handlers]

使用するハンドラーの名前をカンマ区切りで列挙 上記の場合はconsoleHandler,fileHandlerの二つのハンドラーを記載

[formatters]

使用するフォーマッターの名前をカンマ区切りで列挙。

[logger_root]

ロガーの個別設定。「logger_ロガー名」で定義する これはrootロガーの設定

[logger_exampleLogger]

exampleLoggerのロガーの設定。

qualnameはmainが指定してあるため
「logger = logging.getLogger(name)」で呼ばれた時は
このexampleLoggerが返される

「propagate=0」は親ロガーに伝播(プロパゲート)しないようにする設定。
ロガーを独立して動かすので0に設定しておく。

[handler_consoleHandler]

コンソールハンドラーの設定

[handler_fileHandler]

ファイルハンドラーの設定

[formatter_exampleFormatter]

フォーマッターの定義

実行する

iniファイル読み込み
import logging
import logging.config
# ロギング構成ファイルを読み込む
logging.config.fileConfig('app/logging_config.ini', disable_existing_loggers=False)
# __name__を使用してロガーを取得
logger = logging.getLogger(__name__)
def main():
# ログメッセージを記録
logger.debug('ログレベル:debug')
logger.info('ログレベル:info')
logger.warning('ログレベル:warning')
logger.error('ログレベル:error')
logger.critical('ログレベル:critical')
if __name__ == "__main__":
main()

ロギング構成ファイルを読み込んでから.loggerを取得して
実行する。

disable_existing_loggershはロギングの設定を読み込む際のオプションで、既存のロガーの状態をどうするかを指定する。
デフォルトではtrueになっていて、設定ファイルを読み込むと、すでに存在するロガーは無効化されるため
設定ファイルに含まれていないロガーはログ出力を行わなくなる。

falseにしておけばすでに存在するロガーは無効化されず、設定ファイルで指定されたロガーに加えて、
既存のロガーもそのまま使用できるのでfalseを設定しておく。

logging.config.dictConfigで設定

次は設定を辞書形式で定義する
メリットとしては

  • 設定をプログラム内で管理できるので、外部設定ファイルが不要になる
  • コード内で動的に設定を変更可能
  • dictConfigは複雑なロギング設定をサポートしているので柔軟なカスタマイズが可能

がある。

dict読み込み
import logging
import logging.config
# ロギングの設定を辞書形式で定義
logging_config = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'exampleFormatter': {
'format': '%(asctime)s - %(levelname)s - %(message)s',
'datefmt': '%Y-%m-%d %H:%M:%S'
}
},
'handlers': {
'consoleHandler': {
'class': 'logging.StreamHandler',
'level': 'WARNING',
'formatter': 'exampleFormatter',
'stream': 'ext://sys.stdout'
},
'fileHandler': {
'class': 'logging.FileHandler',
'level': 'INFO',
'formatter': 'exampleFormatter',
'filename': 'app.log',
'encoding': 'utf-8'
}
},
'loggers': {
'': { # ルートロガーの設定
'level': 'DEBUG',
'handlers': ['consoleHandler', 'fileHandler']
},
'exampleLogger': { # 特定のロガー設定
'level': 'DEBUG',
'handlers': ['consoleHandler', 'fileHandler'],
'propagate': False
}
}
}
# ロギングの設定を適用
logging.config.dictConfig(logging_config)
# exampleLoggerを使用してロガーを取得
logger = logging.getLogger('exampleLogger')
def main():
# ログメッセージを記録
logger.debug('ログレベル:debug')
logger.info('ログレベル:info')
logger.warning('ログレベル:warning')
logger.error('ログレベル:error')
logger.critical('ログレベル:critical')
if __name__ == "__main__":
main()

辞書形式でloggerの設定を読み込む。

JSONファイルを読み込んでで設定

最後はJSONファイルを読み込んでで設定するパターン。
これはdictConfigを使う点は同じだが、

  • ロギング設定をJSONファイルで一元管理できる
  • プログラムから動的に変更できる

という利点がある。

logging_config.json
{
"version": 1,
"disable_existing_loggers": false,
"formatters": {
"exampleFormatter": {
"format": "%(asctime)s - %(levelname)s - %(message)s",
"datefmt": "%Y-%m-%d %H:%M:%S"
}
},
"handlers": {
"consoleHandler": {
"class": "logging.StreamHandler",
"level": "WARNING",
"formatter": "exampleFormatter",
"stream": "ext://sys.stdout"
},
"fileHandler": {
"class": "logging.FileHandler",
"level": "INFO",
"formatter": "exampleFormatter",
"filename": "app.log",
"encoding": "utf-8"
}
},
"loggers": {
"": {
"level": "DEBUG",
"handlers": []
},
"exampleLogger": {
"level": "DEBUG",
"handlers": ["consoleHandler", "fileHandler"],
"propagate": false
}
}
}

JSONでロギング設定を記載する

jsonパターン
import logging
import logging.config
import json
# JSONファイルを読み込んでロギングの設定を適用
with open('app/logging_config.json', 'r', encoding='utf-8') as f:
config = json.load(f)
logging.config.dictConfig(config)
# ロガーを取得
logger = logging.getLogger(__name__)
def main():
# 基本設定によるログメッセージ
logger.debug('ログレベル:debug')
logger.info('ログレベル:info')
logger.warning('ログレベル:warning')
logger.error('ログレベル:error')
logger.critical('ログレベル:critical')
# 動的に設定を変更
dynamic_console_handler = logging.StreamHandler()
dynamic_console_handler.setLevel(logging.DEBUG)
dynamic_console_handler.setFormatter(logging.Formatter('%(name)s - %(levelname)s - %(message)s'))
logger.addHandler(dynamic_console_handler)
# 動的設定によるログメッセージ
logger.debug('動的設定によるログレベル:debug')
logger.info('動的設定によるログレベル:info')
if __name__ == "__main__":
main()
# 実行結果
# ログレベル:warning
# ログレベル:error
# ログレベル:critical
# __main__ - DEBUG - 動的設定によるログレベル:debug
# __main__ - INFO - 動的設定によるログレベル:info

動的にログ出力を変更できる

basicConfigで設定

basicConfigを使えば、ルートロガーとそのハンドラーを
設定できる。

細かくログ出力をわけずに統一して出したいのであれば basicConfigで十分だと思う。

basicConfig
import logging
logging.basicConfig(level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
filename="app.log",
filemode='a')
logger = logging.getLogger("test")
def main():
logger.info('level:info')
if __name__ == "__main__":
main()
# 実行結果(ファイルに出力)
# 2024-08-03 01:03:31,124 - INFO - level:info

loggerは作ること。
basicConfigはログ設定を一度だけ行うための関数で、
基本的にはアプリケーション全体の最初の設定で使うのみ。
設定が一度行われた後に再度 basicConfig() を呼び出しても、設定は上書きされない。

例外情報とスタックトレースをログに出す

スタックトレース
import logging
logging.basicConfig(level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
filename="app.log",
filemode='a')
logger = logging.getLogger("test")
def main():
try:
a =1 / 0
except Exception:
logger.error("error message", exc_info=True)
if __name__ == "__main__":
main()

exc_info=Trueを設定するだけ
実行するとapp.logに

bash
2024-08-03 12:55:55,369 - ERROR - error message
Traceback (most recent call last):
File "c:\develop\01_TechSamples\Python\PythonOrg\pythonorg-logging\app\log5.py", line 13, in main
a =1 / 0
~~^~~
ZeroDivisionError: division by zero

のようにスタックトレースと例外情報が出力されるようになる。

まとめ

Pythonのloggingの使い方の基本をまとめみた。
初期の設定方法がいろいろあるので作るアプリケーションによって
使い分ける必要がありそうだ。

またloggingはPythonのデコレーターと併用すると使いやすい。
デコレーターについては下記記事でまとめているので良ければ参考にしてください

参考

関連記事

新着記事

top