ZetCode

Python 日志记录

最后修改于 2024 年 1 月 29 日

Python 日志记录教程展示了如何使用 logging 模块在 Python 中进行日志记录。

日志记录

日志记录是将信息写入日志文件的过程。日志文件包含有关操作系统、软件或通信中发生的各种事件的信息。

日志记录的目的

日志记录的目的如下:

日志记录不仅限于识别软件开发中的错误。它还用于检测安全事件、监控策略违规、在出现问题时提供信息、查找应用程序瓶颈或生成使用数据。

记录哪些事件

应记录的事件包括输入验证失败、身份验证和授权失败、应用程序错误、配置更改以及应用程序启动和关闭。

不记录哪些事件

不应记录的事件包括应用程序源代码、会话标识值、访问令牌、敏感个人数据、密码、数据库连接字符串、加密密钥、银行账户和持卡人数据。

日志记录最佳实践

以下是一些日志记录的最佳实践:

logging 模块

Python 的 logging 模块定义了用于实现应用程序和库的灵活事件日志记录系统的函数和类。

logging 模块的组件

logging 模块有四个主要组件:日志记录器 (loggers)、处理器 (handlers)、过滤器 (filters) 和格式化器 (formatters)。日志记录器公开应用程序代码直接使用的接口。处理器将日志记录 (由日志记录器创建) 发送到适当的目的地。过滤器提供更精细的功能来确定输出哪些日志记录。格式化器指定最终输出中日志记录的布局。

Python 日志记录层次结构

Python 日志记录器形成一个层次结构。名为 main 的日志记录器是 main.new 的父级。

子日志记录器会将消息传播到与其祖先日志记录器关联的处理器。因此,不必为应用程序中的所有日志记录器定义和配置处理器。只需为顶级日志记录器配置处理器,并根据需要创建子日志记录器即可。

Python 日志记录级别

级别用于识别事件的严重性。有六个日志记录级别:

如果日志记录级别设置为 WARNING,则所有 WARNINGERRORCRITICAL 消息都会写入日志文件或控制台。如果设置为 ERROR,则只记录 ERRORCRITICAL 消息。

日志记录器具有有效级别的概念。如果日志记录器上没有明确设置级别,则使用其父级的级别作为其有效级别。如果父级没有设置显式级别,则检查其父级,依此类推——搜索所有祖先,直到找到显式设置的级别。

当使用 getLogger 创建日志记录器时,级别设置为 NOTSET。如果日志记录级别未通过 setLevel 显式设置,则消息将传播到日志记录器父级。日志记录器的祖先日志记录器链被遍历,直到找到级别不是 NOTSET 的祖先,或者达到根日志记录器。根日志记录器默认设置为 WARNING 级别。

根日志记录器

所有日志记录器都是根日志记录器的后代。每个日志记录器都会将其日志消息传递给其父级。新日志记录器使用 getLogger(name) 方法创建。调用不带名称的函数 (getLogger) 会返回根日志记录器。

根日志记录器始终具有显式设置的级别,默认为 WARNING

根日志记录器位于层次结构的顶部,并且始终存在,即使未配置。通常,程序或库不应直接针对根日志记录器进行日志记录。相反,应为程序配置一个特定的日志记录器。根日志记录器可用于轻松打开和关闭所有库的所有日志记录器。

Python 日志记录简单示例

logging 模块具有简单的函数,无需任何配置即可立即使用。这可用于简单的日志记录。

simple.py
#!/usr/bin/python

import logging

logging.debug('This is a debug message')
logging.info('This is an info message')
logging.warning('This is a warning message')
logging.error('This is an error message')
logging.critical('This is a critical message')

该示例调用了 logging 模块的五个函数。消息被写入控制台。

$ simple.py
WARNING:root:This is a warning message
ERROR:root:This is an error message
CRITICAL:root:This is a critical message

请注意,使用了根日志记录器,并且只写入了三条消息。这是因为默认情况下,只写入警告及以上级别的消息。

Python 设置日志记录级别

日志记录级别使用 setLevel 设置。它将此日志记录器的阈值设置为 lvl。严重性低于 lvl 的日志消息将被忽略。

set_level.py
#!/usr/bin/python

import logging

logger = logging.getLogger('dev')
logger.setLevel(logging.DEBUG)

logger.debug('This is a debug message')
logger.info('This is an info message')
logger.warning('This is a warning message')
logger.error('This is an error message')
logger.critical('This is a critical message')

在示例中,我们将日志记录级别更改为 DEBUG

logger = logging.getLogger('dev')

getLogger 返回具有指定名称的日志记录器。如果名称为 None,它将返回根日志记录器。名称可以是定义日志记录层次结构的点分隔字符串;例如 'a'、'a.b' 或 'a.b.c'。请注意,存在一个隐式的根名称,但未显示。

$ set_level.py
This is a warning message
This is an error message
This is a critical message

现在所有消息都已写入。

Python 有效日志记录级别

有效日志记录级别是显式设置的级别或从日志记录器父级确定的级别。

effective_level.py
#!/usr/bin/python

import logging

main_logger = logging.getLogger('main')
main_logger.setLevel(5)

dev_logger = logging.getLogger('main.dev')

print(main_logger.getEffectiveLevel())
print(dev_logger.getEffectiveLevel())

在示例中,我们检查了两个日志记录器的有效日志记录级别。

dev_logger = logging.getLogger('main.dev')

dev_logger 的级别未设置;然后使用其父级的级别。

$ effective_level.py
5
5

Python 日志记录处理器

处理器是一个负责将适当的日志消息(根据日志消息的严重性)分派到处理器指定的目的地的对象。

处理器像级别一样被传播。如果日志记录器没有设置处理器,则会搜索其祖先链来查找处理器。

handlers.py
#!/usr/bin/python

import logging

logger = logging.getLogger('dev')
logger.setLevel(logging.INFO)

fileHandler = logging.FileHandler('test.log')
fileHandler.setLevel(logging.INFO)

consoleHandler = logging.StreamHandler()
consoleHandler.setLevel(logging.INFO)

logger.addHandler(fileHandler)
logger.addHandler(consoleHandler)

logger.info('information message')

该示例为日志记录器创建了两个处理器:一个文件处理器和一个控制台处理器。

fileHandler = logging.FileHandler('test.log')

FileHandler 将日志记录发送到 test.log 文件。

consoleHandler = logging.StreamHandler()

StreamHandler 将日志记录发送到流。如果未指定流,则使用 sys.stderr

logger.addHandler(fileHandler)

使用 addHandler 将处理器添加到日志记录器。

Python 日志记录格式化器

格式化器是一个对象,它配置日志记录的最终顺序、结构和内容。除了消息字符串之外,日志记录还包括日期和时间、日志名称以及日志级别严重性。

formatter.py
#!/usr/bin/python

import logging

logger = logging.getLogger('dev')
logger.setLevel(logging.INFO)

consoleHandler = logging.StreamHandler()
consoleHandler.setLevel(logging.INFO)

logger.addHandler(consoleHandler)

formatter = logging.Formatter('%(asctime)s  %(name)s  %(levelname)s: %(message)s')
consoleHandler.setFormatter(formatter)

logger.info('information message')

该示例创建一个控制台日志记录器,并向其处理器添加一个格式化器。

formatter = logging.Formatter('%(asctime)s  %(name)s  %(levelname)s: %(message)s')

创建了格式化器。它包括日期时间、日志记录器名称、日志记录级别名称和日志消息。

consoleHandler.setFormatter(formatter)

使用 setFormatter 将格式化器设置为处理器。

$ formatter.py
2019-03-28 14:53:27,446  dev  INFO: information message

控制台显示了具有已定义格式的消息。

Python 日志记录 basicConfig

basicConfig 配置根日志记录器。它通过创建具有默认格式化器的流处理器来对日志记录系统进行基本配置。如果根日志记录器没有定义任何处理器,debuginfowarningerrorcritical 会自动调用 basicConfig

basic_config.py
#!/usr/bin/python

import logging

logging.basicConfig(filename='test.log', format='%(filename)s: %(message)s',
                    level=logging.DEBUG)

logging.debug('This is a debug message')
logging.info('This is an info message')
logging.warning('This is a warning message')
logging.error('This is an error message')
logging.critical('This is a critical message')

该示例使用 basicConfig 配置根日志记录器。

logging.basicConfig(filename='test.log', format='%(filename)s: %(message)s',
    level=logging.DEBUG)

使用 filename,我们设置写入日志消息的文件。format 决定了写入文件的内容;我们有文件名和消息。使用 level,我们设置日志记录阈值。

$ basic_config.py
$ cat test.log
basic_config.py: This is a debug message
basic_config.py: This is an info message
basic_config.py: This is a warning message
basic_config.py: This is an error message
basic_config.py: This is a critical message

运行程序后,test.log 文件中写入了五个消息。

Python 日志记录 fileConfig

fileConfig 从 configparser 格式的文件读取日志记录配置。

log.conf
[loggers]
keys=root,dev

[handlers]
keys=consoleHandler

[formatters]
keys=extend,simple

[logger_root]
level=INFO
handlers=consoleHandler

[logger_dev]
level=INFO
handlers=consoleHandler
qualname=dev
propagate=0

[handler_consoleHandler]
class=StreamHandler
level=INFO
formatter=extend
args=(sys.stdout,)

[formatter_extend]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s

[formatter_simple]
format=%(asctime)s - %(message)s

log.conf 定义了一个日志记录器、一个处理器和一个格式化器。

file_config.py
#!/usr/bin/python

import logging
import logging.config

logging.config.fileConfig(fname='log.conf')

logger = logging.getLogger('dev')
logger.info('This is an information message')

该示例从 log.conf 读取日志记录配置文件。

$ file_config.py
2019-03-28 15:26:31,137 - dev - INFO - This is an information message

Python 日志记录变量

通过使用字符串格式化来记录动态数据。

log_variable.py
#!/usr/bin/python

import logging

root = logging.getLogger()
root.setLevel(logging.INFO)

log_format = '%(asctime)s %(filename)s: %(message)s'
logging.basicConfig(filename="test.log", format=log_format)

# incident happens

error_message = 'authentication failed'

root.error(f'error: {error_message}')

该示例将自定义数据写入日志消息。

2019-03-21 14:17:23,196 log_variable.py: error: authentication failed

这是日志消息。

Python 日志记录格式化日期时间

日期时间包含在带有 asctime 日志记录的日志消息中。使用 datefmt 配置选项,我们可以格式化日期时间字符串。

date_time.py
#!/usr/bin/python

import logging

logger = logging.getLogger()
logger.setLevel(logging.DEBUG)

log_format = '%(asctime)s %(filename)s: %(message)s'
logging.basicConfig(filename="test.log", format=log_format,
                    datefmt='%Y-%m-%d %H:%M:%S')

logger.info("information message")

该示例格式化了日志消息的日期时间。

log_format = '%(asctime)s %(filename)s: %(message)s'

我们使用 asctime 将日期时间字符串包含在日志中。

logging.basicConfig(filename="test.log", format=log_format,
                    datefmt='%Y-%m-%d %H:%M:%S')

datefmt 选项格式化日期时间字符串。

2019-03-21 14:17:23,196 log_variable.py: error: authentication failed
2019-03-21 14:23:33 date_time.py: information message

请注意日期时间字符串格式的差异。

Python 日志记录堆栈跟踪

堆栈跟踪是函数调用栈,显示了在抛出异常之前运行的函数。堆栈跟踪包含在 exc_info 选项中。

stack_trace.py
#!/usr/bin/python

import logging

log_format = '%(asctime)s %(filename)s: %(message)s'
logging.basicConfig(filename="test.log", format=log_format)

vals = [1, 2]

try:
    print(vals[4])

except Exception as e:
    logging.error("exception occurred", exc_info=True)

在示例中,我们记录了尝试访问不存在的列表索引时抛出的异常。

logging.error("exception occurred", exc_info=True)

通过将 exc_info 设置为 True,将堆栈跟踪包含在日志中。

2019-03-21 14:56:21,313 stack_trace.py: exception occurred
Traceback (most recent call last):
  File "C:\Users\Jano\Documents\pyprogs\pylog\stack_trace.py", line 11, in <module>
    print(vals[4])
IndexError: list index out of range

日志中包含了堆栈跟踪。

Python 日志记录 getLogger

getLogger 返回一个具有指定名称的日志记录器。如果未指定名称,它将返回根日志记录器。将模块名称放在那里是一个常见的做法,使用 __name__

所有对该函数的调用,只要给定的名称相同,就会返回相同的日志记录器实例。这意味着日志记录器实例永远不需要在应用程序的不同部分之间传递。

get_logger.py
#!/usr/bin/python

import logging
import sys

main = logging.getLogger('main')
main.setLevel(logging.DEBUG)

handler = logging.FileHandler('my.log')

format = logging.Formatter('%(asctime)s  %(name)s
    %(levelname)s: %(message)s')
handler.setFormatter(format)

main.addHandler(handler)

main.info('info message')
main.critical('critical message')
main.debug('debug message')
main.warning('warning message')
main.error('error message')

该示例使用 getLogger 创建了一个新的日志记录器。它被赋予了一个文件处理器和一个格式化器。

main = logging.getLogger('main')
main.setLevel(logging.DEBUG)

创建了一个名为 main 的日志记录器;我们将日志记录级别设置为 DEBUG

handler = logging.FileHandler('my.log')

创建了一个文件处理器。消息将被写入 my.log 文件。

format = logging.Formatter('%(asctime)s  %(name)s
    %(levelname)s: %(message)s')
handler.setFormatter(format)

创建了一个格式化器。它将时间、日志记录器名称、日志级别和消息包含在日志中。使用 setFormatter 将格式化器设置到处理器。

main.addHandler(handler)

使用 addHandler 将处理器添加到日志记录器。

$ cat my.log
2019-03-21 14:15:45,439  main  INFO: info message
2019-03-21 14:15:45,439  main  CRITICAL: critical message
2019-03-21 14:15:45,439  main  DEBUG: debug message
2019-03-21 14:15:45,439  main  WARNING: warning message
2019-03-21 14:15:45,439  main  ERROR: error message

这些是写入的日志消息。

Python 日志记录 YAML 配置

日志记录的详细信息可以在 YAML 配置文件中定义。YAML 是一种人类可读的数据序列化语言。它通常用于配置文件。

$ pip install pyyaml

我们需要安装 pyyaml 模块。

config.yaml
version: 1

formatters:
  simple:
    format: "%(asctime)s %(name)s: %(message)s"
  extended:
    format: "%(asctime)s %(name)s %(levelname)s: %(message)s"

handlers:
  console:
    class: logging.StreamHandler
    level: INFO
    formatter: simple

  file_handler:
    class: logging.FileHandler
    level: INFO
    filename: test.log
    formatter: extended
    propagate: false

loggers:
  dev:
    handlers: [console, file_handler]
  test:
    handlers: [file_handler]
root:
  handlers: [file_handler]

在配置文件中,我们定义了各种格式化器、处理器和日志记录器。propagate 选项可防止日志消息传播到父日志记录器;在我们的例子中,是根日志记录器。否则,消息将被复制。

log_yaml.py
#!/usr/bin/python

import logging
import logging.config
import yaml

with open('config.yaml', 'r') as f:

    log_cfg = yaml.safe_load(f.read())

logging.config.dictConfig(log_cfg)

logger = logging.getLogger('dev')
logger.setLevel(logging.INFO)

logger.info('This is an info message')
logger.error('This is an error message')

在示例中,我们读取配置文件并使用 dev 日志记录器。

$ log_yaml.py
2019-03-28 11:36:54,854 dev: This is an info message
2019-03-28 11:36:54,855 dev: This is an error message

当我们运行程序时,控制台上有两条消息。控制台处理器使用简单的格式化器,信息较少。

...
2019-03-28 11:36:54,854 dev INFO: This is an info message
2019-03-28 11:36:54,855 dev ERROR: This is an error message

test.log 文件中有日志消息。它们由扩展的格式化器生成,信息量更多。

来源

Python 的日志记录工具

在本文中,我们使用了 Python 日志记录库。

作者

我叫 Jan Bodnar,我是一名热情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直在撰写编程文章。迄今为止,我已撰写了 1,400 多篇文章和 8 本电子书。我在教学编程方面拥有十多年的经验。

列出所有 Python 教程