Python logging 模块基本使用(37)

14 阅读18分钟

Python logging 模块基本使用

一、引言

在 Python 编程中,日志记录是一项至关重要的功能。无论是开发小型脚本还是大型项目,都需要一种有效的方式来记录程序运行时的信息,比如错误、警告、调试信息等。这些日志信息不仅有助于在程序出现问题时进行调试和排查,还能在程序正常运行时监控程序的状态和性能。Python 的 logging 模块提供了一个灵活且强大的日志记录工具,它允许开发者对日志的输出格式、级别、目标位置等进行细致的控制。本文将深入介绍 logging 模块的基本使用,通过大量的源码示例和详细的注释,帮助你全面掌握该模块的使用方法。

二、logging 模块概述

2.1 日志记录的重要性

日志记录在软件开发中扮演着关键角色,主要体现在以下几个方面:

  • 调试:当程序出现错误时,日志可以记录程序执行过程中的关键信息,帮助开发者定位问题所在。
  • 监控:在程序运行过程中,日志可以记录系统的状态和性能信息,方便进行监控和分析。
  • 审计:对于一些需要合规性的应用,日志可以记录用户的操作和系统的变化,用于审计和追溯。

2.2 logging 模块的特点

Python 的 logging 模块具有以下特点:

  • 灵活性:可以通过配置来控制日志的输出格式、级别、目标位置等。
  • 层次性:支持创建不同层次的日志记录器,方便对不同模块或功能进行独立的日志管理。
  • 可扩展性:可以自定义日志处理器、格式化器等,以满足不同的需求。

三、基本日志记录

3.1 简单的日志记录示例

下面是一个简单的使用 logging 模块进行日志记录的示例:

import logging

# 配置日志记录的基本设置
# 这里设置日志的级别为 DEBUG,即记录所有级别的日志
# 日志的输出格式为包含时间、日志级别和日志消息
# 日志将输出到控制台
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')

# 记录不同级别的日志
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 模块,然后使用 basicConfig 函数对日志记录进行了基本配置。basicConfig 函数接受多个参数,这里我们设置了日志的级别为 DEBUG,表示记录所有级别的日志;设置了日志的输出格式,包含时间、日志级别和日志消息;日志将输出到控制台。最后,我们使用不同级别的日志记录函数记录了一些日志信息。

3.2 日志级别

logging 模块定义了以下几个日志级别,从低到高依次为:

  • DEBUG:详细的调试信息,通常在开发和调试阶段使用。
  • INFO:确认程序按预期运行的信息。
  • WARNING:表明可能存在问题,但程序仍能正常运行的信息。
  • ERROR:记录程序中出现的错误,但程序可能仍能继续运行。
  • CRITICAL:严重错误,表明程序可能无法继续运行。

可以通过 basicConfig 函数的 level 参数来设置日志的级别,只有级别等于或高于该设置的日志才会被记录。例如:

import logging

# 配置日志记录的基本设置,将日志级别设置为 INFO
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

# 记录不同级别的日志
logging.debug('This is a debug message')  # 由于级别低于 INFO,不会被记录
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')  # 会被记录

在这个示例中,我们将日志级别设置为 INFO,因此 DEBUG 级别的日志不会被记录,而 INFOWARNINGERRORCRITICAL 级别的日志会被记录。

四、日志记录器(Logger)

4.1 什么是日志记录器

日志记录器是 logging 模块的核心组件之一,它负责创建和管理日志记录。每个日志记录器都有一个名称,通过名称可以对不同的日志记录器进行区分和管理。日志记录器可以设置自己的日志级别,并且可以添加多个日志处理器(Handler)来控制日志的输出位置。

4.2 创建和使用日志记录器

下面是一个创建和使用日志记录器的示例:

import logging

# 创建一个名为 'my_logger' 的日志记录器
logger = logging.getLogger('my_logger')

# 设置日志记录器的级别为 DEBUG
logger.setLevel(logging.DEBUG)

# 创建一个控制台处理器,用于将日志输出到控制台
console_handler = logging.StreamHandler()

# 创建一个格式化器,定义日志的输出格式
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# 将格式化器添加到控制台处理器
console_handler.setFormatter(formatter)

# 将控制台处理器添加到日志记录器
logger.addHandler(console_handler)

# 使用日志记录器记录不同级别的日志
logger.debug('This is a debug message from my_logger')
logger.info('This is an info message from my_logger')
logger.warning('This is a warning message from my_logger')
logger.error('This is an error message from my_logger')
logger.critical('This is a critical message from my_logger')

在这个示例中,我们首先使用 getLogger 函数创建了一个名为 'my_logger' 的日志记录器。然后,我们设置了该日志记录器的级别为 DEBUG。接着,我们创建了一个控制台处理器 StreamHandler,用于将日志输出到控制台。我们还创建了一个格式化器 Formatter,定义了日志的输出格式,并将其添加到控制台处理器中。最后,我们将控制台处理器添加到日志记录器中,并使用日志记录器记录了不同级别的日志。

4.3 日志记录器的层次结构

logging 模块中的日志记录器具有层次结构,根日志记录器是所有日志记录器的父级。可以通过日志记录器的名称来表示其层次关系,例如 'parent.child' 表示 'child''parent' 的子日志记录器。子日志记录器会继承父日志记录器的一些属性,如日志级别和处理器。例如:

import logging

# 获取根日志记录器
root_logger = logging.getLogger()

# 设置根日志记录器的级别为 INFO
root_logger.setLevel(logging.INFO)

# 创建一个控制台处理器,用于将日志输出到控制台
console_handler = logging.StreamHandler()

# 创建一个格式化器,定义日志的输出格式
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# 将格式化器添加到控制台处理器
console_handler.setFormatter(formatter)

# 将控制台处理器添加到根日志记录器
root_logger.addHandler(console_handler)

# 创建一个名为 'parent' 的日志记录器
parent_logger = logging.getLogger('parent')

# 创建一个名为 'parent.child' 的子日志记录器
child_logger = logging.getLogger('parent.child')

# 记录日志
parent_logger.info('This is an info message from parent_logger')
child_logger.info('This is an info message from child_logger')

在这个示例中,我们首先获取了根日志记录器,并设置了其级别为 INFO,同时添加了一个控制台处理器。然后,我们创建了一个名为 'parent' 的日志记录器和一个名为 'parent.child' 的子日志记录器。由于子日志记录器会继承父日志记录器的属性,因此 'parent.child' 日志记录器也会使用根日志记录器的级别和处理器。最后,我们使用这两个日志记录器记录了一些信息日志。

五、日志处理器(Handler)

5.1 什么是日志处理器

日志处理器负责将日志记录发送到不同的目标位置,如控制台、文件、邮件等。logging 模块提供了多种内置的日志处理器,也可以自定义日志处理器。

5.2 常见的日志处理器

5.2.1 控制台处理器(StreamHandler)

控制台处理器用于将日志记录输出到控制台。在前面的示例中,我们已经使用过控制台处理器。下面是一个简单的示例:

import logging

# 创建一个日志记录器
logger = logging.getLogger('console_logger')

# 设置日志记录器的级别为 DEBUG
logger.setLevel(logging.DEBUG)

# 创建一个控制台处理器
console_handler = logging.StreamHandler()

# 创建一个格式化器
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# 将格式化器添加到控制台处理器
console_handler.setFormatter(formatter)

# 将控制台处理器添加到日志记录器
logger.addHandler(console_handler)

# 记录日志
logger.debug('This is a debug message for console output')
5.2.2 文件处理器(FileHandler)

文件处理器用于将日志记录输出到文件中。下面是一个使用文件处理器的示例:

import logging

# 创建一个日志记录器
logger = logging.getLogger('file_logger')

# 设置日志记录器的级别为 DEBUG
logger.setLevel(logging.DEBUG)

# 创建一个文件处理器,指定日志文件的名称为 'app.log'
file_handler = logging.FileHandler('app.log')

# 创建一个格式化器
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# 将格式化器添加到文件处理器
file_handler.setFormatter(formatter)

# 将文件处理器添加到日志记录器
logger.addHandler(file_handler)

# 记录日志
logger.debug('This is a debug message for file output')

在这个示例中,我们创建了一个文件处理器 FileHandler,并指定了日志文件的名称为 'app.log'。然后,我们将文件处理器添加到日志记录器中,这样日志就会被记录到 app.log 文件中。

5.2.3 滚动文件处理器(RotatingFileHandler)

滚动文件处理器用于在日志文件达到一定大小时自动进行滚动,即创建新的日志文件,并将旧的日志文件进行备份。下面是一个使用滚动文件处理器的示例:

import logging
from logging.handlers import RotatingFileHandler

# 创建一个日志记录器
logger = logging.getLogger('rotating_logger')

# 设置日志记录器的级别为 DEBUG
logger.setLevel(logging.DEBUG)

# 创建一个滚动文件处理器,指定日志文件的名称为 'app_rotating.log'
# 最大文件大小为 1024 * 1024 字节(即 1MB),最多保留 5 个备份文件
rotating_handler = RotatingFileHandler('app_rotating.log', maxBytes=1024 * 1024, backupCount=5)

# 创建一个格式化器
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# 将格式化器添加到滚动文件处理器
rotating_handler.setFormatter(formatter)

# 将滚动文件处理器添加到日志记录器
logger.addHandler(rotating_handler)

# 循环记录大量日志,触发文件滚动
for i in range(10000):
    logger.debug(f'This is a debug message {i} for rotating file output')

在这个示例中,我们使用了 RotatingFileHandler 滚动文件处理器,指定了日志文件的名称为 'app_rotating.log',最大文件大小为 1MB,最多保留 5 个备份文件。当日志文件达到 1MB 时,会自动创建一个新的日志文件,并将旧的日志文件进行备份。

5.2.4 时间滚动文件处理器(TimedRotatingFileHandler)

时间滚动文件处理器用于按照时间间隔自动进行日志文件的滚动。下面是一个使用时间滚动文件处理器的示例:

import logging
from logging.handlers import TimedRotatingFileHandler

# 创建一个日志记录器
logger = logging.getLogger('timed_rotating_logger')

# 设置日志记录器的级别为 DEBUG
logger.setLevel(logging.DEBUG)

# 创建一个时间滚动文件处理器,指定日志文件的名称为 'app_timed_rotating.log'
# 按天进行滚动,即每天创建一个新的日志文件
timed_rotating_handler = TimedRotatingFileHandler('app_timed_rotating.log', when='D', interval=1, backupCount=5)

# 创建一个格式化器
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# 将格式化器添加到时间滚动文件处理器
timed_rotating_handler.setFormatter(formatter)

# 将时间滚动文件处理器添加到日志记录器
logger.addHandler(timed_rotating_handler)

# 记录日志
logger.debug('This is a debug message for timed rotating file output')

在这个示例中,我们使用了 TimedRotatingFileHandler 时间滚动文件处理器,指定了日志文件的名称为 'app_timed_rotating.log',按天进行滚动,即每天创建一个新的日志文件,最多保留 5 个备份文件。

5.3 为日志记录器添加多个处理器

可以为一个日志记录器添加多个处理器,这样日志就可以同时输出到不同的目标位置。下面是一个示例:

import logging

# 创建一个日志记录器
logger = logging.getLogger('multi_handler_logger')

# 设置日志记录器的级别为 DEBUG
logger.setLevel(logging.DEBUG)

# 创建一个控制台处理器
console_handler = logging.StreamHandler()

# 创建一个文件处理器,指定日志文件的名称为 'multi_handler.log'
file_handler = logging.FileHandler('multi_handler.log')

# 创建一个格式化器
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# 将格式化器添加到控制台处理器和文件处理器
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)

# 将控制台处理器和文件处理器添加到日志记录器
logger.addHandler(console_handler)
logger.addHandler(file_handler)

# 记录日志
logger.debug('This is a debug message for multiple handlers')

在这个示例中,我们为日志记录器添加了一个控制台处理器和一个文件处理器,这样日志既会输出到控制台,也会记录到 multi_handler.log 文件中。

六、日志格式化器(Formatter)

6.1 什么是日志格式化器

日志格式化器用于定义日志记录的输出格式。通过使用格式化器,可以控制日志记录中包含的信息,如时间、日志级别、日志消息等。

6.2 基本的日志格式化

logging 模块提供了一些内置的格式化占位符,常用的占位符如下:

  • %(asctime)s:日志记录的时间。
  • %(levelname)s:日志的级别。
  • %(name)s:日志记录器的名称。
  • %(message)s:日志消息。

下面是一个使用基本格式化占位符的示例:

import logging

# 创建一个日志记录器
logger = logging.getLogger('formatter_logger')

# 设置日志记录器的级别为 DEBUG
logger.setLevel(logging.DEBUG)

# 创建一个控制台处理器
console_handler = logging.StreamHandler()

# 创建一个格式化器,定义日志的输出格式
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# 将格式化器添加到控制台处理器
console_handler.setFormatter(formatter)

# 将控制台处理器添加到日志记录器
logger.addHandler(console_handler)

# 记录日志
logger.debug('This is a formatted debug message')

在这个示例中,我们使用 Formatter 类创建了一个格式化器,定义了日志的输出格式包含时间、日志记录器的名称、日志级别和日志消息。

6.3 自定义日志格式化

除了使用内置的格式化占位符,还可以自定义日志的输出格式。例如,可以使用 datefmt 参数来指定时间的显示格式。下面是一个自定义日志格式化的示例:

import logging

# 创建一个日志记录器
logger = logging.getLogger('custom_formatter_logger')

# 设置日志记录器的级别为 DEBUG
logger.setLevel(logging.DEBUG)

# 创建一个控制台处理器
console_handler = logging.StreamHandler()

# 创建一个格式化器,定义日志的输出格式
# 使用 datefmt 参数指定时间的显示格式为 '%Y-%m-%d %H:%M:%S'
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')

# 将格式化器添加到控制台处理器
console_handler.setFormatter(formatter)

# 将控制台处理器添加到日志记录器
logger.addHandler(console_handler)

# 记录日志
logger.debug('This is a custom formatted debug message')

在这个示例中,我们使用 datefmt 参数指定了时间的显示格式为 '%Y-%m-%d %H:%M:%S',这样日志记录中的时间将以指定的格式显示。

七、异常日志记录

7.1 记录异常信息

在程序中捕获异常时,可以使用 logging 模块记录异常信息,方便后续的调试和排查。下面是一个记录异常信息的示例:

import logging

# 创建一个日志记录器
logger = logging.getLogger('exception_logger')

# 设置日志记录器的级别为 ERROR
logger.setLevel(logging.ERROR)

# 创建一个控制台处理器
console_handler = logging.StreamHandler()

# 创建一个格式化器
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# 将格式化器添加到控制台处理器
console_handler.setFormatter(formatter)

# 将控制台处理器添加到日志记录器
logger.addHandler(console_handler)

try:
    # 尝试进行除法运算,故意引发一个除零异常
    result = 1 / 0
except ZeroDivisionError as e:
    # 使用 exception 方法记录异常信息,会自动记录异常的堆栈跟踪信息
    logger.exception('An error occurred: %s', e)

在这个示例中,我们使用 try-except 语句捕获了一个除零异常,并使用 logger.exception 方法记录了异常信息。exception 方法会自动记录异常的堆栈跟踪信息,方便我们定位问题。

7.2 记录异常堆栈跟踪信息

除了使用 exception 方法记录异常信息外,还可以使用 exc_info 参数来记录异常的堆栈跟踪信息。下面是一个示例:

import logging

# 创建一个日志记录器
logger = logging.getLogger('stack_trace_logger')

# 设置日志记录器的级别为 ERROR
logger.setLevel(logging.ERROR)

# 创建一个控制台处理器
console_handler = logging.StreamHandler()

# 创建一个格式化器
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# 将格式化器添加到控制台处理器
console_handler.setFormatter(formatter)

# 将控制台处理器添加到日志记录器
logger.addHandler(console_handler)

try:
    # 尝试进行除法运算,故意引发一个除零异常
    result = 1 / 0
except ZeroDivisionError as e:
    # 使用 error 方法记录异常信息,并设置 exc_info 参数为 True 以记录堆栈跟踪信息
    logger.error('An error occurred: %s', e, exc_info=True)

在这个示例中,我们使用 logger.error 方法记录异常信息,并设置 exc_info 参数为 True,这样会记录异常的堆栈跟踪信息。

八、日志配置

8.1 使用 basicConfig 进行简单配置

在前面的示例中,我们已经使用过 basicConfig 函数进行简单的日志配置。basicConfig 函数可以设置日志的级别、输出格式、目标位置等。下面是一个使用 basicConfig 进行配置的示例:

import logging

# 使用 basicConfig 函数进行日志配置
# 设置日志级别为 DEBUG
# 设置日志输出格式包含时间、日志级别和日志消息
# 设置日志输出到文件 'basic_config.log'
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s', filename='basic_config.log')

# 记录日志
logging.debug('This is a debug message configured by basicConfig')

8.2 使用配置文件进行配置

除了使用 basicConfig 函数进行配置外,还可以使用配置文件进行更复杂的日志配置。logging 模块支持使用 fileConfig 函数从配置文件中读取配置信息。下面是一个配置文件 logging.conf 的示例:

[loggers]
keys=root,my_logger

[handlers]
keys=console_handler,file_handler

[formatters]
keys=simple_formatter

[logger_root]
level=DEBUG
handlers=console_handler

[logger_my_logger]
level=DEBUG
handlers=console_handler,file_handler
qualname=my_logger
propagate=0

[handler_console_handler]
class=StreamHandler
level=DEBUG
formatter=simple_formatter
args=(sys.stdout,)

[handler_file_handler]
class=FileHandler
level=DEBUG
formatter=simple_formatter
args=('config_file.log',)

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

下面是使用该配置文件进行日志配置的 Python 代码:

import logging
import logging.config

# 从配置文件中读取日志配置信息
logging.config.fileConfig('logging.conf')

# 获取名为 'my_logger' 的日志记录器
logger = logging.getLogger('my_logger')

# 记录日志
logger.debug('This is a debug message configured by config file')

在这个示例中,我们创建了一个配置文件 logging.conf,在其中定义了日志记录器、处理器和格式化器的配置信息。然后,使用 logging.config.fileConfig 函数从配置文件中读取配置信息,并应用到日志系统中。

8.3 使用字典进行配置

还可以使用字典来配置日志系统,通过 logging.config.dictConfig 函数来应用配置。下面是一个使用字典进行配置的示例:

import logging
import logging.config

# 定义日志配置字典
log_config = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'simple': {
            'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
        }
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'level': 'DEBUG',
            'formatter': 'simple',
            'stream': 'ext://sys.stdout'
        },
        'file': {
            'class': 'logging.FileHandler',
            'level': 'DEBUG',
            'formatter': 'simple',
            'filename': 'dict_config.log'
        }
    },
    'loggers': {
        'my_logger': {
            'level': 'DEBUG',
            'handlers': ['console', 'file'],
            'propagate': False
        }
    },
    'root': {
        'level': 'DEBUG',
        'handlers': ['console']
    }
}

# 应用日志配置字典
logging.config.dictConfig(log_config)

# 获取名为 'my_logger' 的日志记录器
logger = logging.getLogger('my_logger')

# 记录日志
logger.debug('This is a debug message configured by dictionary')

在这个示例中,我们定义了一个日志配置字典 log_config,其中包含了日志记录器、处理器和格式化器的配置信息。然后,使用 logging.config.dictConfig 函数应用该配置字典。

九、总结与展望

9.1 总结

Python 的 logging 模块为开发者提供了一个强大而灵活的日志记录工具。通过使用日志记录器、处理器、格式化器等组件,可以对日志的输出格式、级别、目标位置等进行细致的控制。可以根据不同的需求选择合适的日志处理器,如控制台处理器、文件处理器、滚动文件处理器等。使用格式化器可以自定义日志的输出格式,方便查看和分析日志信息。在捕获异常时,可以使用 logging 模块记录异常信息和堆栈跟踪信息,有助于调试和排查问题。此外,还可以使用 basicConfig 函数、配置文件或字典来进行日志配置,以满足不同的配置需求。

9.2 展望

随着 Python 在各个领域的广泛应用,日志记录的重要性将更加凸显。未来,logging 模块可能会进一步优化和扩展,提供更多的功能和更好的性能。例如,可能会增加对分布式日志记录的支持,方便在分布式系统中进行日志管理;可能会提供更丰富的日志分析工具,帮助开发者更好地分析和利用日志信息。对于开发者来说,深入理解和掌握 logging 模块的使用方法,合理配置和使用日志记录,将有助于提高程序的可维护性和稳定性。同时,随着日志数据量的不断增加,如何高效地存储、管理和分析日志数据也将成为一个重要的研究方向。