目录 | 上一节 (8.1 测试) | [下一节 (8.3 调试)]()

8.2 日志

本节对日志模块(logging module)进行简略的介绍。

logging 模块

logging 模块是用于记录诊断信息的 Python 规范库模块。日志模块十分宏大,具备许多简单的性能。咱们将会展现一个简略的例子来阐明其用途。

再探异样

在本节练习中,咱们创立这样一个 parse() 函数:

# fileparse.pydef 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.pyimport logginglog = 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.pyimport csvdef 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.pyimport csvimport logginglog = 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 logginglogging.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