目录 | 上一节 (8.1 测试) | [下一节 (8.3 调试)]()
8.2 日志
本节对日志模块(logging module)进行简略的介绍。
logging 模块
logging
模块是用于记录诊断信息的 Python 规范库模块。日志模块十分宏大,具备许多简单的性能。咱们将会展现一个简略的例子来阐明其用途。
再探异样
在本节练习中,咱们创立这样一个 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
块中,咱们应该做什么?
应该打印正告音讯(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)
批改代码以使程序可能遇到问题的时候收回正告音讯,或者非凡的 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