Python日志记录

Python的logging模块是一个内置的标准库,它为编写程序时生成、记录和管理日志信息提供了强大而灵活的功能。日志对于软件开发至关重要,尤其是在调试、监控应用状态、追踪异常行为、分析性能瓶颈以及审计等方面。

入门级的logging应用主要是掌握如何在简单的Python程序中引入logging模块,设置基本的日志级别,记录日志信息,并选择适当的输出目的地(如控制台)。本篇文章就讲一讲logging的入门到进阶:

目录

一、日志级别

二、组件与工作流程

三、基本使用示例

1、导入logging模块并配置日志记录器

2、创建日志记录器实例

3、记录日志信息

4、查看日志输出

5、保存日志到文件

四、进阶

1、自定义日志格式

2、设置日志记录器层次结构

3、使用不同的处理器

4、实现日志文件轮转

1)、使用 RotatingFileHandler 实现按文件大小轮转

2)、使用 TimedRotatingFileHandler 实现按时间间隔轮转


一、日志级别

logging模块定义了多个日志级别,按严重性递增排列如下:

  1. DEBUG: 提供详细的内部信息,用于调试目的。
  2. INFO: 记录常规的运行信息,如系统状态、用户行为等。
  3. WARNING: 表示潜在的问题或非预期的情况,但程序仍能继续运行。
  4. ERROR: 报告程序运行时发生的错误,导致某些功能无法正常工作。
  5. CRITICAL: 指示严重的故障或系统即将崩溃的情况。

每个级别都对应一个整数值,值越大表示严重性越高。日志消息会被分配一个级别,只有当它的级别等于或高于所设置的日志记录器(logger)的阈值时,才会被实际记录和处理。

二、组件与工作流程

logging模块的核心组件包括:

  • Logger: 用于收集和分发日志事件。每个logger对象代表了程序中的某个部分或模块,可以独立设置日志级别和添加处理器。日志消息首先被发送到相应的logger。
  • Handler: 负责将logger传递过来的日志消息输出到适当的目的地,如控制台、文件、数据库等。常见的Handler类型有StreamHandler(输出到流,如sys.stdout或sys.stderr)、FileHandler(写入文件)、SMTPHandler(发送电子邮件)等。每个handler可以有自己的日志级别和格式化器。
  • Formatter: 定义日志消息的输出格式,包括时间戳、日志级别、消息内容、源文件名和行号等信息。可以自定义格式字符串以满足特定需求。
  • Filter: 可选组件,用于对日志消息进行额外的筛选。可以基于特定条件决定是否让某条日志消息通过,进一步细化日志记录的控制。

日志消息的处理流程大致如下:

用户通过logger对象调用相应级别的方法(如logger.debug()、logger.info()等)记录日志。
如果该消息级别不低于logger的阈值,消息会被传递给所有关联的handler。
handler检查消息级别是否达到自己的阈值,如果是,则使用关联的formatter格式化消息,并将格式化后的消息发送到指定的目标。

三、基本使用示例

1、导入logging模块并配置日志记录器

对于简单应用,可以使用logging.basicConfig()进行快速全局配置。例如:

import logging

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')


logging.basicConfig(
    level=logging.INFO,  # 设置日志级别为INFO
    format='%(asctime)s - %(levelname)s - %(message)s',  # 设置日志格式
    datefmt='%Y-%m-%d %H:%M:%S',  # 设置时间格式
    stream=sys.stdout,  # 输出到标准输出(控制台)
)

在这个配置中:

  • level=logging.INFO:设置日志级别为INFO,这意味着DEBUG级别的日志将不会被记录。你可以根据需要调整为DEBUG、WARNING、ERROR等其他级别。
  • format='%(asctime)s - %(levelname)s - %(message)s':定义日志的输出格式,包含时间戳、日志级别和消息内容。%(asctime)s表示时间戳,%(levelname)s表示日志级别,%(message)s表示消息正文。
  • datefmt='%Y-%m-%d %H:%M:%S':指定时间戳的显示格式。
  • stream=sys.stdout:指定日志输出到标准输出(即控制台)。若要改为输出到标准错误(stderr),可以使用stream=sys.stderr。

指定了一个基本的日志格式,所有未显式配置的logger都将继承这些设置。

2、创建日志记录器实例

尽管basicConfig()已经设置了全局的日志行为,但对于更复杂的项目,通常建议创建特定的logger实例。这样可以为不同的模块或组件赋予独立的日志记录器,便于管理和区分日志来源。创建logger实例如下:

logger = logging.getLogger(__name__)

这里使用__name__作为logger的名称,它会自动填充为当前模块的名称,有助于在多模块环境中识别日志来源。就相当于给指定模块或者类加上标签,如果你的程序很大,可能希望不同的模块或类各自有日志记录,这样查起来更方便。

import logging

module_logger = logging.getLogger('MyModule')
module_logger.info('这是MyModule模块的日记')

这样,日记前面就有标签了,一看就知道是哪个部分写的。下面是一个简单的例子,展示如何为不同模块或类创建带标签的logger实例,并记录日志:

project/
│
├── main.py
├── module_a.py
└── module_b.py

#主程序结构示例

main.py (主程序):

import logging
import module_a
import module_b

# 基本日志配置
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S',
    stream=sys.stdout,
)

# 调用模块A和模块B的功能
module_a.do_something()
module_b.do_something_else()

module_a.py (模块A):

import logging

# 创建模块A的日志记录器,标签为'module_a'
module_a_logger = logging.getLogger('module_a')

def do_something():
    module_a_logger.info('模块A开始执行操作...')
    # ... 执行具体操作 ...
    module_a_logger.info('模块A操作完成')

module_b.py (模块B):

import logging

# 创建模块B的日志记录器,标签为'module_b'
module_b_logger = logging.getLogger('module_b')

def do_something_else():
    module_b_logger.info('模块B开始执行操作...')
    # ... 执行具体操作 ...
    module_b_logger.info('模块B操作完成')

在这个例子中:

  • main.py 是主程序,导入了模块module_a和module_b,并进行了基本的日志配置。配置中使用%(name)s来显示日志记录器的名称(即标签)。
  • module_a.py 和 module_b.py 分别代表两个不同的模块。在每个模块中,我们创建了一个带有特定标签的logger实例(如module_a_logger和module_b_logger),标签对应模块的名称。
  • 模块中的函数(如do_something()和do_something_else())在执行关键操作前后,使用各自的logger实例记录日志信息,标签确保了日志消息能够明确标识出它们来自哪个模块。

当你运行main.py时,控制台会输出类似这样的日志:

2024-08-9 10:00:00 - module_a - INFO - 模块A开始执行操作...
2024-08-9 10:00:01 - module_a - INFO - 模块A操作完成
2024-08-9 10:00:02 - module_b - INFO - 模块B开始执行操作...
2024-08-9 10:00:03 - module_b - INFO - 模块B操作完成

每个日志消息都带有标签(如module_amodule_b),清晰地表明了它们分别来自哪个模块。这种做法有助于你在查看日志时快速定位问题所在,特别是在大型项目中,具有显著的便利性。

3、记录日志信息

有了配置好的logger实例,就可以在代码中使用不同级别的方法来记录日志了。例如:

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.')

这些方法对应于之前设置的日志级别。只有级别等于或高于已设置日志级别的消息才会被实际记录和输出。

4、查看日志输出

运行程序,你会在控制台看到类似这样的日志输出:

2024-03-22 15: - This is an info message.
2024-03-22 15:22:34 - WARNING - This is a warning message.
2024-03-22 15:22:34 - ERROR - This is an error message.
2024-03-22 15:22:34 - CRITICAL - This is a critical message.

5、保存日志到文件

如果希望将日志保存到文件而不是仅输出到控制台,可以使用FileHandlerstream=sys.stdout。修改basicConfig()如下:

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S',
    filename='app.log',  # 将日志保存到文件'app.log'
)

现在,日志信息将被写入到指定的app.log文件中,而不是控制台。

四、进阶

随着对logging模块的深入使用,可以进一步学习和应用更复杂的日志处理技术,如自定义日志格式、使用不同的处理器、设置日志记录器层次结构、实现日志文件轮转等等。

1、自定义日志格式

自定义日志格式是指在使用Python的logging模块时,根据实际需求更改日志消息的输出样式。这可以通过创建一个logging.Formatter对象并设置其格式字符串来实现。格式字符串使用特殊的占位符(如%(asctime)s%(levelname)s等)来插入日志消息中的不同属性。以下是如何自定义日志格式的实例:

import logging

# 创建自定义格式器
formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(name)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S')

# 创建Handler,将Formatter与之关联
console_handler = logging.StreamHandler()  # 输出到控制台
console_handler.setFormatter(formatter)

# 获取或创建Logger
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)  # 设置日志级别

# 将Handler添加到Logger
logger.addHandler(console_handler)

# 记录日志
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.')

运行这段代码,你将在控制台看到按照自定义格式输出的日志消息。同样,如果使用了FileHandler,日志消息将以同样的格式写入指定的文件。通过这种方式,你可以根据项目需求灵活调整日志的输出样式,使其更加符合团队约定或易于后期分析。

logging.Formatter传入自定义的格式字符串,格式字符串中可以包含以下常用的占位符:

  • %(asctime)s: 时间戳。默认格式类似2003-07-08 16:49:45,896,可以通过datefmt参数进一步自定义。
  • %(levelname)s: 日志级别名称,如DEBUGINFOWARNINGERRORCRITICAL
  • %(name)s: 记录器名称(即logger实例的名称)。
  • %(message)s: 日志消息正文。
  • %(module)s: 发出日志消息的模块名。
  • %(funcName)s: 发出日志消息的函数名。
  • %(lineno)d: 发出日志消息的源代码行号。
  • %(thread)d 或 %(threadName)s: 当前线程ID或名称。
  • %(process)d 或 %(processName)s: 当前进程ID或名称。
  • 其他自定义字段(如果在日志记录时添加了额外的元数据)。

2、设置日志记录器层次结构

logging模块中的日志记录器(logger)支持层次结构的概念,这意味着记录器可以组织成一个树状结构,通过名称的点分隔表示父子关系。这种层次结构使得日志管理更为灵活,允许在不同粒度上设置日志级别、过滤规则等配置。以下是设置日志记录器层次结构的步骤和示例:

import logging

# 创建处理器,输出到控制台
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)  # 控制台只输出INFO及以上级别

# 设置根记录器级别为INFO
root_logger = logging.getLogger()
root_logger.setLevel(logging.INFO)
root_logger.addHandler(console_handler)

# 模块A记录器,级别为DEBUG
module_a_logger = logging.getLogger('module_a')
module_a_logger.setLevel(logging.DEBUG)  # 模块A记录更详细的日志

# 模块A内部子模块记录器,继承模块A的级别
module_a_submodule_logger = logging.getLogger('module_a.submodule')

# 使用记录器记录日志
module_a_logger.debug('Debug message from module A')
module_a_submodule_logger.info('Info message from submodule of module A')

运行这段代码,控制台将只输出module_a.submoduleinfo消息,因为这是唯一高于处理器级别(INFO)的日志。module_adebug消息虽然其记录器级别允许,但由于处理器级别限制,不会被输出。

通过这样的层次结构设置,可以方便地在不同层级上管理日志记录,如全局设置根记录器的级别以控制整体日志输出量,然后在特定模块或子系统中细化日志级别以获取更多调试信息。这种结构也使得日志过滤、路由到不同输出目的地等操作更加灵活和高效。

3、使用不同的处理器

使用不同的处理器(Handler)可以让Python的logging模块将日志消息输出到多种不同的目的地,如控制台、文件、电子邮件、数据库等。每个处理器负责一种特定的输出方式,并且可以独立配置其日志级别、输出格式和过滤条件。以下是使用不同处理器的步骤和示例:

import logging
from logging import handlers

# 创建处理器
console_handler = logging.StreamHandler()
file_handler = logging.FileHandler('application.log')

# 设置处理器级别
console_handler.setLevel(logging.INFO)
file_handler.setLevel(logging.DEBUG)

# 创建并设置格式器
formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(name)s: %(message)s')
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)

# 获取或创建记录器
logger = logging.getLogger('my_application')

# 添加处理器
logger.addHandler(console_handler)
logger.addHandler(file_handler)

# 使用记录器记录日志
logger.debug('Debug message')
logger.info('Info message')
logger.warning('Warning message')
logger.error('Error message')
logger.critical('Critical message')

运行这段代码,INFO及以上级别的日志消息将输出到控制台,而所有级别的日志消息(包括DEBUG)都将写入到application.log文件中。通过使用不同的处理器,可以将日志消息路由到多种目的地,以满足不同的监控、分析和归档需求。

常见的处理器类型包括:

  • logging.StreamHandler: 输出到标准输出(sys.stdout)或标准错误(sys.stderr)。
  • logging.FileHandler: 将日志写入到指定的文件。
  • logging.handlers.RotatingFileHandler: 当文件达到一定大小时,自动创建新的日志文件,并可选择保留旧文件。
  • logging.handlers.TimedRotatingFileHandler: 按照固定时间间隔(如每天、每周)自动创建新的日志文件。
  • logging.handlers.SMTPHandler: 发送日志邮件。
  • logging.handlers.HTTPHandler: 将日志发送到HTTP服务器。

4、实现日志文件轮转

日志文件轮转(log file rotation)是一种常见的日志管理策略,当日志文件达到一定大小或达到特定时间点时,自动创建新的日志文件,同时可以保留旧日志文件以供后续查阅或归档。在Python的logging模块中,可以使用logging.handlers.RotatingFileHandlerlogging.handlers.TimedRotatingFileHandler来实现日志文件轮转。以下是两种方式的实现示例:

1)、使用 RotatingFileHandler 实现按文件大小轮转

RotatingFileHandler会在日志文件达到指定大小时自动创建新的日志文件。你可以设置最大文件大小、备份文件数量以及备份文件名的模式:

import logging
from logging.handlers import RotatingFileHandler

# 创建RotatingFileHandler实例
rotating_handler = RotatingFileHandler('app.log', maxBytes=10_000_000, backupCount=5)

# 设置处理器级别、格式器等
rotating_handler.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(name)s: %(message)s')
rotating_handler.setFormatter(formatter)

# 获取或创建记录器
logger = logging.getLogger('my_app')

# 添加处理器
logger.addHandler(rotating_handler)

# 使用记录器记录日志
logger.debug('Debug message')
logger.info('Info message')
logger.warning('Warning message')
logger.error('Error message')
logger.critical('Critical message')

在这个例子中:

  • RotatingFileHandler('app.log', maxBytes=10_000_000, backupCount=5) 创建了一个RotatingFileHandler,指定主日志文件名为app.log,当文件大小达到10MB时触发轮转,最多保留5个备份文件。
  • 当日志文件达到最大大小时,RotatingFileHandler会自动创建一个新文件(如app.log.1),并将当前app.log重命名为app.log.2(如果存在app.log.1,则依次向后重命名)。依此类推,直至达到备份文件数量上限,超出的最旧文件将被删除。

2)、使用 TimedRotatingFileHandler 实现按时间间隔轮转

TimedRotatingFileHandler会在指定的时间间隔到达时自动创建新的日志文件。你可以设置轮转周期(如按小时、天、周、月等),以及备份文件的模式:

import logging
from logging.handlers import TimedRotatingFileHandler

# 创建TimedRotatingFileHandler实例
timed_rotating_handler = TimedRotatingFileHandler('app.log', when='midnight', backupCount=7)

# 设置处理器级别、格式器等
timed_rotating_handler.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(name)s: %(message)s')
timed_rotating_handler.setFormatter(formatter)

# 获取或创建记录器
logger = logging.getLogger('my_app')

# 添加处理器
logger.addHandler(timed_rotating_handler)

# 使用记录器记录日志
logger.debug('Debug message')
logger.info('Info message')
logger.warning('Warning message')
logger.error('Error message')
logger.critical('Critical message')

在这个例子中:

  • TimedRotatingFileHandler('app.log', when='midnight', backupCount=7) 创建了一个TimedRotatingFileHandler,指定主日志文件名为app.log,每天午夜(midnight)触发轮转,最多保留7个备份文件。
  • 当轮转时间点到达时,TimedRotatingFileHandler会自动创建一个新文件(如app.log.2024-0¾-10),并将当前app.log重命名为备份文件。依此类推,直至达到备份文件数量上限,超出的最旧文件将被删除。

通过使用RotatingFileHandler或TimedRotatingFileHandler,可以根据实际需求轻松实现日志文件的轮转,避免单个日志文件过大,同时保留历史日志以供查阅或归档。

希望该篇文章能有效帮助大家理解应用logging模块~

  • 33
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Python 中,可以使用 `logging` 模块记录日志。如果需要在多线程环境下记录日志,可以使用 `concurrent_log_handler` 模块来实现。 首先需要安装 `concurrent_log_handler` 模块: ``` pip install concurrent_log_handler ``` 然后,可以使用以下代码来记录日志: ```python import logging from concurrent_log_handler import ConcurrentRotatingFileHandler import threading # 创建日志记录器 logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) # 创建文件处理器 handler = ConcurrentRotatingFileHandler(filename='example.log', mode='a', maxBytes=1024, backupCount=3) handler.setLevel(logging.DEBUG) formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') handler.setFormatter(formatter) # 将处理器添加到记录器 logger.addHandler(handler) # 多线程记录日志 def worker(): logger.debug('Debug message from thread {0}'.format(threading.current_thread().name)) logger.info('Info message from thread {0}'.format(threading.current_thread().name)) logger.warning('Warning message from thread {0}'.format(threading.current_thread().name)) logger.error('Error message from thread {0}'.format(threading.current_thread().name)) logger.critical('Critical message from thread {0}'.format(threading.current_thread().name)) threads = [] for i in range(10): t = threading.Thread(target=worker) threads.append(t) t.start() for t in threads: t.join() ``` 上面的代码中,创建了一个 `ConcurrentRotatingFileHandler` 类实例化的文件处理器,它可以在多线程环境下安全地写入日志文件。然后,将处理器添加到日志记录器中,并使用多线程来记录日志
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值