翻譯:《實用的Python編程》08_02_Logging
目錄 | 上一節(jié) (8.1 測試) | 下一節(jié) (8.3 調(diào)試)
8.2 日志
本節(jié)對日志模塊(logging module)進(jìn)行簡單的介紹。
logging 模塊
logging 模塊是用于記錄診斷信息的 Python 標(biāo)準(zhǔn)庫模塊。日志模塊非常龐大,具有許多復(fù)雜的功能。我們將會展示一個簡單的例子來說明其用處。
再探異常
在本節(jié)練習(xí)中,我們創(chuàng)建這樣一個 parse() 函數(shù):
# 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 塊中,我們應(yīng)該做什么?
應(yīng)該打印警告消息(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
任何一種方式都無法令人滿意,通常情況下,兩種方式我們都需要(用戶可選)。
使用 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)
修改代碼以使程序能夠遇到問題的時候發(fā)出警告消息,或者特殊的 Logger 對象。 Logger 對象使用 logging.getLogger(__name__) 創(chuàng)建。
日志基礎(chǔ)
創(chuàng)建一個記錄器對象(logger object)。
log = logging.getLogger(name) # name is a string
發(fā)出日志消息:
log.critical(message [, args])
log.error(message [, args])
log.warning(message [, args])
log.info(message [, args])
log.debug(message [, args])
不同方法代表不同級別的嚴(yán)重性。
所有的方法都創(chuàng)建格式化的日志消息。args 和 % 運(yùn)算符 一起使用以創(chuàng)建消息。
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
)
通常,在程序啟動時,日志配置是一次性的(譯注:程序啟動后無法重新配置)。該配置與日志調(diào)用是分開的。
說明
日志是可以任意配置的。你可以對日志配置的任何一方面進(jìn)行調(diào)整:如輸出文件,級別,消息格式等等,不必?fù)?dān)心對使用日志模塊的代碼造成影響。
練習(xí)
練習(xí) 8.2:將日志添加到模塊中
在 fileparse.py 中,有一些與異常有關(guān)的錯誤處理,這些異常是由錯誤輸入引起的。如下所示:
# 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
請注意發(fā)出診斷消息的 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
完成修改后,嘗試在錯誤的數(shù)據(jù)上使用這些代碼:
>>> import report
>>> a = report.read_portfolio('Data/missing.csv')
Row 4: Bad row: ['MSFT', '', '51.23']
Row 7: Bad row: ['IBM', '', '70.44']
>>>
如果你什么都不做,則只會獲得 WARNING 級別以上的日志消息。輸出看起來像簡單的打印語句。但是,如果你配置了日志模塊,你將會獲得有關(guān)日志級別,模塊等其它信息。請按以下步驟操作查看:
>>> 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']
>>>
你會發(fā)現(xiàn),看不到來自于 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 級別的日志消息,關(guān)閉其它級別的日志消息。
>>> logging.getLogger('fileparse').level=logging.CRITICAL
>>> a = report.read_portfolio('Data/missing.csv')
>>>
練習(xí) 8.3:向程序添加日志
要添加日志到應(yīng)用中,你需要某種機(jī)制來實現(xiàn)在主模塊中初始化日志。其中一種方式使用看起來像下面這樣的代碼:
# 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 程序里的什么位置?
浙公網(wǎng)安備 33010602011771號