翻譯:《實用的Python編程》08_02_Logging

目錄 | 上一節 (8.1 測試) | [下一節 (8.3 調試)]()python

8.2 日誌

本節對日誌模塊(logging module)進行簡單的介紹。git

logging 模塊

logging 模塊是用於記錄診斷信息的 Python 標準庫模塊。日誌模塊很是龐大,具備許多複雜的功能。咱們將會展現一個簡單的例子來講明其用處。github

再探異常

在本節練習中,咱們建立這樣一個 parse() 函數:segmentfault

# 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 塊中,咱們應該作什麼?app

應該打印警告消息(warning message)?函數

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

任何一種方式都沒法使人滿意,一般狀況下,兩種方式咱們都須要(用戶可選)。ui

使用 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 對象。 Logger 對象使用 logging.getLogger(__name__) 建立。翻譯

日誌基礎

建立一個記錄器對象(logger object)。

log = logging.getLogger(name)   # name is a string

發出日誌消息:

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

不一樣方法表明不一樣級別的嚴重性。

全部的方法都建立格式化的日誌消息。args% 運算符 一塊兒使用以建立消息。

logmsg = message % args # Written to the log

日誌配置

配置:

# main.py

...

if __name__ == '__main__':
    import logging
    logging.basicConfig(
        filename  = 'app.log',      # Log output file
        level     = logging.INFO,   # Output level
    )

一般,在程序啓動時,日誌配置是一次性的(譯註:程序啓動後沒法從新配置)。該配置與日誌調用是分開的。

說明

日誌是能夠任意配置的。你能夠對日誌配置的任何一方面進行調整:如輸出文件,級別,消息格式等等,沒必要擔憂對使用日誌模塊的代碼形成影響。

練習

練習 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('Data/missing.csv')
Row 4: Bad row: ['MSFT', '', '51.23']
Row 7: Bad row: ['IBM', '', '70.44']
>>>

若是你什麼都不作,則只會得到 WARNING 級別以上的日誌消息。輸出看起來像簡單的打印語句。可是,若是你配置了日誌模塊,你將會得到有關日誌級別,模塊等其它信息。請按如下步驟操做查看:

>>> import logging
>>> logging.basicConfig()
>>> a = report.read_portfolio('Data/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').level = logging.DEBUG
>>> a = report.read_portfolio('Data/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: ''
>>>

只留下 critical 級別的日誌消息,關閉其它級別的日誌消息。

>>> logging.getLogger('fileparse').level=logging.CRITICAL
>>> a = report.read_portfolio('Data/missing.csv')
>>>

練習 8.3:向程序添加日誌

要添加日誌到應用中,你須要某種機制來實如今主模塊中初始化日誌。其中一種方式使用看起來像下面這樣的代碼:

# This file sets up basic configuration of the logging module.
# Change settings here to adjust logging output as needed.
import logging
logging.basicConfig(
    filename = 'app.log',            # Name of the log file (omit to use stderr)
    filemode = 'w',                  # File mode (use 'a' to append)
    level    = logging.WARNING,      # Logging level (DEBUG, INFO, WARNING, ERROR, or CRITICAL)
)

再次說明,你須要將日誌配置代碼放到程序啓動步驟中。例如,將其放到 report.py 程序裏的什麼位置?

目錄 | 上一節 (8.1 測試) | [下一節 (8.3 調試)]()

注:完整翻譯見 https://github.com/codists/practical-python-zh

相關文章
相關標籤/搜索