27.1. 01.调试

27.1.1. 日志

如果你曾经在代码中加入 print() 语句,在程序运行时输出某些变量的值,你 就使用了记日志的方式来调试代码。记日志是一种很好的方式,可以理解程序中 发生的事,以及事情发生的顺序。Python 的 logging 模块使得你很容易创建自定义 的消息记录。这些日志消息将描述程序执行何时到达日志函数调用,并列出你指 定的任何变量当时的值。 另一方面,缺失日志信息表明有一部分代码被跳过,从未执行。

27.1.2. 使用日志模块

要启用 logging 模块,在程序运行时将日志信息显示在屏幕上,请将下面的代 码复制到程序顶部(但在 Python 的#!行之下):

#!/usr/bin/env python
# -*- coding:utf8 -*-
# auther; 18793
# Date:2020/3/22 21:31
# filename: sample01.py

import logging

logging.basicConfig(level=logging.DEBUG, format=' %(asctime)s - %(levelname)s- %(message)s')
logging.debug("Start of program")


def factorial(n):
    logging.debug('Start of factorial(%s%%)' % (n))
    total = 1
    for i in range(1, n + 1):
        total *= i
        logging.debug('i is ' + str(i) + ', total is ' + str(total))
    logging.debug('End of factorial(%s%%)' % (n))
    return total


print(factorial(5))
logging.debug('End of program')

输出信息

120
 2020-03-22 21:37:47,202 - DEBUG- Start of program
 2020-03-22 21:37:47,203 - DEBUG- Start of factorial(5%)
 2020-03-22 21:37:47,203 - DEBUG- i is 1, total is 1
 2020-03-22 21:37:47,203 - DEBUG- i is 2, total is 2
 2020-03-22 21:37:47,203 - DEBUG- i is 3, total is 6
 2020-03-22 21:37:47,203 - DEBUG- i is 4, total is 24
 2020-03-22 21:37:47,203 - DEBUG- i is 5, total is 120
 2020-03-22 21:37:47,203 - DEBUG- End of factorial(5%)
 2020-03-22 21:37:47,203 - DEBUG- End of program

logging.debug() 调用不仅打印出了传递给它的字符串,而且包含一个时间戳和单词 DEBUG。

27.1.3. 不要用print()调试

输入 import logging 和 logging.basicConfig(level=logging.DEBUG, format='%
(asctime)s - %(levelname)s - %(message)s')有一点不方便。你可能想使用print()调用
代替,但不要屈服于这种诱惑!在调试完成后,你需要花很多时间,从代码中清除每
条日志消息的print()调用。你甚至有可能不小心删除一些 print() 调用,而它们不是用
来产生日志消息的。

日志消息的好处在于,你可以随心所欲地在程序中想加多少就加多少,稍后只要加入一次 logging.disable(logging.CRITICAL)调用,就可以禁止日
志。不像 print(),logging 模块使得显示和隐藏日志信息之间的切换变得很容易。
日志消息是给程序员的,不是给用户的。用户不会因为你便于调试,而想看到
的字典值的内容。请将日志信息用于类似这样的目的。对于用户希望看到的消息,
例如“文件未找到”或者“无效的输入,请输入一个数字”,应该使用 print() 调用。

我们不希望禁用日志消息之后,让用户看不到有用的信息。

27.1.4. 日志级别

Python中的日志级别

级别          日志函数                             描述
DEBUG       logging.debug()               最低级别。用于小细节。通常只有在诊断问题时,你才会关心这些消息
INFO        logging.info()                用于记录程序中一般事件的信息,或确认一切工作正常
WARNING     logging.warning()             用于表示可能的问题,它不会阻止程序的工作,但将来可能会
ERROR       logging.error()               用于记录错误,它导致程序做某事失败
CRITICAL    logging.critical()            最高级别。用于表示致命的错误,它导致或将要导致程序完全停止工作

日志级别的好处在于,你可以改变想看到的日志消息的优先级。

向basicConfig()函数传入logging.DEBUG作为level关键字参数,这将显示所有日志级别的消息(DEBUG是最低的级别)。 但在开发了更多的程序后,你可能只对错误感兴趣。

在这种情况下,可以将 basicConfig()的level参数设置为logging.ERROR,这将只显示 ERROR和 CRITICAL 消息,跳过 DEBUG、INFO 和 WARNING 消息。

#!/usr/bin/env python
# -*- coding:utf8 -*-
# auther; 18793
# Date:2020/3/22 21:59
# filename: sample02.py
import logging

logging.basicConfig(level=logging.DEBUG, format=' %(asctime)s - %(levelname)s - %(message)s')
logging.debug("Some debugging details.")
logging.info("The logging module is working.")
logging.warning("An error message is about to be logged.")
logging.error("An error has occurred.")
logging.critical("The program is unable to recover!")

"""
 2020-03-22 22:04:00,621 - DEBUG - Some debugging details.
 2020-03-22 22:04:00,622 - INFO - The logging module is working.
 2020-03-22 22:04:00,622 - WARNING - An error message is about to be logged.
 2020-03-22 22:04:00,622 - ERROR - An error has occurred.
 2020-03-22 22:04:00,622 - CRITICAL - The program is unable to recover!
"""

27.1.5. 禁用日志

在调试完程序后,你可能不希望所有这些日志消息出现在屏幕上。logging.disable() 函数禁用了这些消息, 这样就不必进入到程序中,手工删除所有的日志调用。

只要向 logging.disable()传入一个日志级别,它就会禁止该级别和更低级别的所有日志消息。

所以,如果想要禁用所有日志,只要在程序中添加logging. disable(logging.CRITICAL)

#!/usr/bin/env python
# -*- coding:utf8 -*-
# auther; 18793
# Date:2020/3/22 22:08
# filename: sample03.py
import logging

# 禁用所有日志
logging.disable(logging.CRITICAL)

logging.basicConfig(level=logging.INFO, format=' %(asctime)s -%(levelname)s - %(message)s')

logging.critical('Critical error! Critical error!')

logging.critical('Critical error! Critical error!')
logging.error('Error! Error!')

"""
因为 logging.disable() 将禁用它之后的所有消息,你可能希望将它添加到程序中接近import logging 代码行的位置。
这样就很容易找到它,根据需要注释掉它,或取消注释,从而启用或禁用日志消息。
"""

27.1.6. 将日志记录到文件

除了将日志消息显示在屏幕上,还可以将它们写入文本文件。logging.basicConfig()函数接受 filename 关键字参数,像这样:

#!/usr/bin/env python
# -*- coding:utf8 -*-
# auther; 18793
# Date:2020/3/22 22:15
# filename: sample04.py

import logging
# 禁用所有日志
# logging.disable(logging.CRITICAL)

logging.basicConfig(filename='myProgramLog.log', level=logging.DEBUG,
                    format='%(asctime)s - %(levelname)s - %(message)s')
logging.debug("Some debugging details.")
logging.info("The logging module is working.")
logging.warning("An error message is about to be logged.")
logging.error("An error has occurred.")
logging.critical("The program is unable to recover!")

日志信息将被保存到 myProgramLog.txt 文件中。虽然日志消息很有用, 但它们可能塞满屏幕,让你很难读到程序的输出。将日志信息写入到文件,让屏幕保持干净,又能保存信息,这样在运行程序后,可以阅读这些信息。

27.1.7. 给脚本添加日志记录

想改变输出或输出的严重级别,可以通过修改调用basicConfig()的参数来实现。示例如下:

logging.basicConfig(filename='app.log', level=logging.WARNING, format='%(levelname)s:%(asctime)s:%(message)s')

eg:

#!/usr/bin/env python
# -*- coding:utf8 -*-
# auther; 18793
# Date:2020/4/1 21:15
# filename: 给脚本添加日志记录.py
import logging


def main():
    # Configure the logging system
    # logging.basicConfig(filename='app.log', level=logging.ERROR)
    logging.basicConfig(filename='app.log', level=logging.WARNING, format='%(levelname)s:%(asctime)s:%(message)s')
    # Variables (to make the calls that follow work)
    hostname = 'www.python.org'
    item = 'spam'
    filename = 'data.csv'
    mode = 'r'

    # Example logging calls (insert into your program)
    logging.critical('Host %s unknown', hostname)
    logging.error("Couldn't find %r", item)
    logging.warning('Feature is deprecated')
    logging.info('Opening file %r, mode=%r', filename, mode)
    logging.debug('Got here')


if __name__ == '__main__':
    main()

想从配置文件中进行配置,把basicConfig()调用修改成如下形式:

eg

#!/usr/bin/env python
# -*- coding:utf8 -*-
# auther; 18793
# Date:2020/4/1 21:15
# filename: 给脚本添加日志记录.py
import logging
import logging.config

def main():
    logging.config.fileConfig('logconfig.ini')
    # Example logging calls (insert into your program)
    logging.critical('Host %s unknown', "critical..........")
    logging.error("Couldn't find %r", "error...........")
    logging.warning('Feature is deprecated')
    logging.info('Opening file %r, mode=%r')
    logging.debug('Got here')

if __name__ == '__main__':
    main()

现在创建一个配置文件logconfig.ini

[loggers]
keys=root

[handlers]
keys=defaultHandler

[formatters]
keys=defaultFormatter

[logger_root]
level=INFO
handlers=defaultHandler
qualname=root

[handler_defaultHandler]
class=FileHandler
formatter=defaultFormatter
args=('app_log.log', 'a')

[formatter_defaultFormatter]
format=%(levelname)s:%(asctime)s:%(message)s

如果想修改配置,直接编辑logconfig.ini 文件即可。

如果想让日志消息发送到标准错误输出而不是文件中,不要给basicConfig()提供任何文件名做参数即可。例如,可以这么做:

logging.basicConfig(level=logging.INFO)

关于basicConfig(),一个微妙的地方在于它只能在程序中调用一次。如果稍后需要修改日志模块的配置,需要取得根日志对象(root logger)并直接对其做修改。示例如下:

logging.getLogger().level = logging.DEBUG