目录 | 上一节 (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

翻译:《实用的Python编程》08_02_Logging的更多相关文章

  1. 翻译:《实用的Python编程》InstructorNotes

    实用的 Python 编程--讲师说明 作者:戴维·比兹利(David Beazley) 概述 对于如何使用我的课程"实用的 Python 编程"进行教学的问题,本文档提供一些通用 ...

  2. 翻译:《实用的Python编程》README

    欢迎光临 大约 25 年前,当我第一次学习 Python 时,发现 Python 竟然可以被高效地应用到各种混乱的工作项目上,我立即被震惊了.15 年前,我自己也将这种乐趣教授给别人.教学的结果就是本 ...

  3. 翻译:《实用的Python编程》05_02_Classes_encapsulation

    目录 | 上一节 (5.1 再谈字典) | 下一节 (6 生成器) 5.2 类和封装 创建类时,通常会尝试将类的内部细节进行封装.本节介绍 Python 编程中有关封装的习惯用法(包括私有变量和私有属 ...

  4. 翻译:《实用的Python编程》04_02_Inheritance

    目录 | 上一节 (4.1 类) | 下一节 (4.3 特殊方法) 4.2 继承 继承(inheritance)是编写可扩展程序程序的常用手段.本节对继承的思想(idea)进行探讨. 简介 继承用于特 ...

  5. 翻译:《实用的Python编程》01_02_Hello_world

    目录 | 上一节 (1.1 Python) | 下一节 (1.3 数字) 1.2 第一个程序 本节讨论有关如何创建一个程序.运行解释器和调试的基础知识. 运行 Python Python 程序始终在解 ...

  6. 翻译:《实用的Python编程》03_03_Error_checking

    目录 | 上一节 (3.2 深入函数) | 下一节 (3.4 模块) 3.3 错误检查 虽然前面已经介绍了异常,但本节补充一些有关错误检查和异常处理的其它细节. 程序是如何运行失败的 Python 不 ...

  7. 翻译:《实用的Python编程》03_04_Modules

    目录 | 上一节 (3.3 错误检查) | 下一节 (3.5 主模块) 3.4 模块 本节介绍模块的概念以及如何使用跨多个文件的函数. 模块和导入 任何一个 Python 源文件都是一个模块. # f ...

  8. 翻译:《实用的Python编程》03_05_Main_module

    目录 | 上一节 (3.4 模块) | 下一节 (3.6 设计讨论) 3.5 主模块 本节介绍主程序(主模块)的概念 主函数 在许多编程语言中,存在一个主函数或者主方法的概念. // c / c++ ...

  9. 翻译:《实用的Python编程》04_01_Class

    目录 | 上一节 (3.6 设计讨论) | 下一节 (4.2 继承) 4.1 类 本节介绍 class 语句以及创建新对象的方式. 面向对象编程(OOP) 面向对象编程是一种将代码组织成对象集合的编程 ...

随机推荐

  1. TS type different String / string

    TS type different String / string String / string https://stackoverflow.com/questions/14727044/types ...

  2. Flutter 真机调试

    先把手机开启开发者模式,并打开USB调试功能(每种机型开启方法可能不一样) flutter devices 查看是否连接 flutter run

  3. [转]LINUX下编译c++11的代码

    转载地址: https://blog.csdn.net/lwb102063/article/details/50445201 C++11,(即ISO/IEC 14882:2011),是目前的C++编程 ...

  4. docker数据卷的操作

    一般情况下会比较频繁的修改容器内部的文件 频繁docker cp 不太方便 使用数据卷可以将宿机的某个目录映射至容器的目录 修改会方便点 1.创建数据卷 docker volume create 数据 ...

  5. MySQL 导出 select 结果集

    reference: https://blog.csdn.net/huaishuming/article/details/74762652法一:SELECT * from jc_archives wh ...

  6. 力扣496. 下一个更大元素 I

    原题 1 class Solution: 2 def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[i ...

  7. 【转载】Android的事件分发(dispatchTouchEvent),拦截(onInterceptTouchEvent)与处理(onTouchEvent)

    出处:https://blog.csdn.net/caifengyao/article/details/65437695 在Android中,View的结构是树状的,所以,当触发触摸事件的时候,其事件 ...

  8. js mysql 时间日期比较

    js代码 1 var date1 = '2017/2/13'; 2 //var date1 = new Date().toLocaleDateString(); 3 var date2 = '2017 ...

  9. 【DP】斜率优化初步

    向y总学习了斜率优化,写下这篇blog加深一下理解. 模板题:https://www.acwing.com/problem/content/303/ 分析 因为本篇的重点在于斜率优化,故在此给出状态转 ...

  10. Linux安全模型中的3A

    3A Authentication : 认证 验证用户身份 Authorization : 授权 不同用户设置不同权限 Accouting | Audition : 审计 Linux 验证用户身份 U ...