软件开发中通过日志记录程序的运行情况是一个开发的好习惯,对于错误排查和系统运维都有很大帮助。Python标准库自带日志模块,程序的日志功能直接调用标准库的日志模块即可通过日志,开发者可以清楚的了解发生了哪些事件,包括出现了哪些错误。
日志记录是一种跟踪某些软件运行时发生的事件的方法。 该软件的开发人员在其代码中添加了日志记录调用,以指示发生了某些事件。 事件由描述性消息描述,该消息可以可选地包含变量数据。 事件也具有开发者认为该事件的重要性。 重要性也可以称为级别或严重性。
由标准库模块提供的日志记录API的主要好处是所有python模块都可以参与日志记录,因此您的应用程序日志可以包括您自己的消息以及与第三方模块的消息集成的消息。
首先,需要知道日志是有等级的。下面列举了五个等级。
日志等级(level) | 描述 |
---|---|
DEBUG | 调试信息,通常在诊断问题的时候用 |
INFO | 普通信息,确认程序按照预期运行 |
WARNING | 警告信息,表示发生意想不到的事,或者指示接下来可能会出现一些问题,但是程序还是继续运行 |
ERROR | 错误信息,程序运行中出现了一些问题,程序某些功能不能执行 |
CRITICAL | 危险信息,一个严重的错误,导致程序无法继续运行 |
注意:指定了日志等级后,只会显示大于等于所指定日志等级的日志信息!
logging中级别大小:
DEBUG < INFO < WARNING < ERROR < CRITICAL
日志内容是可以自己定制的,包括时间,记录内容等等。这些格式是由formater来定制。下面列举一些formater格式:(加粗的就是常用的)
%(asctime)s | 日志时间发生的时间 |
---|---|
%(levelname)s | 该日志记录的日志级别 |
%(message)s | 日志记录的文本内容 |
%(name)s | 所使用的日志器名称,默认是“root” |
%(filename)s | 调用日志记录函数的文件 |
%(funcName)s | 调用日志记录函数的函数名 |
%(lineno)d | 调用日志记录函数的代码所在的行号 |
%(pathname)s | 打印当前执行程序的路径,其实就是sys.argv[0] |
%(thread)d | 打印线程ID |
%(process)d | 打印进程ID |
日常使用中,可以通过创建带有默认Formatter
的StreamHandler
并将其添加到根记录器r,对记录系统进行基本配置。 如果没有为根记录器定义处理程序handler
,则debug()
,info()
,warning()
,error()
和critical()
函数将自动调用basicConfig()
函数。如果根记录器已经配置了处理程序,则此功能将不执行任何操作,除非将关键字参数force
设置为True
。
logging.basicConfig(**kwargs)
basicConfig()
的相关参数列举如下:
Format | Description |
---|---|
filename | 使用指定文件名创建FileHandler
|
filemode | 如果filename 指定,使用filemode 打开文件,默认是a ,追加 |
format | 为handler 使用指定format string |
datefmt | 使用指定date/time格式 |
style | 如果指定format,使用为format string使用该style。 |
level | 将root logger级别设置为指定level,默认是logging.WARNING
|
stream | 使用指定stream 初始化StreamHandler 。和filename 不能同时指定 |
handlers | 如果指定,应该是已经创建的可迭代的处理程序handler ,并添加到根记录器。 任何尚未设置格式器的处理程序都将被分配此函数中创建的默认格式器。 请注意,此参数与filename 或stream不兼容,如果同时存在,则会引发ValueError 。 |
force | 如果将此关键字参数指定为True ,则在执行其他参数指定的配置之前,将删除并关闭附加到根记录程序的所有现有处理程序。 |
这些都是碎片化的知识,下面直接列举一些实用的例子。
PS:日常使用中,只需要对logging进行一定的配置(根据需要),然后把程序中所有使用print()
打印内容换成logging.warning()
等函数打印即可。
简单使用
日志内容输出到控制台
import logging
# 设置输出的格式
LOG_FORMAT = "Time:%(asctime)s - Level:%(levelname)s - Message:%(message)s"
# 对logger进行配置——日志等级&输出格式
logging.basicConfig(level=logging.WARNING, format=LOG_FORMAT)
# logging.level(message)创建一条level级别的日志
logging.debug("This is a debug log")
logging.info("This is a info log")
logging.warning("This is a warning log")
logging.error("This is a error log")
logging.critical("This is a critical log")
运行结果如下:
观察可知,只有大于等于WARNING日志等级的日志信息才进行输出!
日志信息保存为文件
上述使用最终日志信息都是在终端输出——电脑一关/程序一关/编辑器一关,日志信息就丢失了! 而且我们实际使用中也不会那样做,所以下面就来来看看如何写入文件!
方法很简单,直接再basicConfig()
函数中加入filename
参数即可。(这里添加filename
参数之后,日志不会输出到控制台,而是只输出到log
文件)
import logging
# 设置输出的格式
LOG_FORMAT = "Time:%(asctime)s - Level:%(levelname)s - Message:%(message)s"
# 对logger进行配置——日志等级&输出格式
logging.basicConfig(level=logging.WARNING, format=LOG_FORMAT, filename="train.log")
# logging.level(message)创建一条level级别的日志
logging.debug("This is a debug log")
logging.info("This is a info log")
logging.warning("This is a warning log")
logging.error("This is a error log")
logging.critical("This is a critical log")
运行结果如下:
对basicConfig
的调用应先于对debug
,info
等的任何调用。由于它是一次性的简单配置工具,因此实际上只有第一个调用会做任何事情:后续调用实际上是无操作。如果您多次运行上述脚本,则连续运行的消息将附加到文件train.log
中。 如果您希望每个运行重新开始,而不记得先前运行的消息,则可以通过将上述示例中的调用更改为以下方式来指定filemode
参数:
logging.basicConfig(filename='example.log', filemode='w', level=logging.WARNING)
输出将与以前相同,但是不再将日志文件附加到该文件,因此先前运行的消息将丢失。
进阶操作:模块化组件
如果只是简单的使用logging,那么使用上面介绍的方法就可以了,如果要深度定制logging,那么就需要对它有更深的了解!
logging模块提供了模块化组件的方法:
组件 | 说明 |
---|---|
Loggers(日志记录器) | 提供程序直接使用的接口 |
(基操中的logging.basicConfig() 就是配置了此组件) |
|
Handlers(日志处理器) | 将记录的日志发送到指定的位置(终端打印/保存为文件) |
Filters(日志过滤器) | 用于过滤特定的日志记录 |
Formatters(日志格式器) | 用于控制日志信息的输出格式 |
模块化组件的使用步骤:
- 创建一个logger(日志记录器)对象;
- 定义handler(日志处理器),决定把日志发到哪里;常用的是:
- 设置日志级别(level)和输出格式Formatters(日志格式器);
- 把handler添加到对应的logger中去。
下面列举两个例子,也即是上面例子的升级版。
日志信息保存为文件
import logging
# 1.创建一个logger(日志记录器)对象;
my_logger = logging.Logger("first_logger")
# 2.定义handler(日志处理器),决定把日志发到哪里;
my_handler = logging.FileHandler('test.log')
# 3.设置日志级别(level)和输出格式Formatters(日志格式器);
my_handler.setLevel(logging.INFO)
my_format = logging.Formatter("时间:%(asctime)s 日志信息:%(message)s 行号:%(lineno)d")
# 把handler添加到对应的logger中去。
my_handler.setFormatter(my_format)
my_logger.addHandler(my_handler)
# 使用:
my_logger.info("我是日志组件")
同时写入文件和控制台
在日常使用中,一般我的习惯是同时写入文件和控制台,这样有两个好处:
import logging
logger = logging.getLogger("train")
handler = logging.FileHandler('train.log')
handler.setLevel(logging.INFO)
formatter_handler = logging.Formatter("时间:%(asctime)s 日志信息:%(message)s")
handler.setFormatter(formatter_handler)
logger.addHandler(handler)
console = logging.StreamHandler()
console.setLevel(logging.INFO)
format_console = logging.Formatter("时间:%(asctime)s 日志信息:%(message)s 行号:%(lineno)d")
console.setFormatter(format_console)
logger.addHandler(console)
logger.warning("~~~~~哦豁~~~~~")
运行结果如下: