ロギングモジュールの紹介

PythonPythonBeginner
今すぐ練習

This tutorial is from open-source community. Access the source code

💡 このチュートリアルは英語版からAIによって翻訳されています。原文を確認するには、 ここをクリックしてください

はじめに

このセクションでは、ロギングモジュールを簡単に紹介します。

ロギングモジュール

logging モジュールは、診断情報を記録するための標準ライブラリモジュールです。また、非常に大きなモジュールであり、洗練された機能が豊富です。その有用性を示すために、簡単な例を示します。

再訪:例外処理

演習では、次のような関数 parse() を書きました。

## fileparse.py
def parse(f, types=None, names=None, delimiter=None):
    records = []
    for line in f:
        line = line.strip()
        if not line: continue
        try:
            records.append(split(line,types,names,delimiter))
        except ValueError as e:
            print("Couldn't parse :", line)
            print("Reason :", e)
    return records

try-except 文に注目してください。except ブロックで何をすべきでしょうか?

警告メッセージを表示するべきですか?

try:
    records.append(split(line,types,names,delimiter))
except ValueError as e:
    print("Couldn't parse :", line)
    print("Reason :", e)

それとも、黙って無視しますか?

try:
    records.append(split(line,types,names,delimiter))
except ValueError as e:
    pass

どちらの解決策も満足のいくものではありません。なぜなら、多くの場合、両方の動作(ユーザーが選択可能)が必要だからです。

logging の使用

logging モジュールを使うことでこの問題を解決できます。

## fileparse.py
import logging
log = logging.getLogger(__name__)

def parse(f,types=None,names=None,delimiter=None):
 ...
    try:
        records.append(split(line,types,names,delimiter))
    except ValueError as e:
        log.warning("Couldn't parse : %s", line)
        log.debug("Reason : %s", e)

コードを修正して、警告メッセージを発行するか、特殊な Logger オブジェクトを使うようにしました。logging.getLogger(__name__) で作成されたものです。

ロギングの基本

ロガーオブジェクトを作成します。

log = logging.getLogger(name)   ## name は文字列

ログメッセージを発行します。

log.critical(message [, args])
log.error(message [, args])
log.warning(message [, args])
log.info(message [, args])
log.debug(message [, args])

各メソッドは、異なる重大度レベルを表します。

すべてのメソッドは、フォーマットされたログメッセージを作成します。args は、メッセージを作成するために % 演算子とともに使用されます。

logmsg = message % args ## ログに書き込まれます

ロギングの設定

ロギングの動作は別途設定されます。

## main.py

...

if __name__ == '__main__':
    import logging
    logging.basicConfig(
        filename  = 'app.log',      ## ログ出力ファイル
        level     = logging.INFO,   ## 出力レベル
    )

通常、これはプログラム起動時の一度だけの設定です。設定は、ロギング呼び出しを行うコードとは別です。

コメント

ロギングは高度に設定可能です。出力ファイル、レベル、メッセージ形式など、そのあらゆる側面を調整できます。ただし、ロギングを使用するコードはそれについて心配する必要はありません。

演習8.2: モジュールにロギングを追加する

fileparse.py には、入力データの不備による例外に関するエラー処理がいくつかあります。このようになっています。

## fileparse.py
import csv

def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):
    '''
    Parse a CSV file into a list of records with type conversion.
    '''
    if select and not has_headers:
        raise RuntimeError('select requires column headers')

    rows = csv.reader(lines, delimiter=delimiter)

    ## Read the file headers (if any)
    headers = next(rows) if has_headers else []

    ## If specific columns have been selected, make indices for filtering and set output columns
    if select:
        indices = [ headers.index(colname) for colname in select ]
        headers = select

    records = []
    for rowno, row in enumerate(rows, 1):
        if not row:     ## Skip rows with no data
            continue

        ## If specific column indices are selected, pick them out
        if select:
            row = [ row[index] for index in indices]

        ## Apply type conversion to the row
        if types:
            try:
                row = [func(val) for func, val in zip(types, row)]
            except ValueError as e:
                if not silence_errors:
                    print(f"Row {rowno}: Couldn't convert {row}")
                    print(f"Row {rowno}: Reason {e}")
                continue

        ## Make a dictionary or a tuple
        if headers:
            record = dict(zip(headers, row))
        else:
            record = tuple(row)
        records.append(record)

    return records

診断メッセージを発行する print 文に注目してください。これらの print をロギング操作に置き換えるのは比較的簡単です。コードを次のように変更します。

## fileparse.py
import csv
import logging
log = logging.getLogger(__name__)

def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):
    '''
    Parse a CSV file into a list of records with type conversion.
    '''
    if select and not has_headers:
        raise RuntimeError('select requires column headers')

    rows = csv.reader(lines, delimiter=delimiter)

    ## Read the file headers (if any)
    headers = next(rows) if has_headers else []

    ## If specific columns have been selected, make indices for filtering and set output columns
    if select:
        indices = [ headers.index(colname) for colname in select ]
        headers = select

    records = []
    for rowno, row in enumerate(rows, 1):
        if not row:     ## Skip rows with no data
            continue

        ## If specific column indices are selected, pick them out
        if select:
            row = [ row[index] for index in indices]

        ## Apply type conversion to the row
        if types:
            try:
                row = [func(val) for func, val in zip(types, row)]
            except ValueError as e:
                if not silence_errors:
                    log.warning("Row %d: Couldn't convert %s", rowno, row)
                    log.debug("Row %d: Reason %s", rowno, e)
                continue

        ## Make a dictionary or a tuple
        if headers:
            record = dict(zip(headers, row))
        else:
            record = tuple(row)
        records.append(record)

    return records

これらの変更を行ったら、不適切なデータに対してコードをいくつか使用してみてください。

>>> import report
>>> a = report.read_portfolio('missing.csv')
Row 4: Bad row: ['MSFT', '', '51.23']
Row 7: Bad row: ['IBM', '', '70.44']
>>>

何もしなければ、WARNING レベル以上のロギングメッセージのみが表示されます。出力は単純な print 文のように見えます。ただし、ロギングモジュールを設定すると、ロギングレベル、モジュールなどに関する追加情報が得られます。これを確認するには、次の手順を実行してください。

>>> import logging
>>> logging.basicConfig()
>>> a = report.read_portfolio('missing.csv')
WARNING:fileparse:Row 4: Bad row: ['MSFT', '', '51.23']
WARNING:fileparse:Row 7: Bad row: ['IBM', '', '70.44']
>>>

log.debug() 操作の出力が表示されないことに気付くでしょう。レベルを変更するには、次を入力してください。

>>> logging.getLogger('fileparse').setLevel(logging.DEBUG)
>>> a = report.read_portfolio('missing.csv')
WARNING:fileparse:Row 4: Bad row: ['MSFT', '', '51.23']
DEBUG:fileparse:Row 4: Reason: invalid literal for int() with base 10: ''
WARNING:fileparse:Row 7: Bad row: ['IBM', '', '70.44']
DEBUG:fileparse:Row 7: Reason: invalid literal for int() with base 10: ''
>>>

最も重要なロギングメッセージ以外はすべて無効にします。

>>> logging.getLogger('fileparse').setLevel(logging.CRITICAL)
>>> a = report.read_portfolio('missing.csv')
>>>

演習8.3: プログラムにロギングを追加する

アプリケーションにロギングを追加するには、メインモジュールでロギングモジュールを初期化するための何らかのメカニズムが必要です。これを行う方法の1つは、次のようなセットアップコードを含めることです。

## このファイルでは、ロギングモジュールの基本設定を行います。
## 必要に応じてロギング出力を調整するには、ここで設定を変更してください。
import logging
logging.basicConfig(
    filename = 'app.log',            ## ログファイルの名前 (省略するとstderrを使用します)
    filemode = 'w',                  ## ファイルモード ('a' を使用して追記する場合もあります)
    level    = logging.WARNING,      ## ロギングレベル (DEBUG、INFO、WARNING、ERROR、またはCRITICAL)
)

同様に、これをプログラムの起動手順のどこかに配置する必要があります。たとえば、report.py プログラムのどこにこれを配置するでしょうか。

✨ 解答を確認して練習

まとめ

おめでとうございます! あなたはロギングの実験を完了しました。あなたのスキルを向上させるために、LabExでさらに多くの実験を行って練習することができます。