Pythonでソースコードをデバッグするとき、「print()メソッド」を使用していないでしょうか。小規模なプログラムならprint()でも十分ですが、規模が大きくなると機能性・柔軟性などで不満が生じがちになります。
そこで、Pythonの「loggingモジュール」を活用すれば、プログラム全体の流れを把握しながら最適化やデバッグができるようになります。しかし、loggingモジュールにはさまざまな設定や機能があるため、「使い方がわからない」部分も多いでしょう。
本記事では、Pythonのloggingモジュールの特徴や使い方に加えて、活用例も解説します。ぜひ参考にして、プログラム開発を効率化してみてください。
目次
Pythonの「loggingモジュール」とは、プログラムの「ログ」を出力するためのものです。Pythonのloggingモジュールについて学ぶ前に、まずは「ログ(log)」や「ロギング(logging)」という言葉の意味を確認しておきましょう。
「ログ(log)」とは、プログラムの実行中に起きたことの記録を指します。
ログというと、「エラー発生の記録」のようなイメージがあるかもしれませんが、実は正常時・異常時のさまざまな情報を記録することがログの役割です。たとえば、頻繁に変動する変数の値を細かく記録したり、エラーが発生したファイルやメソッドの情報を出力したりします。
ログを記録することで、プログラムが思うように動作しないときや、エラーが発生したときに原因を特定しやすくなります。また、ログを見ることで高速化・最適化のヒントを得ることも可能です。プログラムの開発やデバッグをスムーズに進めるためにも、ぜひ活用したいのがログです。
ログを記録・出力することを「ロギング(logging)」と呼びます。
簡易的なロギングであれば、print()メソッドでも可能です。たとえば、あるメソッドを呼び出したときに思うような結果が得られないとき、print()は便利です。呼び出し前後およびメソッド内にprint()を追加し、問題となっている変数の値を表示すれば、どの部分の動作に問題があるか把握できます。
しかし、大規模なプロジェクトになってくると、print()の数も膨大になるので手間がかかります。リリース前にprint()を削除し忘れて、トラブルの原因になることもあるかもしれません。Pythonのloggingモジュールを使えば、こうした課題を解決し、より高機能で柔軟なロギングができるようになります。
Pythonのloggingモジュールでロギングすることにより、主に以下の3つのメリットが得られます。
loggingモジュールを活用すると、「デバッグ用のログ出力」と「ユーザーへのメッセージ」を分離できるようになります。
print()によるロギングの大きなデメリットは、状況に応じたログの表示・非表示の切り替えが難しいことです。たとえば、開発段階ではさまざまなログを表示させたいものの、リリース時は非表示にしたい場合はどうでしょうか。大量のprint()を削除することは困難ですし、フラグを設定してif文で表示・非表示を切り替えるのも厄介です。
また、開発者だけに見せたいログと、ユーザーにも表示したいエラーメッセージを分けたい場合はどうでしょうか。こちらは、ログの重要度によって表示・非表示を切り替えたり、デバッグとリリース時のどちらかを判別したりする仕組みづくりが必須です。
つまり、print()のような標準的な機能では、柔軟なロギングは困難だということです。そこで、Pythonのloggingモジュールを活用することで、こうした問題を簡単に解決できます。
loggingモジュールでは、「Error」や「Debug」のようにログの種類やレベル分けができます。前述したように、ロギングではデバッグ用にさまざまな情報を表示したい場合と、エラー発生時にメッセージを伝えたい場合があります。
loggingモジュールを活用すると、メッセージの設定時にログの重要度を設定することが可能です。さらに、どのレベル以上のメッセージを表示させるかを指定できるため、状況に応じた柔軟なロギングが可能となります。
loggingモジュールでは、ログのメッセージ出力時にフォーマットを指定できます。たとえば、現在の日時やファイル名・ソースコードの行数など、バグやエラーの特定に役立つ情報を出力できます。これらの機能を活用すれば、より柔軟なログ出力ができるようになり、プログラム開発の効率化が図れるでしょう。
Pythonのloggingモジュールを活用するためには、前提知識として「ログレベル」について理解しておく必要があります。前述したように、loggingモジュールではログメッセージの設定時に、「Debug」や「Error」など重要度を設定可能です。Pythonのloggingモジュールで出力できるログは、以下の5つのレベルに分類されています。
ログレベル | レベル番号 | 概要 |
---|---|---|
logging.NOTSET | 0 | 変数値や詳細情報などすべての情報 |
logging.DEBUG | 10 | 基本的な動作確認のためのデバッグ情報 |
logging.INFO | 20 | 正常動作のプロセスを示す記録 |
logging.WARNING | 30 | 警告を出したい動作の記録 |
logging.ERROR | 40 | エラーなどの問題動作の記録 |
logging.CRITICAL | 50 | プログラムが停止するような致命的エラーの情報 |
これらのログレベルは、「プログラムに与える悪影響」によって分類されています。loggingモジュールでは、どのレベル以上のログを残すか選択可能です。その設定値以上のレベルのログであれば、開発者やユーザーに表示されます。
そのため、リリース時に表示レベルを変えるだけで、不要なメッセージを非表示にできます。たとえば、開発中はDebugですべての情報を出力し、リリース時はWARNING以上の情報を出力するなどです。
Pythonのloggingモジュールの基本的な使い方について、以下の4つのポイントに分けて解説します。
まずは「loggingモジュール」をインポートし、以下の構文でロギング関数・メソッドを使用しましょう。
「logging.ログレベル」とありますが、これは前述したログレベルのうち、「debug」「info」「warning」「error」「critical」の5種類から選べます。実際に、以下のサンプルコードで確認してみましょう。
なお、本記事のソースコードの実行時に「SyntaxError」というエラーが出るときは、「# coding: UTF-8」の部分を「# coding: Shift_JIS」に書き換えてください。
//サンプルプログラム
# coding: UTF-8 # 「loggingモジュール」をインポートする import logging # logging.debug()で「debug」レベルのログを表示する logging.debug("ログ:debug") # logging.info()で「info」レベルのログを表示する logging.info("ログ:info") # logging.warning()で「warning」レベルのログを表示する logging.warning("ログ:warning") # logging.error()で「error」レベルのログを表示する logging.error("ログ:error") # logging.critical()で「critical」レベルのログを表示する logging.critical("ログ:critical")
//実行結果
上記の実行結果では、「warning」「error」「critical」の3つのメソッドしか表示されていません。これは、loggingモジュールはデフォルト設定で「WARNING」以上のログレベルしか、表示されない仕様になっているためです。表示対象を拡大したい場合は、ログレベルの変更が必要です。
ログレベルの表示対象を変更するためには、「logging.basicConfig()メソッド」を使用します。この関数は、loggingモジュールのさまざまな設定を変更するためのものです。以下の構文で「level引数」にログレベルを引き渡すことで、ロギングの範囲を変更できます。
以下の例では、出力対象のログレベルを「DEBUG」に設定し、すべてのログを表示できるようにしています。
//サンプルプログラム
# coding: UTF-8 # 「loggingモジュール」をインポートする import logging # 出力対象のログレベルを「DEBUG」に変更する logging.basicConfig(level = logging.DEBUG) # logging.debug()で「debug」レベルのログを表示する logging.debug("ログ:debug") # logging.info()で「info」レベルのログを表示する logging.info("ログ:info") # logging.warning()で「warning」レベルのログを表示する logging.warning("ログ:warning") # logging.error()で「error」レベルのログを表示する logging.error("ログ:error") # logging.critical()で「critical」レベルのログを表示する logging.critical("ログ:critical")
//実行結果
これまでのロギングでは、関数に指定したログメッセージしか表示されていませんでした。しかし、それではログの意味や内容を把握しにくいため、「フォーマット」も設定することが重要です。ロギングのフォーマットはログレベルと同じく、「logging.basicConfig()メソッド」で設定できます。フォーマットを設定する構文は以下のとおりです。
「format引数」にフォーマットを指定すれば、自由な形式でログを残せます。なお、フォーマットには以下の要素を指定できるので、見やすい形式に整えてみましょう。
フォーマット | 概要 |
---|---|
%(asctime)s | 実行時刻 |
%(filename)s | ファイル名 |
%(funcName)s | 関数名 |
%(levelname)s | ログレベル名 |
%(lineno)d | 行番号 |
%(message)s | ログメッセージ |
%(module)s | モジュール名 |
%(name)s | ロガーの名称 |
%(process)d | プロセスID |
%(thread)d | スレッドID |
これらのフォーマットを活用して、以下の例のように表示内容を変えてみましょう。
//サンプルプログラム
# coding: UTF-8 # 「loggingモジュール」をインポートする import logging # logging_test()関数|ロギングのテストを行う def logging_test(): # 出力対象のログレベルを「DEBUG」に変更し、フォーマットを設定する logging.basicConfig(level = logging.DEBUG, format = "%(asctime)s - %(filename)s(%(lineno)d行目) - %(funcName)s - ロガー「%(name)s」 - %(levelname)s(%(message)s)") # logging.debug()で「debug」レベルのログを表示する logging.debug("ログ:debug") # logging.info()で「info」レベルのログを表示する logging.info("ログ:info") # logging.warning()で「warning」レベルのログを表示する logging.warning("ログ:warning") # logging.error()で「error」レベルのログを表示する logging.error("ログ:error") # logging.critical()で「critical」レベルのログを表示する logging.critical("ログ:critical") # main()関数|プログラムのエントリーポイント if __name__ == "__main__": # logging_test()関数を呼び出す logging_test()
//実行結果
上記のサンプルプログラムでは、「現在日時」「ファイル名」「行数」「関数・メソッド名」「ロガー名」「ログレベル」「メッセージ」の順番で、ログを表示しています。必要な情報を取捨選択して表示することで、わかりやすいログメッセージを構成できるでしょう。
Pythonのloggingモジュールには、ログを自動的にファイル出力する機能もあります。こちらも「logging.basicConfig()メソッド」を使用し、以下の構文で設定可能です。
「filename引数」に出力したいファイル名を指定しておけば、あとはロギング関数を呼び出すだけで、すべてのログがファイル出力されます。ただし、この関数で出力ファイルを指定したあとは、コンソール画面にログが表示されなくなります。また、指定したログレベルより低いログは、ファイル出力されないので注意が必要です。詳細を以下のサンプルコードで確認しましょう。
//サンプルプログラム
# coding: UTF-8 # 「loggingモジュール」をインポートする import logging # logging_test()関数|ロギングのテストを行う def logging_test(): # 出力対象のログレベルを「DEBUG」に変更し、フォーマットを設定する logging.basicConfig(level = logging.DEBUG, format = "%(asctime)s - %(filename)s(%(lineno)d行目) - %(funcName)s - ロガー「%(name)s」 - %(levelname)s(%(message)s)", filename = "Test.log") # logging.debug()で「debug」レベルのログを表示する logging.debug("ログ:debug") # logging.info()で「info」レベルのログを表示する logging.info("ログ:info") # logging.warning()で「warning」レベルのログを表示する logging.warning("ログ:warning") # logging.error()で「error」レベルのログを表示する logging.error("ログ:error") # logging.critical()で「critical」レベルのログを表示する logging.critical("ログ:critical") # main()関数|プログラムのエントリーポイント if __name__ == "__main__": # logging_test()関数を呼び出す logging_test()
//実行結果
プロジェクトフォルダ
ログファイルの中身
この機能を利用すれば、open()関数などでファイル出力を実装する必要がないため、スムーズなロギングが可能です。こうした便利さも、Pythonでloggingモジュールを使用する大きなメリットだといえるでしょう。
これまでのサンプルプログラムでは、すべてのログがひとつの「logger(ロガー)」で管理されていました。loggerとは、これまで使用してきたインスタンスのことです。loggingクラスは、そのまま使うと「root」というロガーで全ログが出力されるため、モジュールやクラスごとにログを分類できません。
そこで、以下の構文でloggingクラスのインスタンスを取得するようにすると、より便利なログを出力できるようになります。
logging.getLogger()メソッドの引数にロガーの名称を指定すると、ロガーのインスタンスを生成できます。このロガーを介してロギングを行うことで、複数のログを分離して出力可能です。ただし、個別のロガーを使用する場合は、ログレベルやフォーマットの出力方法が異なるので注意が必要です。それぞれの手順について、以下の5つのステップに分けて見ていきましょう。
まずは、loggerインスタンスを生成して、単純なログを出力してみましょう。使い方そのものは、先ほどのルートロガーを使用する場合と同じ。ただし、logging.getLogger()関数を使うことや、生成したlogger変数を介してメッセージを表示する点が異なります。
//サンプルプログラム
# coding: UTF-8 # 「loggingモジュール」をインポートする import logging # logging_test()関数|ロギングのテストを行う def logging_test(): # ロガーのインスタンスを取得する logger = logging.getLogger("Logger") # logger.debug()で「debug」レベルのログを表示する logger.debug("ログ:debug") # logger.info()で「info」レベルのログを表示する logger.info("ログ:info") # logger.warning()で「warning」レベルのログを表示する logger.warning("ログ:warning") # logger.error()で「error」レベルのログを表示する logger.error("ログ:error") # logger.critical()で「critical」レベルのログを表示する logger.critical("ログ:critical") # main()関数|プログラムのエントリーポイント if __name__ == "__main__": # logging_test()関数を呼び出す logging_test()
//実行結果
ロガーごとに個別のログレベルを設定する場合は、loggerインスタンスの「setLevel()メソッド」を呼び出してログレベルを選択します。次に「logging.StreamHandler()メソッド」で、コンソールにログを出力するための「ハンドラ」を生成します。そのうえで、ロガーの「addHandler()メソッド」を呼び出して、ロガーとハンドラを関連付ければOKです。
//サンプルプログラム
# coding: UTF-8 # 「loggingモジュール」をインポートする import logging # logging_test_1()関数|1つ目のロガーのテストを行う def logging_test_1(): # ロガーのインスタンスを取得する logger = logging.getLogger("Logger_1") # ログレベルを設定する logger.setLevel(logging.DEBUG) # コンソールにログを出力するための「ハンドラ」を生成する stream_handler = logging.StreamHandler() # ハンドラをロガーと関連付ける logger.addHandler(stream_handler) # 生成したロガーで各レベルのログを表示する log_messages(logger) # logging_test_2()関数|2つ目のロガーのテストを行う def logging_test_2(): # ロガーのインスタンスを取得する logger = logging.getLogger("Logger_2") # ログレベルを設定する logger.setLevel(logging.ERROR) # コンソールにログを出力するための「ハンドラ」を生成する stream_handler = logging.StreamHandler() # ハンドラをロガーと関連付ける logger.addHandler(stream_handler) # 生成したロガーで各レベルのログを表示する log_messages(logger) def log_messages(logger): # logger.debug()で「debug」レベルのログを表示する logger.debug("ログ:debug") # logger.info()で「info」レベルのログを表示する logger.info("ログ:info") # logger.warning()で「warning」レベルのログを表示する logger.warning("ログ:warning") # logger.error()で「error」レベルのログを表示する logger.error("ログ:error") # logger.critical()で「critical」レベルのログを表示する logger.critical("ログ:critical") # main()関数|プログラムのエントリーポイント if __name__ == "__main__": # logging_test_1()関数を呼び出す print("1つ目のロガー") logging_test_1() # logging_test_2()関数を呼び出す print("\n2つ目のロガー") logging_test_2()
//実行結果
ロガーごとに個別のフォーマットを指定する場合は、まず「logging.Formatter()メソッド」でフォーマッターを生成し、ハンドラーの「setFormatter()メソッド」で設定します。詳細は以下のサンプルコードのとおりです。
//サンプルプログラム
# coding: UTF-8 # 「loggingモジュール」をインポートする import logging # logging_test_1()関数|1つ目のロガーのテストを行う def logging_test_1(): # ロガーのインスタンスを取得する logger = logging.getLogger("Logger_1") # ログレベルを設定する logger.setLevel(logging.DEBUG) # コンソールにログを出力するための「ハンドラ」を生成する stream_handler = logging.StreamHandler() # ロガーのフォーマットを設定するための「フォーマッター」を生成する formatter = logging.Formatter("%(asctime)s - %(filename)s(%(lineno)d行目) - %(funcName)s - ロガー「%(name)s」 - %(levelname)s(%(message)s)") # ハンドラにフォーマッターを設定する stream_handler.setFormatter(formatter) # ハンドラをロガーと関連付ける logger.addHandler(stream_handler) # 生成したロガーで各レベルのログを表示する log_messages(logger) # logging_test_2()関数|2つ目のロガーのテストを行う def logging_test_2(): # ロガーのインスタンスを取得する logger = logging.getLogger("Logger_2") # ログレベルを設定する logger.setLevel(logging.ERROR) # コンソールにログを出力するための「ハンドラ」を生成する stream_handler = logging.StreamHandler() # ロガーのフォーマットを設定するための「フォーマッター」を生成する formatter = logging.Formatter("%(asctime)s - %(filename)s(%(lineno)d行目) - %(funcName)s - ロガー「%(name)s」 - %(levelname)s(%(message)s)") # ハンドラにフォーマッターを設定する stream_handler.setFormatter(formatter) # ハンドラをロガーと関連付ける logger.addHandler(stream_handler) # 生成したロガーで各レベルのログを表示する log_messages(logger) def log_messages(logger): # logger.debug()で「debug」レベルのログを表示する logger.debug("ログ:debug") # logger.info()で「info」レベルのログを表示する logger.info("ログ:info") # logger.warning()で「warning」レベルのログを表示する logger.warning("ログ:warning") # logger.error()で「error」レベルのログを表示する logger.error("ログ:error") # logger.critical()で「critical」レベルのログを表示する logger.critical("ログ:critical") # main()関数|プログラムのエントリーポイント if __name__ == "__main__": # logging_test_1()関数を呼び出す print("1つ目のロガー") logging_test_1() # logging_test_2()関数を呼び出す print("\n2つ目のロガー") logging_test_2()
//実行結果
上記の例では、2つのロガーを生成して、個別にメッセージを出力しています。異なるログレベルやフォーマットを設定できるため、シーンに応じて使い分けたいときに便利です。
ログをコンソールではなくファイルに出力する場合は、先ほどの「StreamHandler」ではなく「FileHandler」を使用します。「logging.FileHandler()メソッド」の引数に、出力したいファイル名を指定しましょう。ロガーごとに出力先を変えれば、個別のログファイルとして出力可能です。
//サンプルプログラム
# coding: UTF-8 # 「loggingモジュール」をインポートする import logging # logging_test_1()関数|1つ目のロガーのテストを行う def logging_test_1(): # ロガーのインスタンスを取得する logger = logging.getLogger("Logger_1") # ログレベルを設定する logger.setLevel(logging.DEBUG) # ファイルにログを出力するための「ハンドラ」を生成する file_handler = logging.FileHandler("Logger_1.log") # ロガーのフォーマットを設定するための「フォーマッター」を生成する formatter = logging.Formatter("%(asctime)s - %(filename)s(%(lineno)d行目) - %(funcName)s - ロガー「%(name)s」 - %(levelname)s(%(message)s)") # ハンドラにフォーマッターを設定する file_handler.setFormatter(formatter) # ハンドラをロガーと関連付ける logger.addHandler(file_handler) # 生成したロガーで各レベルのログを表示する log_messages(logger) # logging_test_2()関数|2つ目のロガーのテストを行う def logging_test_2(): # ロガーのインスタンスを取得する logger = logging.getLogger("Logger_2") # ログレベルを設定する logger.setLevel(logging.ERROR) # ファイルにログを出力するための「ハンドラ」を生成する file_handler = logging.FileHandler("Logger_2.log") # ロガーのフォーマットを設定するための「フォーマッター」を生成する formatter = logging.Formatter("%(asctime)s - %(filename)s(%(lineno)d行目) - %(funcName)s - ロガー「%(name)s」 - %(levelname)s(%(message)s)") # ハンドラにフォーマッターを設定する file_handler.setFormatter(formatter) # ハンドラをロガーと関連付ける logger.addHandler(file_handler) # 生成したロガーで各レベルのログを表示する log_messages(logger) def log_messages(logger): # logger.debug()で「debug」レベルのログを表示する logger.debug("ログ:debug") # logger.info()で「info」レベルのログを表示する logger.info("ログ:info") # logger.warning()で「warning」レベルのログを表示する logger.warning("ログ:warning") # logger.error()で「error」レベルのログを表示する logger.error("ログ:error") # logger.critical()で「critical」レベルのログを表示する logger.critical("ログ:critical") # main()関数|プログラムのエントリーポイント if __name__ == "__main__": # logging_test_1()関数を呼び出す logging_test_1() # logging_test_2()関数を呼び出す logging_test_2()
//実行結果
Logger_1.log
Logger_2.log
ログのレベルに応じて、コンソール出力とファイル出力を使い分けたいこともあるでしょう。たとえば、DEBUG以上はコンソールに表示して、ERROR以上のみファイル出力するなどです。
この場合は、StreamHandlerとFileHandlerを生成し、両者にログレベルとフォーマッターを設定してからロガーと関連付ければOKです。ただし、StreamHandlerのログレベルは、loggerインスタンスから設定する必要があります。
//サンプルプログラム
# coding: UTF-8 # 「loggingモジュール」をインポートする import logging # logging_test()関数|ロガーのテストを行う def logging_test(): # ロガーのインスタンスを取得する logger = logging.getLogger("Logger_1") # ログレベルを設定する logger.setLevel(logging.DEBUG) # コンソール画面にログを出力するための「ストリームハンドラ」を生成する stream_handler = logging.StreamHandler() # ファイルにログを出力するための「ファイルハンドラ」を生成する file_handler = logging.FileHandler("Logger.log") # ロガーのフォーマットを設定するための「フォーマッター」を生成する formatter = logging.Formatter("%(asctime)s - %(filename)s(%(lineno)d行目) - %(funcName)s - ロガー「%(name)s」 - %(levelname)s(%(message)s)") # ストリームハンドラにフォーマッターを設定する stream_handler.setFormatter(formatter) # ファイルハンドラにログレベルを設定する file_handler.setLevel(logging.ERROR) # ファイルハンドラにフォーマッターを設定する file_handler.setFormatter(formatter) # ストリームハンドラをロガーと関連付ける logger.addHandler(stream_handler) # ファイルハンドラをロガーと関連付ける logger.addHandler(file_handler) # 生成したロガーで各レベルのログを表示する log_messages(logger) def log_messages(logger): # logger.debug()で「debug」レベルのログを表示する logger.debug("ログ:debug") # logger.info()で「info」レベルのログを表示する logger.info("ログ:info") # logger.warning()で「warning」レベルのログを表示する logger.warning("ログ:warning") # logger.error()で「error」レベルのログを表示する logger.error("ログ:error") # logger.critical()で「critical」レベルのログを表示する logger.critical("ログ:critical") # main()関数|プログラムのエントリーポイント if __name__ == "__main__": # logging_test()関数を呼び出す logging_test()
//実行結果
コンソール画面
出力ログファイル
これまで解説したように、ソースコード上でログのフォーマットなどを設定すると、どうしてもソースコードが冗長になりがちです。そこで、「Configファイル」であらかじめ細かいフォーマットを設定しておくと、ソースコードがシンプルになります。Configファイルの使い方について、以下の3つのポイントに分けて解説します。
ロギングの設定を外部ファイルで行うためには、まずConfigファイルを作成する必要があります。テキストエディタを開き、以下の内容どおりに「Logging_Settings.ini」という名称で、Configファイルを作成・保存しましょう。
//Logging_Settings.ini
[loggers]
keys=root
[logger_root]
handlers=sh
[handlers]
keys=sh
[handler_sh]
class=StreamHandler
level=DEBUG
args=()
[formatters]
keys=
上記の内容が、Configファイルの最小構成となります。[]で始まるのは項目名で、「_」以降には「keys」に設定した名称を入れます。Configファイルの書き方や項目については、後ほど詳しく解説するので、まずはサンプルどおりの内容で作成して、動作内容に慣れていきましょう。
なお、Configファイル内に日本語を入力すると、エンコーディングの関係上エラーになることがあるので注意が必要です。
//サンプルプログラム
# coding: UTF-8 # 「loggingモジュール」をインポートする import logging # logging()関数|ロギングを行う def logging(): # 「logging.configモジュール」をインポートする import logging.config # Configファイルを読み込む logging.config.fileConfig("Logging_Settings.ini") # ロガーを生成する logger = logging.getLogger() # 生成したロガーで各レベルのログを表示する log_messages(logger) def log_messages(logger): # logger.debug()で「debug」レベルのログを表示する logger.debug("ログ:debug") # logger.info()で「info」レベルのログを表示する logger.info("ログ:info") # logger.warning()で「warning」レベルのログを表示する logger.warning("ログ:warning") # logger.error()で「error」レベルのログを表示する logger.error("ログ:error") # logger.critical()で「critical」レベルのログを表示する logger.critical("ログ:critical") # main()関数|プログラムのエントリーポイント if __name__ == "__main__": # logging()関数を呼び出す logging()
//実行結果
コンソール画面
次に、ロガーのフォーマットをConfigファイルで指定する方法を解説します。以下のように、「Logging_Settings.ini」ファイルを編集しましょう。
//Logging_Settings.ini
[loggers]
keys=root
[logger_root]
level=DEBUG
handlers=sh, fh
[handlers]
keys=sh, fh
[handler_sh]
class=StreamHandler
level=DEBUG
formatter=fmt
args=()
[handler_fh]
class=FileHandler
level=ERROR
formatter=fmt
args=(“logger.log”, )
[formatters]
keys=fmt
[formatter_fmt]
class=logging.Formatter
format=%(asctime)s – %(filename)s(%(lineno)d) – %(funcName)s – “%(name)s” – %(levelname)s(%(message)s)
まずは、[formatters]項目の「key」にフォーマッター名を記載し、[formatter_フォーマッター名]という項目を追加します。そのうえで「format」には、ソースコードで指定したときと同じ形式で、フォーマットを指定します。[handler_sh]の「formatter」に、追加したフォーマッターを指定すると、ロガーにフォーマッターを適用することが可能です。先ほどと同じソースコードでプログラムを実行すると、以下のような実行結果となります。
//実行結果
コンソール画面
logger.log
Configファイルを変更することで、ハンドラーやフォーマッターなどを自由に設定できるので便利です。ソースコードを編集する必要がないため、開発やデバッグがスムーズに進みやすくなるでしょう。
Configファイルで複数のロガーを使い分ける場合は、「Logging_Settings.ini」を以下のように変更してみましょう。
//Logging_Settings.ini
[loggers]
keys=root, lg1, lg2
[logger_root]
level=DEBUG
handlers=
[logger_lg1]
level=DEBUG
handlers=sh1, fh1
qualname=lg1
[logger_lg2]
level=DEBUG
handlers=sh2, fh2
qualname=lg2
[handlers]
keys=sh, fh, sh1, fh1, sh2, fh2
[handler_sh]
class=StreamHandler
level=DEBUG
formatter=fmt
args=()
[handler_fh]
class=FileHandler
level=DEBUG
formatter=fmt
args=(“root.log”, )
[handler_sh1]
class=StreamHandler
level=DEBUG
formatter=fmt1
args=()
[handler_fh1]
class=FileHandler
level=ERROR
formatter=fmt1
args=(“logger_1.log”, )
[handler_sh2]
class=StreamHandler
level=INFO
formatter=fmt2
args=()
[handler_fh2]
class=FileHandler
level=CRITICAL
formatter=fmt2
args=(“logger_2.log”, )
[formatters]
keys=fmt, fmt1, fmt2
[formatter_fmt]
class=logging.Formatter
format=
[formatter_fmt1]
class=logging.Formatter
format=%(asctime)s – %(filename)s(%(lineno)d) – %(funcName)s – “%(name)s” – %(levelname)s(%(message)s)
[formatter_fmt2]
class=logging.Formatter
format=%(asctime)s – %(filename)s(%(lineno)d) – %(funcName)s – “%(name)s” – %(levelname)s(%(message)s)
[loggers]項目には、keys変数に「,」で区切って複数のロガー名を設定します。その下に[loggers_ロガー名]という項目を作っていきます。ハンドラーとフォーマッターも個別に作成し、それぞれをロガーで指定すればOKです。そのうえで、以下のサンプルコードを実行してみましょう。
//サンプルプログラム
# coding: UTF-8 # 「loggingモジュール」をインポートする import logging # logging()関数|ロギングを行う def logging(): # 「logging.configモジュール」をインポートする import logging.config # Configファイルを読み込む logging.config.fileConfig("Logging_Settings.ini") # 「lg1」ロガーを生成する logger_1 = logging.getLogger("lg1") # 「lg2」ロガーを生成する logger_2 = logging.getLogger("lg2") # 生成したロガーで各レベルのログを表示する log_messages(logger_1) log_messages(logger_2) def log_messages(logger): # logger.debug()で「debug」レベルのログを表示する logger.debug("ログ:debug") # logger.info()で「info」レベルのログを表示する logger.info("ログ:info") # logger.warning()で「warning」レベルのログを表示する logger.warning("ログ:warning") # logger.error()で「error」レベルのログを表示する logger.error("ログ:error") # logger.critical()で「critical」レベルのログを表示する logger.critical("ログ:critical") # main()関数|プログラムのエントリーポイント if __name__ == "__main__": # logging()関数を呼び出す logging()
//実行結果
コンソール画面
logger_1.log
logger_2.log
Configファイルの作成時に問題となりやすいのが、記述方法や構文でしょう。Configファイルに記載する必要がある項目は、以下のとおりです。それぞれの意味について、具体的な記載例を交えて解説します。
項目 | 変数 | 概要 | 記載例 |
---|---|---|---|
[loggers] | keys | ロガー名を列挙する | keys=root, lg1 |
[logger_ロガー名] | level | ログレベルを設定する | level=DEBUG |
handlers | 使用するハンドラーを列挙する | handlers=sh1, fh1 | |
qualname | ロガー名を指定する | qualname=lg1 | |
handlers | keys | ハンドラー名を列挙する | keys=sh1, fh1 |
handler_ハンドラー名 | class | コンソール出力は「StreamHandler」 ファイル出力は「FileHandler」 |
class=StreamHandler |
level | ログレベルを設定する | level=DEBUG | |
formatter | 使用するフォーマットを設定する | formatter=fmt | |
args | FileHandlerを使用する場合は、出力先ファイルを指定する | args=(“Info.log”, ) | |
formatters | keys | フォーマッター名を列挙する | keys=fmt, fmt1, fmt2 |
formatter_フォーマッター名 | class | 基本的には「logging.Formatter」を指定すればOK | class=logging.Formatter |
format | フォーマットを文字列で指定する | format=%(asctime)s – %(filename)s(%(lineno)d) – %(funcName)s – “%(name)s” – %(levelname)s(%(message)s) |
以上の項目を正しく記載すれば、Configファイルから自由にロギングの形式を設定できます。最初はなかなかうまくいかないかもしれませんが、先ほど紹介したサンプルを順番に追っていけば理解が深まるでしょう。
Pythonのloggingモジュールの活用例として、ごく簡易的な計算プログラムのロギング処理を紹介します。以下のようにConfigファイルを作成し、サンプルコードを実行しましょう。
//Logging_Settings.ini
[loggers]
keys=root, lg1, lg2
[logger_root]
level=DEBUG
handlers=
[logger_lg1]
level=DEBUG
handlers=sh
qualname=lg1
[logger_lg2]
level=DEBUG
handlers=sh, fh
qualname=lg2
[handlers]
keys=sh, fh
[handler_sh]
class=StreamHandler
level=DEBUG
formatter=fmt1
args=()
[handler_fh]
class=FileHandler
level=ERROR
formatter=fmt2
args=(“Info.log”, )
[formatters]
keys=fmt1, fmt2
[formatter_fmt1]
class=logging.Formatter
format=%(asctime)s – %(filename)s(%(lineno)d) – %(funcName)s – “%(name)s” – %(levelname)s(%(message)s)
[formatter_fmt2]
class=logging.Formatter
format=%(asctime)s – [%(levelname)s]%(message)s
//サンプルプログラム
# coding: UTF-8 # 「loggingモジュール」をインポートする import logging # Loggerクラス|ロガーを管理する class Logger: # コンストラクタ|ロガーを生成する def __init__(self): # 「logging.configモジュール」をインポートする import logging.config # Configファイルを読み込む logging.config.fileConfig("Logging_Settings.ini") # 「lg1」ロガーを生成する self.logger_debug = logging.getLogger("lg1") # 「lg2」ロガーを生成する self.logger_error = logging.getLogger("lg2") # Programクラス|プログラムを処理する class Program: # コンストラクタ|ロガーを生成する def __init__(self): # Loggerクラスのインスタンスを生成する self.logger = Logger() # process()関数|計算プログラムを進行する def process(self): # 演算結果を格納するための変数 result = 0 # ユーザーが入力した値を格納するためのリスト self.nums = [] # ユーザーに2つの整数値の入力を求める for i in range(2): self.nums.append(self.get_input()) # 符号の入力を求める print("「+」「-」「*」「/」いずれかの符号を入力してください:", end = "") sign = input() # デバッグログでユーザーの入力値を表示する self.logger.logger_debug.debug(f"ユーザーが符号「{sign}」を入力") # 「+」の場合は足し算を行う if sign == "+": result = self.add() # 「-」の場合は引き算を行う elif sign == "-": result = self.sub() # 「*」の場合は掛け算を行う elif sign == "*": result = self.mul() # 「/」の場合は割り算を行う elif sign == "/": result = self.div() # それ以外の場合はエラーを表示する else: # エラーログを表示する self.logger.logger_error.error(f"不正な符号「{sign}」が入力されました") # 演算結果を表示する print(f"演算結果は「{result}」です") # get_input()関数|ユーザーの入力値を受け取る def get_input(self): # 変換結果を格納するための変数 result = 0 # 正常な値が入力されるまで繰り返す while True: # ユーザーに入力を求めるメッセージを表示する print("整数値を入力してください:", end = "") string = input() # デバッグログでユーザーの入力値を表示する self.logger.logger_debug.debug(f"ユーザーが値「{string}」を入力") try: # 整数値への変換を試す result = int(string) break except ValueError: # エラーログを表示する self.logger.logger_error.error(f"不正な値「{string}」が入力されました") pass # デバッグログでプロセスを表示する self.logger.logger_debug.debug(f"文字列から整数値への変換成功") # 変換結果を返す return result # add()関数|2つの値の足し算を行う def add(self): # デバッグログでプロセスを表示する self.logger.logger_debug.debug(f"足し算を実行") return self.nums[0] + self.nums[1] # sub()関数|2つの値の引き算を行う def sub(self): # デバッグログでプロセスを表示する self.logger.logger_debug.debug(f"引き算を実行") return self.nums[0] - self.nums[1] # mul()関数|2つの値の掛け算を行う def mul(self): # デバッグログでプロセスを表示する self.logger.logger_debug.debug(f"掛け算を実行") return self.nums[0] * self.nums[1] # div()関数|2つの値の割り算を行う def div(self): # デバッグログでプロセスを表示する self.logger.logger_debug.debug(f"割り算を実行") # 演算結果を格納するための変数 result = 0 # まず割り算を試行する try: result = self.nums[0] / self.nums[1] # 「ゼロ除算例外」が出た場合はエラーログを表示する except ZeroDivisionError: # エラーログを表示する self.logger.logger_error.error(f"「{self.nums[0]} / {self.nums[1]}」のゼロ除算が行われました") return result # main()関数|プログラムのエントリーポイント if __name__ == "__main__": # Programクラスのインスタンスを生成する program = Program() # プログラムを進行する program.process()
//実行結果
コンソール画面
「Info.log」の内容
上記のプログラムでは、ユーザーに2つの整数値の入力を求め、そのうえで足し算・引き算・掛け算・割り算いずれかの符号を指定してもらいます。
整数値以外が入力された場合は、文字列を整数値に変換する段階でエラーメッセージのログを表示します。さらに、ゼロ除算の例外発生時や不正な符号が入力されたときなども、エラーログを出力していることがポイントです。
また、エラーログとは別にデバッグ用のテキストとして、随所にプロセスを記録するためのデバッグログ表示を行っています。ただし、エラーログはコンソールとファイル両方に出力されるのに対し、デバッグログはコンソールのみに出力される仕様となっています。
このように、複数のロガーを使い分けることで、開発者とユーザーが確認できるログを明確に区別できるので便利です。デバッグ後にログ出力部分を削除する必要はありません。
Pythonのloggingモジュールを活用すると、さまざまな場面で役立つログ出力の仕組みを構築できます。
ログのレベルには、「DEBUG」「INFO」「WARNING」「ERROR」「CRITICAL」の5種類があり、どのレベル以上をログ出力するか設定できます。さらに、事前にファイル名を指定しておけば、ログファイルの自動出力も可能です。
ロギングのフォーマット設定などはソースコード上でも行えますが、Configファイルを作成して外部から行うと、ソースコードを簡潔化できます。今回紹介した知識や実践例を活用して、Pythonのloggingモジュールでスムーズなプログラム開発を実現しましょう。
2024.06.17
子供におすすめのプログラミングスクール10選!学習メリットや教室選びのコツも紹介
#プログラミングスクール
2022.01.06
【完全版】大学生におすすめのプログラミングスクール13選!選ぶコツも詳しく解説
#プログラミングスクール
2024.01.26
【未経験でも転職可】30代におすすめプログラミングスクール8選!
#プログラミングスクール
2024.01.26
初心者必見!独学のJava学習方法とおすすめ本、アプリを詳しく解説
#JAVA
2024.01.26
忙しい社会人におすすめプログラミングスクール15選!失敗しない選び方も詳しく解説
#プログラミングスクール
2022.01.06
【無料あり】大阪のおすすめプログラミングスクール14選!スクール選びのコツも紹介
#プログラミングスクール
2024.01.26
【目的別】東京のおすすめプログラミングスクール20選!スクール選びのコツも徹底解説
#プログラミングスクール
2024.01.26
【無料あり】福岡のおすすめプログラミングスクール13選!選び方も詳しく解説
#プログラミングスクール
2024.01.26
【徹底比較】名古屋のおすすめプログラミングスクール13選!選び方も詳しく解説
#プログラミングスクール
2024.01.26
【徹底比較】おすすめのプログラミングスクール18選!失敗しない選び方も徹底解説
#プログラミングスクール