前言

logging 模块为应用与库实现了灵活的事件日志系统的函数与类。

使用标准库提供的 logging API 最主要的好处是,所有的 Python 模块都可能参与日志输出,包括你自己的日志消息和第三方模块的日志消息。

最简单的例子:

1
2
3
>>> import logging
>>> logging.warning('Watch out!')
WARNING:root:Watch out!

官方推荐的教程:

  1. 基础教程
  2. 进阶教程
  3. 日志记录操作手册

操作系统:Windows 10 专业版

参考文档

  1. logging — Python 的日志记录工具

日志基础教程

源教程地址: https://docs.python.org/zh-cn/3/howto/logging.html .

日志是对软件执行时所发生事件的一种追踪方式。软件开发人员对他们的代码添加日志调用,借此来指示某事件的发生。一个事件通过一些包含变量数据的描述信息来描述(比如:每个事件发生时的数据都是不同的)。开发者还会区分事件的重要性,重要性也被称为 等级 或 严重性。

什么时候使用日志

对于简单的日志使用来说日志功能提供了一系列便利的函数。它们是 debug()info()warning()error()critical()。想要决定何时使用日志,请看下表,其中显示了对于每个通用任务集合来说最好的工具。

你想要执行的任务 此任务最好的工具
对于命令行或程序的应用,结果显示在控制台。 print()
在对程序的普通操作发生时提交事件报告(比如:状态监控和错误调查) logging.info() 函数(当有诊断目的需要详细输出信息时使用 logging.debug() 函数)
提出一个警告信息基于一个特殊的运行时事件 warnings.warn() 位于代码库中,该事件是可以避免的,需要修改客户端应用以消除告警。logging.warning() 不需要修改客户端应用,但是该事件还是需要引起关注
对一个特殊的运行时事件报告错误 引发异常
报告错误而不引发异常(如在长时间运行中的服务端进程的错误处理) logging.error(), logging.exception()logging.critical() 分别适用于特定的错误及应用领域

日志功能应以所追踪事件级别或严重性而定。各级别适用性如下(以严重性递增):

级别 何时使用
DEBUG 细节信息,仅当诊断问题时适用。
INFO 确认程序按预期运行。
WARNING 表明有已经或即将发生的意外(例如:磁盘空间不足)。程序仍按预期进行。
ERROR 由于严重的问题,程序的某些功能已经不能正常执行
CRITICAL 严重的错误,表明程序已不能继续执行

默认的级别是 WARNING,意味着只会追踪该级别及以上的事件,除非更改日志配置。

所追踪事件可以以不同形式处理。最简单的方式是输出到控制台。另一种常用的方式是写入磁盘文件。

一个简单的例子

一个非常简单的例子:

1
2
3
import logging
logging.warning('Watch out!') # will print a message to the console
logging.info('I told you so') # will not print anything

如果你在命令行中输入这些代码并运行,你将会看到:

1
WARNING:root:Watch out!

输出到命令行。INFO 消息并没有出现,因为默认级别是 WARNING 。打印的信息包含事件的级别以及在日志调用中的对于事件的描述,例如 “Watch out!”。暂时不用担心 “root” 部分:之后会作出解释。输出格式可按需要进行调整,格式化选项同样会在之后作出解释。

记录日志到文件

A very common situation is that of recording logging events in a file, so let’s look at that next. Be sure to try the following in a newly started Python interpreter, and don’t just continue from the session described above:

1
2
3
4
5
6
import logging
logging.basicConfig(filename='example.log', encoding='utf-8', level=logging.DEBUG)
logging.debug('This message should go to the log file')
logging.info('So should this')
logging.warning('And this, too')
logging.error('And non-ASCII stuff, too, like Øresund and Malmö')

在 3.9 版更改: 增加了 encoding 参数。在更早的 Python 版本中或没有指定时,编码会用 open() 使用的默认值。尽管在上面的例子中没有展示,但也可以传入一个决定如何处理编码错误的 errors 参数。可使用的值和默认值,请参照 open() 的文档。

现在,如果我们打开日志文件,我们应当能看到日志信息:

1
2
3
4
DEBUG:root:This message should go to the log file
INFO:root:So should this
WARNING:root:And this, too
ERROR:root:And non-ASCII stuff, too, like Øresund and Malmö

该示例同样展示了如何设置日志追踪级别的阈值。该示例中,由于我们设置的阈值是 DEBUG,所有信息都将被打印。

如果你想从命令行设置日志级别,例如:

1
--log=INFO

并且在一些 loglevel 变量中你可以获得 --log 命令的参数,你可以使用:

1
getattr(logging, loglevel.upper())

通过 level 参数获得你将传递给 basicConfig() 的值。你需要对用户输入数据进行错误排查,可如下例:

1
2
3
4
5
6
7
# assuming loglevel is bound to the string value obtained from the
# command line argument. Convert to upper case to allow the user to
# specify --log=DEBUG or --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, ...)

对 basicConfig() 的调用应该在 debug(),info() 等之前。否则,这些函数会替你用默认配置调用 basicConfig()。它被设计为一次性的配置,只有第一次调用会进行操作,随后的调用不会产生有效操作。

如果多次运行上述脚本,则连续运行的消息将追加到文件 example.log 。 如果你希望每次运行重新开始,而不是记住先前运行的消息,则可以通过将上例中的调用更改为来指定 filemode 参数:

1
logging.basicConfig(filename='example.log', filemode='w', level=logging.DEBUG)

输出将与之前相同,但不再追加进日志文件,因此早期运行的消息将丢失。

从多个模块记录日志

如果你的程序包含多个模块,这里有一个如何组织日志记录的示例:

1
2
3
4
5
6
7
8
9
10
11
12
# 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()
1
2
3
4
5
# mylib.py
import logging

def do_something():
logging.info('Doing something')

如果你运行 myapp.py ,你应该在 myapp.log 中看到:

1
2
3
INFO:root:Started
INFO:root:Doing something
INFO:root:Finished

这是你期待看到的。 你可以使用 mylib.py 中的模式将此概括为多个模块。 请注意,对于这种简单的使用模式,除了查看事件描述之外,你不能通过查看日志文件来了解应用程序中消息的 来源 。 如果要跟踪消息的位置,则需要参考教程级别以外的文档 —— 请参阅 进阶日志教程 。

记录变量数据

要记录变量数据,请使用格式字符串作为事件描述消息,并附加传入变量数据作为参数。 例如:

1
2
import logging
logging.warning('%s before you %s', 'Look', 'leap!')

将显示:

1
WARNING:root:Look before you leap!

如你所见,将可变数据合并到事件描述消息中使用旧的 %-s形式的字符串格式化。 这是为了向后兼容:logging 包的出现时间早于较新的格式化选项例如 str.format() 和 string.Template。 这些较新格式化选项 是 受支持的,但探索它们超出了本教程的范围:有关详细信息,请参阅 生效于整个应用程序的格式化样式。

更改显示消息的格式

要更改用于显示消息的格式,你需要指定要使用的格式:

1
2
3
4
5
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')

这将输出:

1
2
3
DEBUG:This message should appear on the console
INFO:So should this
WARNING:And this, too

请注意,前面示例中出现的 “root” 已消失。 对于可以出现在格式字符串中的全部内容,你可以参考以下文档 LogRecord 属性 ,但为了简单使用,你只需要 levelname (严重性), message (事件描述,包括可变数据),也许在事件发生时显示。 这将在下一节中介绍。

在消息中显示日期/时间

要显示事件的日期和时间,你可以在格式字符串中放置 ‘%(asctime)s’

1
2
3
import logging
logging.basicConfig(format='%(asctime)s %(message)s')
logging.warning('is when this event was logged.')

应该打印这样的东西:

1
2010-12-12 11:41:42,612 is when this event was logged.

日期/时间显示的默认格式(如上所示)类似于 ISO8601 或 RFC 3339 。 如果你需要更多地控制日期/时间的格式,请为 basicConfig 提供 datefmt 参数,如下例所示:

1
2
3
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.')

这会显示如下内容:

1
12/12/2010 11:46:36 AM is when this event was logged.

datefmt 参数的格式与 time.strftime() 支持的格式相同。

后续步骤

基本教程到此结束。 它应该足以让你启动并运行日志记录。 logging 包提供了更多功能,但为了充分利用它,你需要花更多的时间来阅读以下部分。 如果你准备好了,可以拿一些你最喜欢的饮料然后继续。

If your logging needs are simple, then use the above examples to incorporate logging into your own scripts, and if you run into problems or don’t understand something, please post a question on the comp.lang.python Usenet group (available at https://groups.google.com/g/comp.lang.python) and you should receive help before too long.

还不够? 你可以继续阅读接下来的几个部分,这些部分提供了比上面基本部分更高级或深入的教程。 之后,你可以看一下 日志操作手册 。

LogRecord 属性

源教程: https://docs.python.org/zh-cn/3/library/logging.html#logrecord-attributes

LogRecord 具有许多属性,它们大多数来自于传递给构造器的形参。 (请注意 LogRecord 构造器形参与 LogRecord 属性的名称并不总是完全彼此对应的。) 这些属性可被用于将来自记录的数据合并到格式字符串中。 下面的表格(按字母顺序)列出了属性名称、它们的含义以及相应的 %-style 格式字符串内占位符。

如果是使用 {}-格式化str.format()),你可以将 {attrname} 用作格式字符串内的占位符。 如果是使用 $-格式化string.Template),则会使用 ${attrname} 的形式。 当然在这两种情况下,都应当将 attrname 替换为你想要使用的实际属性名称。

{}-格式化的情况下,你可以在属性名称之后放置指定的格式化旗标,并用冒号来分隔。 例如:占位符 {msecs:03.0f} 会将毫秒值 4 格式化为 004。 有参看 str.format() 文档了解你可以使用的选项的详情。

属性名称 格式 描述
args 此属性不需要用户进行格式化。 合并到 msg 以产生 message 的包含参数的元组,或是其中的值将被用于合并的字典(当只有一个参数且其类型为字典时)。
asctime %(asctime)s 表示人类易读的 LogRecord 生成时间。 默认形式为 ‘2003-07-08 16:49:45,896’ (逗号之后的数字为时间的毫秒部分)。
created %(created)f Time when the LogRecord was created (as returned by time.time()).
exc_info 此属性不需要用户进行格式化。 异常元组(例如 sys.exc_info)或者如未发生异常则为 None。
文件名 %(filename)s pathname 的文件名部分。
funcName %(funcName)s 函数名包括调用日志记录.
levelname %(levelname)s 消息文本记录级别(‘DEBUG’,‘INFO’,‘WARNING’,‘ERROR’,‘CRITICAL’)。
levelno %(levelno)s 消息数字的记录级别 (DEBUG, INFO, WARNING, ERROR, CRITICAL).
lineno %(lineno)d 发出日志记录调用所在的源行号(如果可用)。
message %(message)s 记入日志的消息,即 msg % args 的结果。 这是在发起调用 Formatter.format() 时设置的。
module %(module)s 模块 (filename 的名称部分)。
msecs %(msecs)d LogRecord 被创建的时间的毫秒部分。
msg 此属性不需要用户进行格式化。 在原始日志记录调用中传入的格式字符串。 与 args 合并以产生 message,或是一个任意对象 (参见 使用任意对象作为消息)。
name %(name)s 用于记录调用的日志记录器名称。
pathname %(pathname)s 发出日志记录调用的源文件的完整路径名(如果可用)。
process %(process)d 进程ID(如果可用)
processName %(processName)s 进程名(如果可用)
relativeCreated %(relativeCreated)d 以毫秒数表示的 LogRecord 被创建的时间,即相对于 logging 模块被加载时间的差值。
stack_info 此属性不需要用户进行格式化。 当前线程中从堆栈底部起向上直到包括日志记录调用并引发创建当前记录堆栈帧创建的堆栈帧信息(如果可用)。
thread %(thread)d 线程ID(如果可用)
threadName %(threadName)s 线程名(如果可用)
taskName %(taskName)s asyncio.Task 名称(如果可用)。

例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import logging
from logging import Logger

log_path: str = ""

# 日志器,将日志打印到日志文件中
logger: Logger = logging.getLogger(__name__)
logging.basicConfig(
format='%(levelname)s:%(module)s:%(funcName)s:%(asctime)s:%(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
level=logging.INFO,
filename=log_path,
filemode='w'
)

结语

第八十篇博文写完,开心!!!!

今天,也是充满希望的一天。