Python 3でロギングを使用する方法

前書き

loggingモジュールは標準のPythonライブラリの一部であり、ソフトウェアの実行中に発生するイベントの追跡を提供します。 ロギングコールをコードに追加して、発生したイベントを示すことができます。

loggingモジュールでは、アプリケーションの操作に関連するイベントを記録する診断ログと、分析のためにユーザーのトランザクションのイベントを記録する監査ログの両方を使用できます。 特に、イベントをファイルに記録するために使用されます。

loggingモジュールを使用する理由

loggingモジュールは、プログラム内で発生するイベントの記録を保持し、ソフトウェアの実行時に発生するイベントのいずれかに関連する出力を確認できるようにします。

コード全体でprint()ステートメントを使用することで、イベントが発生していることを確認することに慣れている場合があります。 print()ステートメントdoesは、問題を解決するためにコードをデバッグするための基本的な方法を提供します。 コード全体にprint()ステートメントを埋め込むと、実行フローとプログラムの現在の状態を追跡できますが、このソリューションは、いくつかの理由でloggingモジュールを使用するよりも保守が難しいことがわかります。

  • 2つが混在しているため、デバッグ出力と通常のプログラム出力を区別することが難しくなります。

  • コード全体に分散されたprint()ステートメントを使用する場合、デバッグ出力を提供するステートメントを無効にする簡単な方法はありません。

  • デバッグが終了すると、すべてのprint()ステートメントを削除することが困難になります

  • すぐに利用できる診断情報を含むログレコードはありません

コードでloggingモジュールを使用する習慣を身に付けることをお勧めします。これは、単純なPythonスクリプトを超えて成長し、デバッグへの持続可能なアプローチを提供するアプリケーションに適しているためです。

ログは時間の経過とともに動作とエラーを表示できるため、アプリケーション開発プロセスで行われていることの全体像をよりよく把握できます。

コンソールへのデバッグメッセージの印刷

print()ステートメントを使用してプログラムで何が起こっているかを確認することに慣れている場合は、defines a classを実行し、次のようなオブジェクトをインスタンス化するプログラムを確認することに慣れている可能性があります。

pizza.py

class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        print("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        print("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        print("Ate {} pizza(s)".format(quantity, self.name))

pizza_01 = Pizza("artichoke", 15)
pizza_01.make()
pizza_01.eat()

pizza_02 = Pizza("margherita", 12)
pizza_02.make(2)
pizza_02.eat()

上記のコードには、Pizzaクラスのオブジェクトのnamepriceを定義する__init__メソッドがあります。 次に、2つのメソッドがあります。1つはピザを作るためのmake()と呼ばれ、もう1つはピザを食べるためのeat()と呼ばれます。 これらの2つのメソッドは、1で初期化されるquantityのパラメーターを受け取ります。

それではプログラムを実行しましょう:

python pizza.py

次の出力が表示されます。

OutputPizza created: artichoke ($15)
Made 1 artichoke pizza(s)
Ate 1 pizza(s)
Pizza created: margherita ($12)
Made 2 margherita pizza(s)
Ate 1 pizza(s)

print()ステートメントを使用すると、コードが機能していることを確認できますが、代わりにloggingモジュールを使用してこれを行うことができます。

コード全体でprint()ステートメントを削除またはコメントアウトし、ファイルの先頭にimport loggingを追加しましょう。

pizza.py

import logging


class Pizza():
    def __init__(self, name, value):
        self.name = name
        self.value = value
...

loggingモジュールにはWARNINGdefault levelがあります。これはDEBUGより上のレベルです。 この例ではデバッグにloggingモジュールを使用するため、logging.DEBUGのレベルがコンソールに情報を返すように構成を変更する必要があります。 これを行うには、import statementの下に次の行を追加します。

pizza.py

import logging

logging.basicConfig(level=logging.DEBUG)


class Pizza():
...

このレベルのlogging.DEBUGは、しきい値を設定するために上記のコードで参照する定数整数値を参照します。 DEBUGのレベルは10です。

ここで、代わりにすべてのprint()ステートメントをlogging.debug()ステートメントに置き換えます。 定数であるlogging.DEBUGとは異なり、logging.debug()loggingモジュールのメソッドです。 このメソッドを使用する場合、以下に示すように、print()に渡されたものと同じstringを使用できます。

pizza.py

import logging

logging.basicConfig(level=logging.DEBUG)


class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        logging.debug("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        logging.debug("Ate {} pizza(s)".format(quantity, self.name))

pizza_01 = Pizza("artichoke", 15)
pizza_01.make()
pizza_01.eat()

pizza_02 = Pizza("margherita", 12)
pizza_02.make(2)
pizza_02.eat()

この時点で、python pizza.pyコマンドを使用してプログラムを実行すると、次の出力が表示されます。

OutputDEBUG:root:Pizza created: artichoke ($15)
DEBUG:root:Made 1 artichoke pizza(s)
DEBUG:root:Ate 1 pizza(s)
DEBUG:root:Pizza created: margherita ($12)
DEBUG:root:Made 2 margherita pizza(s)
DEBUG:root:Ate 1 pizza(s)

ログメッセージには、重大度レベルDEBUGと、Pythonモジュールのレベルを表す単語rootが埋め込まれています。 loggingモジュールは、異なる名前のロガーの階層で使用できるため、モジュールごとに異なるロガーを使用できます。

たとえば、異なる名前と異なる出力を持つ異なるロガーに等しいロガーを設定できます。

logger1 = logging.getLogger("module_1")
logger2 = logging.getLogger("module_2")

logger1.debug("Module 1 debugger")
logger2.debug("Module 2 debugger")
OutputDEBUG:module_1:Module 1 debugger
DEBUG:module_2:Module 2 debugger

loggingモジュールを使用してメッセージをコンソールに出力する方法を理解したので、次にloggingモジュールを使用してメッセージをファイルに出力する方法に移ります。

メッセージをファイルに記録する

loggingモジュールの主な目的は、メッセージをコンソールではなくファイルに記録することです。 メッセージのファイルを保持することで、コードをどのように変更する必要があるかを確認できるように、時間をかけてデータを参照して定量化できます。

ファイルへのロギングを開始するには、filenameメソッドを変更してfilenameパラメーターを含めることができます。 この場合、ファイル名をtest.logと呼びましょう。

pizza.py

import logging

logging.basicConfig(filename="test.log", level=logging.DEBUG)


class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        logging.debug("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        logging.debug("Ate {} pizza(s)".format(quantity, self.name))

pizza_01 = Pizza("artichoke", 15)
pizza_01.make()
pizza_01.eat()

pizza_02 = Pizza("margherita", 12)
pizza_02.make(2)
pizza_02.eat()

上記のコードは前のセクションと同じですが、印刷するログのファイル名を追加した点が異なります。 python pizza.pyコマンドを使用してコードを実行すると、ディレクトリにtest.logという新しいファイルが作成されます。

nano(または選択したテキストエディタ)でtest.logファイルを開きましょう:

nano test.log

ファイルが開くと、次のように表示されます。

test.log

DEBUG:root:Pizza created: artichoke ($15)
DEBUG:root:Made 1 artichoke pizza(s)
DEBUG:root:Ate 1 pizza(s)
DEBUG:root:Pizza created: margherita ($12)
DEBUG:root:Made 2 margherita pizza(s)
DEBUG:root:Ate 1 pizza(s)

これは、前のセクションで検出したコンソール出力と似ていますが、test.logファイルにある点が異なります。

CTRL +xでファイルを閉じ、pizza.pyファイルに戻って、コードを変更できるようにします。

コードの大部分は同じままにしますが、2つのピザインスタンスpizza_01pizza_02のパラメータを変更します。

pizza.py

import logging

logging.basicConfig(filename="test.log", level=logging.DEBUG)


class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        logging.debug("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        logging.debug("Ate {} pizza(s)".format(quantity, self.name))

# Modify the parameters of the pizza_01 object
pizza_01 = Pizza("Sicilian", 18)
pizza_01.make(5)
pizza_01.eat(4)

# Modify the parameters of the pizza_02 object
pizza_02 = Pizza("quattro formaggi", 16)
pizza_02.make(2)
pizza_02.eat(2)

これらの変更を加えて、python pizza.pyコマンドを使用してプログラムを再度実行してみましょう。

プログラムが実行されたら、nanoを使用してtest.logファイルを再度開くことができます。

nano test.log

ファイルを見ると、いくつかの新しい行が追加され、プログラムが最後に実行された前の行が保持されていることがわかります。

test.log

DEBUG:root:Pizza created: artichoke ($15)
DEBUG:root:Made 1 artichoke pizza(s)
DEBUG:root:Ate 1 pizza(s)
DEBUG:root:Pizza created: margherita ($12)
DEBUG:root:Made 2 margherita pizza(s)
DEBUG:root:Ate 1 pizza(s)
DEBUG:root:Pizza created: Sicilian ($18)
DEBUG:root:Made 5 Sicilian pizza(s)
DEBUG:root:Ate 4 pizza(s)
DEBUG:root:Pizza created: quattro formaggi ($16)
DEBUG:root:Made 2 quattro formaggi pizza(s)
DEBUG:root:Ate 2 pizza(s)

この情報は確かに役立ちますが、LogRecord attributesを追加することで、ログをより有益なものにすることができます。 主に、LogRecordがいつ作成されたかを示す、人間が読み取れるタイムスタンプを追加します。

その属性をformatというパラメーターに追加して、表に示すように文字列%(asctime)sで参照できます。 さらに、DEBUGレベル名を保持するには、文字列%(levelname)sを含める必要があり、ロガーに印刷するように要求する文字列メッセージを保持するには、%(message)sを含めます。 以下に追加するコードに示すように、これらの各属性はcolonで区切られます。

pizza.py

import logging

logging.basicConfig(
    filename="test.log",
    level=logging.DEBUG,
    format="%(asctime)s:%(levelname)s:%(message)s"
    )


class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        logging.debug("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        logging.debug("Ate {} pizza(s)".format(quantity, self.name))

pizza_01 = Pizza("Sicilian", 18)
pizza_01.make(5)
pizza_01.eat(4)

pizza_02 = Pizza("quattro formaggi", 16)
pizza_02.make(2)
pizza_02.eat(2)

python pizza.pyコマンドで属性を追加して上記のコードを実行すると、%のレベル名に加えて人間が読めるタイムスタンプを含む新しい行がtest.logファイルに追加されます。 (t2)sおよび文字列としてロガーに渡される関連メッセージ。

OutputDEBUG:root:Pizza created: Sicilian ($18)
DEBUG:root:Made 5 Sicilian pizza(s)
DEBUG:root:Ate 4 pizza(s)
DEBUG:root:Pizza created: quattro formaggi ($16)
DEBUG:root:Made 2 quattro formaggi pizza(s)
DEBUG:root:Ate 2 pizza(s)
2017-05-01 16:28:54,593:DEBUG:Pizza created: Sicilian ($18)
2017-05-01 16:28:54,593:DEBUG:Made 5 Sicilian pizza(s)
2017-05-01 16:28:54,593:DEBUG:Ate 4 pizza(s)
2017-05-01 16:28:54,593:DEBUG:Pizza created: quattro formaggi ($16)
2017-05-01 16:28:54,593:DEBUG:Made 2 quattro formaggi pizza(s)
2017-05-01 16:28:54,593:DEBUG:Ate 2 pizza(s)

必要に応じて、プログラムファイルのログを自分に関連させるために、コードで追加のLogRecord attributesを使用することをお勧めします。

デバッグとその他のメッセージを別々のファイルに記録すると、時間の経過とともにPythonプログラムの全体的な理解が得られ、プログラムに加えられた履歴作業によって通知される方法でコードのトラブルシューティングと修正を行うことができます発生するイベントとトランザクション。

ロギングレベルの表

開発者は、重大度レベルを追加することにより、ロガーでキャプチャされるイベントの重要度を割り当てることができます。 重大度レベルを以下の表に示します。

ロギングレベルは技術的には整数(定数)であり、ロガーを数値0で初期化するNOTSETから始まり、すべて10ずつ増加します。

事前定義されたレベルに関連して独自のレベルを定義することもできます。 同じ数値でレベルを定義すると、その値に関連付けられた名前が上書きされます。

次の表は、さまざまなレベル名、それらの数値、レベルを呼び出すために使用できる関数、およびそのレベルの用途を示しています。

レベル 数値 関数 慣れている

CRITICAL

50

logging.critical()

重大なエラーを表示します。プログラムは実行を継続できない可能性があります

ERROR

40

logging.error()

より深刻な問題を表示する

WARNING

30

logging.warning()

予期しないことが起こった、または起こる可能性があることを示します

INFO

20

logging.info()

物事が期待どおりに機能していることを確認します

DEBUG

10

logging.debug()

問題を診断し、詳細情報を表示する

loggingモジュールはデフォルトレベルをWARNINGに設定するため、WARNINGERROR、およびCRITICALはすべてデフォルトでログに記録されます。 上記の例では、次のコードでDEBUGレベルを含めるように構成を変更しました。

logging.basicConfig(level=logging.DEBUG)

コマンドとデバッガーの操作について詳しくは、official logging documentationを参照してください。

結論

デバッグは、ソフトウェア開発プロジェクトの重要なステップです。 loggingモジュールは標準のPythonライブラリの一部であり、ソフトウェアの実行中に発生するイベントの追跡を提供し、これらのイベントを別のログファイルに出力して、コードの実行中に発生するイベントを追跡できます。 これにより、時間の経過に伴うプログラムの実行から発生するさまざまなイベントの理解に基づいてコードをデバッグできます。