跳至主要内容

Python 日志(Logging)教程

了解 Python 日志(Logging)的基础知识。
· 9分钟 阅读

使用 AI 探索

如果您刚开始学习 Python,想要了解更多内容,欢迎参加 DataCamp 的Python 数据科学入门课程。

日志级别
日志级别

一般来说,日志记录(logging)是一种保存由计算机程序生成的日志的方式。日志文件通常包含一系列在您的代码(例如 Python 代码)或操作系统中发生的事件记录。这些事件或消息会写入日志文件,消息或事件也可能是不同用户之间的通信。

通常由开发者决定如何生成日志。也许他们希望记录所开发应用的每一次操作,只在发生错误时记录,或监控并记录用户执行的重要操作。一个重要操作可能是检查哪个终端用户或 IP 地址访问了应用或服务器,如果是一次未成功的尝试,开发者就可以借助日志提供比简单堆栈跟踪更有价值的洞见。

一个不错的例子是:当您在 Linux 上尝试以 sudo 用户身份安装软件,而您并不是 sudo 用户时,您会看到一条消息 "Xyz is not in the sudoers file. This incident will be reported",这条信息会被写入日志文件。

好消息是,Python 自带 logging 模块,开箱即用,无需安装。

Logging 与 Print 函数有何不同?

Logging 与Print 函数在精神上相似。然而,Print 函数缺少许多对开发者有用的信息。相比之下,Logging 可以记录错误发生的时间戳与行号;它还能将错误或任意信息写入文件、套接字等,并提供五种严重性级别,便于区分日志。

如此处所示,日志有五个级别:

  • Debug (10):用于诊断代码中的问题。

  • Info (20):可作为代码无错误的确认。一个常见用例是记录机器学习模型训练的进度。

  • Warning (30):指示未来可能出现的问题。例如,某模块将来可能停用的警告,或内存不足警告。

  • Error (40):代码中的严重错误,可能是语法错误、内存不足错误或异常。

  • Critical (50):导致程序可能停止运行或异常退出的错误。

理论够多了,对吧?

现在我们来理解如何在 Python 代码中实现日志。

在此之前,先看看日志属性表,今天的教程中您会用到下面的一些日志属性。

日志属性表
Logging Attributes</a
import logging

现在您将调用 basicConfig() 方法,它可帮助您创建一个基础配置,使日志系统顺畅运行。

它允许您设置以下参数:

  • 日志记录的严重性:五个日志级别。
  • 将事件记录到文件中。
  • 通过 filemode 覆盖旧日志,仅记录当前日志。
  • 日志的存储格式。

请记住,basicConfig() 方法是可选的,您也可以跳过。

logging.basicConfig()

现在我们来构建五个严重性级别。

logging.debug("A Debug Logging Message")
logging.info("A Info Logging Message")
logging.warning("A Warning Logging Message")
WARNING:root:A Warning Logging Message
logging.error("An Error Logging Message")
ERROR:root:An Error Logging Message
logging.critical("A Critical Logging Message")
CRITICAL:root:A Critical Logging Message

从上面的输出可以看到,只有严重性级别高于 20 的日志被成功记录,因为这是 Python 日志记录的默认配置。

不过,如上所述,您可以使用 basicConfig() 方法修改严重性级别。这里将严重性级别改为 20,意味着记录严重性大于 10 的日志。

注意:请在运行下面的单元格前重启内核,以看到正确输出。

import logging
logging.basicConfig(level=logging.INFO)
logging.debug("A Debug Logging Message")
logging.info("A Info Logging Message")
INFO:root:A Info Logging Message
logging.warning("A Warning Logging Message")
WARNING:root:A Warning Logging Message
logging.error("An Error Logging Message")
ERROR:root:An Error Logging Message
logging.critical("A Critical Logging Message")
CRITICAL:root:A Critical Logging Message

现在我们把上述输出记录到一个日志文件中。为此,您将使用 basicConfig() 方法。

import logging
logging.basicConfig(level = logging.INFO, filename = 'datacamp.log')
logging.debug("A Debug Logging Message")
logging.info("A Info Logging Message")
logging.warning("A Warning Logging Message")
logging.error("An Error Logging Message")
logging.critical("A Critical Logging Message")
输出

如您所见,现在日志会生成在日志文件 datacamp.log 中。多次运行上述单元后,您会注意到日志会多次追加到该文件中。默认情况下,文件为 append 模式。

输出

您可以使用 filemode 关键字来避免日志文件中出现多份重复日志(即追加模式)。可以将 filemode 改为 write 模式,这会覆盖之前的日志,只保存当前日志。由于 filemode 设为 w,这意味着每次运行 basicConfig() 时,日志文件都会以写入模式打开,并最终被覆盖。

logging 中的文件模式功能与 Python 提供的标准文件处理类似。

import logging
logging.basicConfig(level = logging.INFO, filename = 'datacamp.log', filemode = 'w')
logging.debug("A Debug Logging Message")
logging.info("A Info Logging Message")
logging.warning("A Warning Logging Message")
logging.error("An Error Logging Message")
logging.critical("A Critical Logging Message")

我们多次运行上述代码行。您会注意到,这次只会保存当前的日志条目,之前的会被覆盖,如下所示。

输出

我们再来看一些日志属性,比如生成警告或错误的日期、时间、行号。您可能已经同意,这些都是优于简单 Print 函数的优势,Logging 的能力远不止于此。

为此,您将传入日志属性 asctime 和 lineno。同时还要传入 message 属性,它是您希望在日志中显示的信息的占位符。所有这些属性将传给 format 方法。

import logging
logging.basicConfig(format='Date-Time : %(asctime)s : Line No. : %(lineno)d - %(message)s', \
                    level = logging.DEBUG)
logging.debug("A Debug Logging Message")
logging.info("A Info Logging Message")
logging.warning("A Warning Logging Message")
logging.error("An Error Logging Message")
logging.critical("A Critical Logging Message")
Date-Time : 2020-03-18 23:58:12,429 : Line No. : 1 - A Debug Logging Message
Date-Time : 2020-03-18 23:58:12,436 : Line No. : 2 - A Info Logging Message
Date-Time : 2020-03-18 23:58:12,437 : Line No. : 3 - A Warning Logging Message
Date-Time : 2020-03-18 23:58:12,440 : Line No. : 4 - An Error Logging Message
Date-Time : 2020-03-18 23:58:12,441 : Line No. : 5 - A Critical Logging Message

让我们直接从这张表看看各属性的定义。

  • asctime:生成 LogRecord 时的人类可读时间。默认形式为 2020-03-18 22:24:42,670(逗号后的数字为毫秒)。由于该属性是字符串,因此后面使用 s 关键字。

  • lineno:调用日志记录的源代码行号。该属性是整数,因此后面使用 d 关键字。

  • message:已记录的消息,计算方式为 msg % args。该值在调用 Formatter.format() 时设置。如果在使用 format() 参数时不指定此属性,您希望记录的消息将不会生成。类似地,对于 message 属性,由于其为字符串,因此使用 s。

  • funcName:输出生成日志的模块或函数名。当您的代码中有很多函数,并希望根据所属函数或方法对日志进行区分时非常有用。

我们用一个简单例子看看如何用 logging 的 debug 功能替代 print 函数。

import logging

logging.basicConfig(format='%(asctime)s :: %(levelname)s :: %(funcName)s :: %(lineno)d \
:: %(message)s', level = logging.INFO)

def addition(x, y):
    add = x + y
    return add


def subtract(x, y):
    sub = x - y
    return sub


def multiply(x, y):
    mul = x * y
    return mul


def divide(x, y):
    div = x / y
    return div

def exponent(x, y):
    exp = x ** y
    return exp


num1 = 20
num2 = 2

def main():
    add_result = addition(num1, num2)
    logging.info('Add: {} + {} = {}'.format(num1, num2, add_result))

    sub_result = subtract(num1, num2)
    logging.info('Sub: {} - {} = {}'.format(num1, num2, sub_result))

    mul_result = multiply(num1, num2)
    logging.info('Mul: {} * {} = {}'.format(num1, num2, mul_result))

    div_result = divide(num1, num2)
    logging.info('Div: {} / {} = {}'.format(num1, num2, div_result))

    exp_result = exponent(num1, num2)
    logging.info('Exp: {} ** {} = {}'.format(num1, num2, exp_result))


main()
2020-03-19 00:01:27,372 :: INFO :: main :: 35 :: Add: 20 + 2 = 22
2020-03-19 00:01:27,376 :: INFO :: main :: 38 :: Sub: 20 - 2 = 18
2020-03-19 00:01:27,377 :: INFO :: main :: 41 :: Mul: 20 * 2 = 40
2020-03-19 00:01:27,378 :: INFO :: main :: 44 :: Div: 20 / 2 = 10.0
2020-03-19 00:01:27,379 :: INFO :: main :: 47 :: Exp: 20 ** 2 = 400

结语

恭喜您完成本教程。

您不妨尝试更多日志属性,了解如何在代码中加以利用。

如有与本教程相关的问题,欢迎在下方评论区留言。

如果您刚开始学习 Python,想要了解更多内容,欢迎参加 DataCamp 的Python 数据科学入门课程。

主题