深入理解Python中的日志记录(Logging)

🚀 个人主页:xmp65535

🚀 专栏:python技术专栏


目录

一、Python Logging 模块概述

二、相关组件

Logger

Handler

Formatter

Filter

LogRecord

Level

三、基本配置

Formatters格式:

四、记录日志

五、日志记录者(Loggers)

logging.getLogger(name=None)

六、处理器(Handlers)

logging.Handler()

RotatingFileHandler和TimedRotatingFileHandler:

七、进阶使用

日志消息的过滤

日志流的重定向

日志的异步写入

八、propagate属性

九、结论


在软件开发过程中,了解应用程序的运行状态对于调试、监控和解决问题至关重要。日志记录是捕获这些信息的关键工具之一。Python提供了一个强大的日志记录系统,即logging模块,它为应用程序提供了一套标准的日志记录接口。

一、Python Logging 模块概述

logging模块是Python的标准库的一部分,它使得追踪事件、诊断问题和调试应用程序变得容易。它提供了不同的日志级别,如DEBUG、INFO、WARNING、ERROR和CRITICAL,这可以帮助开发者根据重要性区分日志消息。

二、相关组件

在Python的logging模块中,有几个重要的组件构成了日志系统的基础。下表概述了这些组件以及它们的作用:

组件描述
Logger日志记录器,提供了应用程序可直接使用的接口来记录消息。
Handler处理器负责将日志记录(由Logger创建)发送到合适的目的地。
Formatter格式化器决定日志记录的最终输出格式。
Filter过滤器提供了更细粒度的工具来确定输出哪些日志记录。
LogRecord日志记录对象,包含了所有日志信息的数据结构。
Level日志级别如DEBUG、INFO、WARNING、ERROR和CRITICAL,用于区分消息的严重性。

Logger

  • 描述: Logger是日志记录的主体,提供了应用程序用来记录日志的接口。一个应用程序中可以有多个Logger实例,它们可以通过名称来区分。Logger的层级结构允许以继承和重载设置的方式对日志记录进行灵活管理。
  • 用途: 记录应用程序的操作信息,如调试信息、执行流程、异常等。
  • 方法示例: debug(), info(), warning(), error(), critical()等方法,对应不同的日志级别。

    Handler

    • 描述: Handler用于将日志记录发送到指定的目的地。Python的logging模块提供了多种Handler,比如将日志写入文件的FileHandler,使用网络协议发送日志的SocketHandler,将日志输出到控制台的StreamHandler等。
    • 用途: 控制日志的输出行为,包括日志存储的位置和方式。
    • 类型示例: StreamHandler, FileHandler, HTTPHandler, SMTPHandler等。

      Formatter

      • 描述: Formatter定义了日志记录的输出格式。通过Formatter,可以控制日志信息的结构和内容,比如时间戳、日志级别、消息文本等的排列方式和格式。
      • 用途: 定制日志信息的展示格式,以便更好地理解和分析日志。
      • 配置方法: 通过设置格式字符串来定义日志的输出格式,如"%(asctime)s - %(name)s - %(levelname)s - %(message)s"。

        Filter

        • 描述: Filter提供了一种过滤日志记录的机制。可以在Logger或Handler级别上应用Filter,只允许符合特定条件的日志记录通过。
        • 用途: 实现更精细化的日志控制,比如只记录特定级别以上的日志,或者只记录包含特定文本的日志。
        • 实现方式: 继承logging.Filter类并重写filter()方法。

          LogRecord

          • 描述: LogRecord是一个日志记录实例,包含了日志事件的所有信息,如日志消息、发生时间、日志级别等。
          • 用途: 在内部传递日志信息。当Logger生成一条日志消息时,它会创建一个LogRecord对象,该对象随后被传递给相应的Handlers进行处理。
          • 属性示例: name, levelno, pathname, msg, args等。

            Level

            • 描述: 日志级别表示了日志消息的严重性。logging模块预定义了多个级别,包括DEBUG、INFO、WARNING、ERROR和CRITICAL。
            • 用途: 通过设置不同的日志级别,可以控制日志的输出粒度和范围,比如在开发过程中使用DEBUG级别以获得尽可能多的信息,在生产环境中使用WARNING或更高级别以避免信息泛滥。
            • 级别值: DEBUG < INFO < WARNING < ERROR < CRITICAL,值越大表示日志级别越高。DEBUG: 10、INFO: 20、WARNING: 30、ERROR: 40、CRITICAL: 50
            • 注:级别的数值表明了它们的优先级:数值更低的级别表示更详细的、经常是用于调试目的的信息,而数值更高的级别表示更严重的情况。当你设置一个日志记录器(Logger)的级别时,它会记录该级别以及比该级别更高级别的所有日志消息。例如,如果设置日志级别为INFO,则记录器会记录INFO, WARNING, ERROR和CRITICAL级别的消息,而忽略DEBUG级别的消息。

三、基本配置

在开始记录日志之前,需要了解如何配置日志系统。基本配置可以通过调用logging.basicConfig()函数来完成。这个函数可以指定日志级别、输出格式、输出位置等参数,从而自定义日志记录行为。

需要注意的是,logging.basicConfig 方法只能在应用程序中调用一次。如果多次调用 logging.basicConfig 方法,它只会生效一次。因此,如果需要在应用程序中使用多个不同的日志记录器和处理器,最好使用 logging.getLogger() 和 logging.Handler() 方法手动进行配置。

logging.basicConfig() 方法有多个可选参数,用于自定义日志记录行为。下面是 logging.basicConfig 方法的参数详解:

  • filename:将日志记录到指定的文件中。如果不指定此参数,则日志消息将被输出到标准输出流中(控制台)。
  • filemode:指定日志文件的打开模式,默认为 'a'(追加模式)。如果设置为 'w',则每次运行程序都会清空日志文件中的内容。
  • format:指定日志输出格式。默认格式为 '%(levelname)s:%(name)s:%(message)s'。常用格式包括:
    •  '%(asctime)s %(levelname)s %(message)s':显示时间、级别和消息。
    • '%(levelname)s:%(name)s:%(message)s':显示级别、记录器名称和消息。
    • '%(asctime)s %(levelname)s %(name)s:%(message)s':显示时间、级别、记录器名称和消息。
  • datefmt:指定时间格式。默认格式为 '%Y-%m-%d %H:%M:%S'。具体的时间格式化代码可以参考 Python 的 datetime 模块。
  • level:指定日志级别。低于此级别的日志消息将不会被记录。默认级别为 logging.WARNING。可以使用以下级别:
    • logging.DEBUG:详细的调试信息。
    • logging.INFO:普通的信息消息。
    • logging.WARNING:警告消息。
    • logging.ERROR:错误消息。
    • logging.CRITICAL:严重的错误消息。
  • stream:指定日志输出流。默认为 sys.stderr。

Formatters格式:

属性

格式

描述

asctime

%(asctime)s

日志产生的时间,默认格式为msecs2003-07-0816:49:45,896

msecs

%(msecs)d

日志生成时间的亳秒部分

created

%(created)f

time.tme)生成的日志创建时间戳

message

%(message)s

具体的日志信息内容

filename

%(filename)s

生成日志的程序名

name

%(name)s

日志调用者

funcname

%(funcName)s

调用日志的函数名

levelname

%(levelname)s

日志级別( DEBUG,INFO, WARNING, 'ERRORCRITICAL)

levene

%( leveling)s

日志级别对应的数值

lineno

%(lineno)d

日志所针对的代码行号(如果可用的话)

module

%( module)s

生成日志的模块名

pathname

%( pathname)s

生成日志的文件的完整路径

process

%( (process)d

生成日志的进程D(如果可用)

processname

%(processName)s

进程名(如果可用)

thread

%(thread)d

生成日志的线程D(如果可用)

threadname

%( threadName)s

线程名(如果可用)

exc_info

%(exc_info)s

如果日志消息由异常触发,则包括异常信息。

relativeCreated

%(relativeCreated)d

日志消息被创建的时间(以毫秒为单位),相对于 Logger 对象被创建的时间。

下面是一个logging.basicConfig的使用示例:

import logging
# 配置日志记录器
logging.basicConfig(
    filename='example.log',
    filemode='w',
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S',
    level=logging.DEBUG
)
# 记录日志
logging.debug('debug message')
logging.info('info message')
logging.warning('warning message')
logging.error('error message')
logging.critical('critical message')
在这个示例中,我们使用 logging.basicConfig 方法自定义了日志记录行为,将日志记录到文件 example.log 中,使用追加模式,指定了输出格式和时间格式,将日志级别设置为 DEBUG。最后,我们记录了不同级别的日志消息。

四、记录日志

配置完成后,可以使用不同的日志级别方法来记录消息:

logging.debug('这是一个debug信息')
logging.info('这是一个info信息')
logging.warning('这是一个warning信息')
logging.error('这是一个error信息')
logging.critical('这是一个critical信息')

五、日志记录者(Loggers)

日志记录者是日志操作的主要接口。可以创建多个记录者,它们可以有独立的配置和日志级别。默认情况下,可以使用logging.getLogger()获取一个记录者,并通过名称区分。

logging.getLogger(name=None)

getLogger()方法用于获取一个日志记录器(Logger),这个记录器可以是一个新的记录器,也可以是一个已经存在的记录器。

  • 如果你调用getLogger()并且不传递任何参数或者传递None作为参数,那么你将会获取到一个名为'root'的日志记录器。
  • 如果传递了一个字符串作为参数,getLogger(name)将会返回一个以该字符串命名的日志记录器。

每个Logger都可以有独立的日志级别、处理器和格式化器。这样可以灵活配置日志信息的记录方式。例如:

import logging
# 获取名为'my_logger'的Logger
logger = logging.getLogger('my_logger')
logger.setLevel(logging.DEBUG) # 设置日志级别为DEBUG
# 创建一个Handler来将日志写入文件
file_handler = logging.FileHandler('my_logger.log')
file_handler.setLevel(logging.ERROR) # 该Handler只记录ERROR及以上级别的日志
# 创建一个Handler来将日志输出到控制台
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO) # 控制台仅记录INFO及以上级别的日志
# 设置日志格式
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
# 将Handler添加到Logger
logger.addHandler(file_handler)
logger.addHandler(console_handler)
# 使用不同的日志级别记录消息
logger.debug('这是一条DEBUG级别的消息')  # 不会被任何处理器记录,因为它低于它们的设置级别
logger.info('这是一条INFO级别的消息')    # 会被console_handler处理并显示在控制台
logger.warning('这是一条WARNING级别的消息')  # 会被console_handler处理并显示在控制台
logger.error('这是一条ERROR级别的消息')    # 会被file_handler和console_handler处理,并记录到文件和控制台
logger.critical('这是一条CRITICAL级别的消息') # 同上

六、处理器(Handlers)

如果要将日志记录发送到不同的目的地,比如文件或者网络,就需要使用处理器(Handlers)。每个处理器可以有自己的日志级别,格式化器(Formatter)和过滤器(Filter)。

logging.Handler()

Handler是所有日志处理器(Handler)的基类。在logging模块中,处理器用于定义日志的输出行为,比如将日志发送到控制台、文件、网络等。通过继承Handler类,可以创建自定义的日志处理器。然而,在大多数情况下,你会使用Handler的子类,如:

  • StreamHandler: 将日志输出到像控制台这样的流。
  • FileHandler: 将日志写入到文件中。
  • RotatingFileHandler: 将日志写入到文件,并在文件到达一定大小后进行滚动。
  • SocketHandler: 将日志通过TCP协议发送到网络。
  • SMTPHandler: 通过电子邮件发送日志。

每个Handler可以有自己的日志级别和格式化器。如果不显式设置Handler级别,它将默认处理所有级别的日志。例如:

# 创建一个文件处理器,并设置级别为ERROR
file_handler = logging.FileHandler('error.log')
file_handler.setLevel(logging.ERROR)
# 创建一个格式化器,并设置相应的格式
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
# 将处理器添加到记录者
logger.addHandler(file_handler)
# 这条ERROR级别的信息将被写入'error.log'文件中
logger.error('这是一个error信息')

RotatingFileHandler和TimedRotatingFileHandler:

在Python的logging库中,RotatingFileHandler和TimedRotatingFileHandler都是用于将日志消息写入到文件中的处理器类。它们的主要区别在于如何管理日志文件的大小或数量。

RotatingFileHandler会按照一定的规则(比如文件大小或保留的文件数量)来自动滚动日志文件,从而避免单个文件过大或存储过多历史日志的问题。当达到指定条件时,RotatingFileHandler会自动将当前日志文件重命名为旧的备份文件,并创建一个新的日志文件来记录最新的日志消息。可以使用maxBytes和backupCount两个参数来控制每个日志文件的最大大小和保存的备份文件数目。

例如,下面是一个使用RotatingFileHandler的示例:

import logging
from logging.handlers import RotatingFileHandler
logger = logging.getLogger(__name__)
# 创建RotatingFileHandler对象,设置日志输出路径、最大文件大小和备份数量
handler = RotatingFileHandler('mylog.log', maxBytes=1024*1024, backupCount=5)
# 设置日志级别和输出格式
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
# 将处理器添加到日志记录器中
logger.addHandler(handler)
# 记录日志
logger.info("This is a test message")

与RotatingFileHandler不同,TimedRotatingFileHandler会按照一定的时间间隔来自动滚动日志文件,从而避免存储过多历史日志的问题。TimedRotatingFileHandler会根据指定的时间间隔(比如每小时、每天或每周)创建新的日志文件,并将当前日志消息写入到这个最新的日志文件中。可以使用when和interval两个参数来控制时间间隔。

例如,下面是一个使用TimedRotatingFileHandler的示例:

import logging
from logging.handlers import TimedRotatingFileHandler
logger = logging.getLogger(__name__)
# 创建TimedRotatingFileHandler对象,设置日志输出路径、时间间隔和备份数量
handler = TimedRotatingFileHandler('mylog.log', when='D', interval=1, backupCount=5)
# 设置日志级别和输出格式
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
# 将处理器添加到日志记录器中
logger.addHandler(handler)
# 记录日志
logger.info("This is a test message")
#在这个示例中,我们使用TimedRotatingFileHandler创建一个每天滚动一次的日志文件,最多保留5个历史日志文件。当时间间隔到达时,TimedRotatingFileHandler会自动将当前日志文件重命名为旧的备份文件,并创建一个新的日志文件来记录最新的日志消息。

七、进阶使用

日志系统的高级功能包括日志消息的过滤、日志流的重定向以及日志的异步写入等。可以利用过滤器按照具体需求筛选日志信息,或者创建自定义的日志处理器来扩展其功能。

日志消息的过滤

过滤器(Filter)可用于对日志消息进行额外的控制,它允许更精细的日志消息输出。比如,你可能只想记录特定条件下的日志消息。

import logging
class InfoFilter(logging.Filter):
    def filter(self, record):
        return record.levelno == logging.INFO
logger = logging.getLogger('my_advanced_logger')
logger.setLevel(logging.DEBUG)
# 创建一个流处理器,并设置级别为DEBUG
stream_handler = logging.StreamHandler()
stream_handler.setLevel(logging.DEBUG)
# 为流处理器设置过滤器,只记录INFO级别的日志
stream_handler.addFilter(InfoFilter())
# 为logger添加处理器
logger.addHandler(stream_handler)
# 下面的日志中,只有INFO级别的日志会被输出
logger.debug('这是一个debug信息')
logger.info('这是一个info信息')
logger.warning('这是一个warning信息')
logger.error('这是一个error信息')

日志流的重定向

有时候,将日志流重定向到除了标准输出之外的地方可能非常有用,比如一个文件或者网络。下面的代码示例展示了如何将日志输出到一个文件中。

import logging
# 创建logger
file_logger = logging.getLogger('fileLogger')
file_logger.setLevel(logging.DEBUG)
# 创建一个文件处理器
file_handler = logging.FileHandler('my_log.log')
file_handler.setLevel(logging.DEBUG)
# 创建一个格式化器,并添加到文件处理器上
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
# 将文件处理器添加到logger
file_logger.addHandler(file_handler)
# 记录一条信息
file_logger.info('这是保存到文件的信息')

日志的异步写入

在一些高性能要求的应用中,异步日志记录可以减少日志记录对应用性能的影响。Python的logging模块自身不直接支持异步记录,但可以使用concurrent.futures模块或其他异步框架来实现。以下是一个简单的使用concurrent.futures.ThreadPoolExecutor来实现异步日志记录的示例。

import logging
from concurrent.futures import ThreadPoolExecutor
# 创建logger
async_logger = logging.getLogger('asyncLogger')
async_logger.setLevel(logging.DEBUG)
# 添加简单的流处理器
stream_handler = logging.StreamHandler()
async_logger.addHandler(stream_handler)
# 定义一个异步记录日志的函数
def async_log(message):
    async_logger.info(message)
# 使用线程池执行器来异步记录日志
with ThreadPoolExecutor() as executor:
    executor.submit(async_log, '这是异步记录的信息')

八、propagate属性

在Python的logging模块中,propagate是Logger对象的一个属性,用于控制日志信息是否传播(或称为“向上传递”)到Logger的父级。默认情况下,这个属性被设置为True。

当一个Logger对象生成一条日志记录时,如果propagate设置为True,那么除了在当前Logger配置的所有Handlers上处理这条日志记录外,它还会将这条日志记录传递给更高层次的Logger(即它的父级),由父级Logger的Handlers也对这条日志进行处理。这种机制允许创建一个日志处理的层次结构,使得可以很方便地控制不同模块(或不同部分代码)的日志处理方式。

例如,假设我们有一个“root”Logger,以及一个名为"module"的子Logger,子Logger的propagate属性设置为True:

import logging
# 配置root Logger
root_logger = logging.getLogger()
root_handler = logging.StreamHandler()
root_formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
root_handler.setFormatter(root_formatter)
root_logger.addHandler(root_handler)
root_logger.setLevel(logging.DEBUG)
# 配置module Logger
module_logger = logging.getLogger('module')
module_logger.setLevel(logging.DEBUG)
# 发送日志消息
module_logger.debug('这条消息将会在root_logger的handler中也被看到,因为propagate默认为True')

在这个例子中,当调用module_logger.debug()时,该消息会在module_logger的Handlers上处理,若module_logger没有配置任何Handler,消息会向上传递到root_logger,并且在root_logger配置的Handler(这里是root_handler)中输出。

如果你不希望日志在层次结构中向上传递,可以将propagate属性设置为False:

module_logger.propagate = False
module_logger.debug('这条消息不会传播到root_logger,只会在module_logger的handler中被处理')

当设置propagate为False时,module_logger生成的日志记录将不会传递到root_logger,只会在module_logger配置的Handlers中被处理。如果module_logger没有配置任何Handler,这条日志信息将不会被输出到任何地方。这样,可以对日志记录进行更精细的控制,避免不必要的重复日志或者控制特定Logger的日志不被上层Logger截获。

九、结论

合理使用logging模块可以极大提升应用程序的可维护性。通过不同的日志级别,格式化和处理器,可以创建出能够满足不同需求的灵活的日志记录方案。无论是进行问题排查还是系统行为分析,logging模块都是Python开发者的得力助手。