动机
日常开发中调试一般分为两类
- print调试:缺点明显,仅仅适用于临时输出
- 单步调试:优点是能看清每一步的细节。缺点也很明显,效率低,多线程下,基本无法发挥作用
新手一般喜欢上述两种,然而随着经验提升,我们才会知道,日志才是调试的最佳方法。
目标
了解logging运行的核心,掌握基本的logging概念和基本函数。学会使用logging进行记录。
内容
什么是日志
什么时候使用日志
追踪事件的常用形式
-
输出到控制台
-
写入磁盘文件
从多个模块记录数据
记录变量数据
更改显示消息的格式
在消息中显示日期和时间
××××××××××××××××××××××××××××××××××××××××××××
模块化日志——高阶
记录器
处理程序
格式化程序
配置日志记录
什么是日志
日志是对软件执行时所发生的事件的一种追踪方式。软件开发人员对他们的代码添加日志调用,借此来指示某事件的发生。一个时间通过一些包含变量数据的描述信息来描述。开发者还会区分事件的重要性,重要性也被称为等级或严重性。
什么时候使用日志
你想要执行的任务 | 此任务的最好的工具 |
---|---|
对于命令行或程序的应用,结果显示在控制台。 | print() |
在对程序的普通操作发生时提交事件报告(比如:状态监控和错误调查) | logging.info() 函数(当有诊断目的需要详细输出信息时使用 logging.debug() 函数) |
提出一个警告信息基于一个特殊的运行时事件 |
|
对一个特殊的运行时事件报告错误 | 引发异常 |
报告错误而不引发异常(如在长时间运行中的服务端进程的错误处理) | logging.error() , logging.exception() 或 logging.critical() 分别适用于特定的错误及应用领域 |
级别:
级别 | 何时使用 |
---|---|
DEBUG | 细节信息,仅当诊断问题时适用。 |
INFO | 确认程序按预期运行 |
WARNING | 表明有已经或即将发生的意外(例如:磁盘空间不足)。程序仍按预期进行 |
ERROR | 由于严重的问题,程序的某些功能已经不能正常执行 |
CRITICAL | 严重的错误,表明程序已不能继续执行 |
默认的级别是‘WARNING’,意味着只会追踪该级别及以上事件的事件,除非更改日志配置。所追踪事件可以用不同形式处理。最简单的方式是输出到控制台。另一种常用的方式是写入磁盘文件。
追踪事件的常用形式
输出到控制台
举例:
import logging
logging.warning('Watch out!')#will print a message to the console
logging.info('I told you so')#will not print anything
控制台将会显示
INFO并没有出现,因为默认级别是WARNING。打印的信息包含事件的级别以及在日志调用中的对于事件的描述。
写入磁盘文件
示例:
import logging
logging.basicConfig(filename='example.log',level=logging.DEBUG)
logging.debug('This message should go to the log file')
logging.info('So should this')
logging.warning('And this,too')
打开example.log文件能看到:
该示例同样展示了如何设置日志追踪级别的阈值。由于阈值是DEBUG,所有信息全部打印
如果想从命令行设置日志级别,例:
numeric_level = getattr(logging, loglevel.upper(), None)
if not isinstance(numeric_level, int):
raise ValueError('Invalid log level:%s'%loglevel)
logging.basicConfig(level=numeric_level,....)
The syntax of getattr() method is:
getattr(object, name[, default])The above syntax is equivalent to:
object.name
对basicConfig的调用应该在debug(),info()等的前面。因为它被设计为一次性的配置,只有第一次调用会进行操作,随后的调用不会产生有效操作。如果多次运行商数脚本,则连续运行的消息将追加到文件example.log 。 如果你希望每次运行重新开始,而不是记住先前运行的消息,则可以通过将上例中的调用更改为来指定 filemode 参数:
logging.basicConfig(filename='example.log', filemode='w', level=logging.DEBUG)
输出将与之前相同,但不再追加进日志文件,因此早期运行的消息将丢失。
从多个模块记录数据
# myapp.py
import logging
import mylib
def main():
logging.basicConfig(filename='myapp.log', level=logging.INFO)
logging.info('Started')
mylib.do_something()
logging.info('Finished')
if __name__ == '__main__':
main()
# mylib.py
import logging
def do_something():
logging.info('Doing something')
记录变量数据
要记录变量数据,请使用格式字符串作为事件描述信息,并将变量数据作为参数附加。
例如
import logging
logging.warning('%s before you %s', 'LOOK', 'leap')
更多str.format()
和 string.Template
。 这些较新格式化选项 是 受支持的,但探索它们超出了本教程的范围:有关详细信息,请参阅 Using particular formatting styles throughout your application。
更改显示消息的格式
更改用于显示的消息个事,你需要制定要使用的格式:
import logging
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
logging.debug('This message should appear on the console')
logging.info('So should this')
logging.warning('And this, too')
在消息中显示日期和时间
要显示事件的日期和事件,你可以在格式字符串那种放置‘%(asctime)s’
import logging
logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
logging.warning('is when this event was logged.')
模块化日志——高阶
日志库采用模块化方法,并提供几类组件:记录器、处理程序、过滤器和格式化程序。
- 记录器报漏了应用程序代码直接使用的接口。
- 处理程序将日志记录(由记录器创建)发送到适当的目标。
- 过滤器提供了更精细的附加功能
- 格式化程序制定输出中日志记录的样式。
日志事件信息在LogRecord示例中的记录器、处理程序、过滤器和格式化程序之间传递。
记录器
通过调用Logger类(以下称为loggers,记录器)的实例来执行日志记录。每个实例都有一个名称,它们在概念上以点(句点)作为分隔符排列在命名空间的层次结构中。 例如,名为 'scan' 的记录器是记录器 'scan.text' ,'scan.html' 和 'scan.pdf' 的父级。 记录器名称可以是你想要的任何名称,并指示记录消息源自的应用程序区域。
在命名记录器时使用的一个好习惯是在每个使用日志记录的模块中使用模块级记录器,命名如下
logger = logging.getLogger(__name__)
记录器Logger对象有三重任务。首先,它们向应用程序代码公开了几种方法,以便于程序可以在运行时记录消息。其次,记录器对象根据严重性(默认过滤工具)或过滤器对象确定要处理的日志消息。记录器对象将相关的日志消息传递给所有感兴趣的日志处理程序。
记录器独享使用最广泛的方法分为两类:配置和消息发送。
最常见的配置方法:
- Logger.setLevel()指定记录器将处理的最低严重性日志消息。其中 debug 是最低内置严重性级别, critical 是最高内置严重性级别。 例如,如果严重性级别为 INFO ,则记录器将仅处理 INFO 、 WARNING 、 ERROR 和 CRITICAL 消息,并将忽略 DEBUG 消息。
- Logger.addHandler()和Logger.removeHandler()从记录器对象增加和删除处理程序对象。更详细的资料
- Logger.addFilter()和Logger.removeFilter()可以添加或移除记录器对象中的过滤器。 Filter Objects 包含更多的过滤器细节。
配置记录器对象后,以下方法将创建日志消息:
Logger.debug()
、Logger.info()
、Logger.warning()
、Logger.error()
和Logger.critical()
都创建日志记录,包含消息和与其各自方法名称对应的级别。该消息实际上是一个格式化字符串,它可能包含标题字符串替换语法%s
、%d
、%f
等等。其余参数是与消息中的替换字段对应的对象列表。关于**kwargs
,日志记录方法只关注exc_info
的关键字,并用它来确定是否记录异常信息。Logger.exception()
创建与Logger.error()
相似的日志信息。 不同之处是,Logger.exception()
同时还记录当前的堆栈追踪。仅从异常处理程序调用此方法。Logger.log()
将日志级别作为显式参数。对于记录消息而言,这比使用上面列出的日志级别方便方法更加冗长,但这是自定义日志级别的方法。
××××××××××××××××××××××
getLogger()
返回对具有指定名称的记录器实例的引用(如果已提供),或者如果没有则返回 root
。名称是以句点分隔的层次结构。多次调用 getLogger()
具有相同的名称将返回对同一记录器对象的引用。在分层列表中较低的记录器是列表中较高的记录器的子项。例如,给定一个名为 foo
的记录器,名称为 foo.bar
、 foo.bar.baz
和 foo.bam
的记录器都是 foo
子项。
记录器具有 有效等级 的概念。如果未在记录器上显式设置级别,则使用其父级别作为其有效级别。如果父级没有明确的级别设置,则检查 其 父级。依此类推,搜索所有上级元素,直到找到明确设置的级别。根记录器始终具有显式级别集(默认情况下为 WARNING
)。在决定是否处理事件时,记录器的有效级别用于确定事件是否传递给记录器的处理程序。
子记录器将消息传播到与其上级记录器关联的处理程序。因此,不必为应用程序使用的所有记录器定义和配置处理程序。为顶级记录器配置处理程序并根据需要创建子记录器就足够了。(但是,你可以通过将记录器的 propagate 属性设置 False
来关闭传播。)
处理程序
Handler
对象负责将适当的日志消息(基于日志消息的严重性)分派给处理程序的指定目标。 Logger
对象可以使用 addHandler()
方法向自己添加零个或多个处理程序对象。作为示例场景,应用程序可能希望将所有日志消息发送到日志文件,将错误或更高的所有日志消息发送到标准输出,以及将所有关键消息发送至一个邮件地址。 此方案需要三个单独的处理程序,其中每个处理程序负责将特定严重性的消息发送到特定位置。
标准库包含很多处理程序类型(参见 有用的处理程序 );教程主要使用 StreamHandler
和 FileHandler
。
setLevel()
方法,就像在记录器对象中一样,指定将被分派到适当目标的最低严重性。为什么有两个setLevel()
方法?记录器中设置的级别确定将传递给其处理程序的消息的严重性。每个处理程序中设置的级别确定处理程序将发送哪些消息。setFormatter()
选择一个该处理程序使用的 Formatter 对象。addFilter()
和removeFilter()
分别在处理程序上配置和取消配置过滤器对象。
格式化程序
格式化程序对象配置日志消息的最终顺序、结构和内容。 与 logging.Handler
类不同,应用程序代码可以实例化格式化程序类,但如果应用程序需要特殊行为,则可能会对格式化程序进行子类化。构造函数有三个可选参数 —— 消息格式字符串、日期格式字符串和样式指示符。
logging.Formatter.
__init__
(fmt=None, datefmt=None, style='%')
如果没有消息格式字符串,则默认使用原始消息。如果没有日期格式字符串,则默认日期格式为:
%Y-%m-%d %H:%M:%S
最后加上毫秒数。 style
是 %,'{ ' 或 '$' 之一。 如果未指定其中一个,则将使用 '%'。
如果 style
是 '%',则消息格式字符串使用 %(<dictionary key>)s
样式字符串替换;可能的键值在 LogRecord attributes 中。 如果样式为 '{',则假定消息格式字符串与 str.format()
(使用关键字参数)兼容,而如果样式为 '$' ,则消息格式字符串应符合 string.Template.substitute()
。
在 3.2 版更改: 添加 style
形参。
以下消息格式字符串将以人类可读的格式记录时间、消息的严重性以及消息的内容,按此顺序:
'%(asctime)s - %(levelname)s - %(message)s'
格式化程序使用用户可配置的函数将记录的创建时间转换为元组。 默认情况下,使用 time.localtime()
;要为特定格式化程序实例更改此项,请将实例的 converter
属性设置为具有相同签名的函数 time.localtime()
或 time.gmtime()
。 要为所有格式化程序更改它,例如,如果你希望所有记录时间都以 GMT 显示,请在格式化程序类中设置 converter
属性(对于 GMT 显示,设置为 time.gmtime
)。
配置日志记录
开发者可以通过三种方式配置日志记录:
- 使用调用上面列出的配置方法的 Python 代码显式创建记录器、处理程序和格式化程序。
- 创建日志配置文件并使用
fileConfig()
函数读取它。 - 创建配置信息字典并将其传递给
dictConfig()
函数。
更多https://docs.python.org/zh-cn/3/howto/logging.html#logging-basic-tutorial