大家好!今天我们来聊聊 Python 中非常重要的一个模块——logging。这个模块帮助我们记录程序运行过程中的各种信息,对于调试和维护程序来说非常重要。

什么是 logging?

logging 是 Python 的标准库之一,它允许开发者记录程序运行时的状态信息。这些信息可以帮助我们更好地理解程序的行为,尤其是在处理错误或者进行性能优化的时候。

为什么使用 logging?

  • 调试:记录程序运行时的状态,方便调试。

  • 审计:跟踪程序执行的过程,便于审计。

  • 日志分析:收集运行时数据,便于后续分析。

  • 错误追踪:记录错误信息,方便追踪问题。

安装和导入

首先,确保你的 Python 环境中已经安装了 logging 模块。这是 Python 标准库的一部分,所以通常不需要单独安装。

import logging

接下来,让我们一起学习 logging 模块的六个主要配置选项。

1. 设置日志级别

日志级别用于定义记录信息的重要性。常见的级别有:

  • DEBUG:详细的信息,通常只在调试时开启。

  • INFO:确认一切按预期运行。

  • WARNING:异常情况发生,但不影响程序运行。

  • ERROR:由于更严重的问题,某些功能无法执行。

  • CRITICAL:严重错误,导致程序崩溃或无法继续执行。

你可以设置全局的日志级别,也可以为不同的日志记录器设置不同的级别。

示例:设置日志级别

import logging

# 设置日志级别为 INFO
logging.basicConfig(level=logging.INFO)

logging.debug('这是一条 debug 信息')  # 不会被打印
logging.info('这是一条 info 信息')    # 会被打印
logging.warning('这是一条 warning 信息')  # 会被打印
logging.error('这是一条 error 信息')     # 会被打印
logging.critical('这是一条 critical 信息')  # 会被打印

输出:

INFO:root:这是一条 info 信息
WARNING:root:这是一条 warning 信息
ERROR:root:这是一条 error 信息
CRITICAL:root:这是一条 critical 信息

2. 创建日志记录器

为了更好地组织日志,我们可以创建多个日志记录器(logger)。每个记录器可以有自己的配置,比如日志级别和格式。

示例:创建日志记录器

import logging

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

# 设置日志级别为 DEBUG
logger.setLevel(logging.DEBUG)

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

# 创建日志格式
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
console_handler.setFormatter(formatter)

# 将处理器添加到记录器
logger.addHandler(console_handler)

# 记录日志
logger.debug('这是一条 debug 信息')
logger.info('这是一条 info 信息')
logger.warning('这是一条 warning 信息')
logger.error('这是一条 error 信息')
logger.critical('这是一条 critical 信息')

输出:

2023-10-03 14:50:23,123 - app - DEBUG - 这是一条 debug 信息
2023-10-03 14:50:23,123 - app - INFO - 这是一条 info 信息
2023-10-03 14:50:23,123 - app - WARNING - 这是一条 warning 信息
2023-10-03 14:50:23,123 - app - ERROR - 这是一条 error 信息
2023-10-03 14:50:23,123 - app - CRITICAL - 这是一条 critical 信息

3. 配置日志格式

日志格式定义了日志消息的外观。你可以自定义日期格式、日志级别、消息内容等。

示例:配置日志格式

import logging

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

# 设置日志级别为 DEBUG
logger.setLevel(logging.DEBUG)

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

# 创建日志格式
formatter = logging.Formatter('%(asctime)s - [%(levelname)s] - %(message)s')
console_handler.setFormatter(formatter)

# 将处理器添加到记录器
logger.addHandler(console_handler)

# 记录日志
logger.debug('这是一条 debug 信息')
logger.info('这是一条 info 信息')
logger.warning('这是一条 warning 信息')
logger.error('这是一条 error 信息')
logger.critical('这是一条 critical 信息')

输出:

2023-10-03 14:50:23 - [DEBUG] - 这是一条 debug 信息
2023-10-03 14:50:23 - [INFO] - 这是一条 info 信息
2023-10-03 14:50:23 - [WARNING] - 这是一条 warning 信息
2023-10-03 14:50:23 - [ERROR] - 这是一条 error 信息
2023-10-03 14:50:23 - [CRITICAL] - 这是一条 critical 信息

4. 使用文件处理器

除了将日志输出到控制台,你还可以将日志保存到文件中,以便后续查看和分析。

示例:使用文件处理器

import logging

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

# 设置日志级别为 DEBUG
logger.setLevel(logging.DEBUG)

# 创建文件处理器
file_handler = logging.FileHandler('app.log')
file_handler.setLevel(logging.DEBUG)

# 创建日志格式
formatter = logging.Formatter('%(asctime)s - [%(levelname)s] - %(message)s')
file_handler.setFormatter(formatter)

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

# 记录日志
logger.debug('这是一条 debug 信息')
logger.info('这是一条 info 信息')
logger.warning('这是一条 warning 信息')
logger.error('这是一条 error 信息')
logger.critical('这是一条 critical 信息')

输出:app.log 文件内容如下:

2023-10-03 14:50:23 - [DEBUG] - 这是一条 debug 信息
2023-10-03 14:50:23 - [INFO] - 这是一条 info 信息
2023-10-03 14:50:23 - [WARNING] - 这是一条 warning 信息
2023-10-03 14:50:23 - [ERROR] - 这是一条 error 信息
2023-10-03 14:50:23 - [CRITICAL] - 这是一条 critical 信息

5. 配置日志文件轮转

当日志文件变得非常大时,你可以使用日志文件轮转功能。这样可以在达到一定条件时生成新的日志文件,旧的日志文件会被备份。

示例:配置日志文件轮转

import logging
from logging.handlers import RotatingFileHandler

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

# 设置日志级别为 DEBUG
logger.setLevel(logging.DEBUG)

# 创建文件处理器
file_handler = RotatingFileHandler('app.log', maxBytes=1024 * 1024, backupCount=5)
file_handler.setLevel(logging.DEBUG)

# 创建日志格式
formatter = logging.Formatter('%(asctime)s - [%(levelname)s] - %(message)s')
file_handler.setFormatter(formatter)

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

# 记录大量日志
for i in range(10000):
    logger.debug(f'这是第 {i} 条 debug 信息')
    logger.info(f'这是第 {i} 条 info 信息')
    logger.warning(f'这是第 {i} 条 warning 信息')
    logger.error(f'这是第 {i} 条 error 信息')
    logger.critical(f'这是第 {i} 条 critical 信息')

在这个示例中,当 app.log 文件大小超过 1MB 时,会生成新的日志文件,并保留最多 5 份旧的日志文件。

6. 配置日志文件按时间分割

另一种常见的日志管理方式是按时间分割日志文件。这样可以更方便地管理和查找特定时间段的日志。

示例:配置日志文件按时间分割

import logging
from logging.handlers import TimedRotatingFileHandler

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

# 设置日志级别为 DEBUG
logger.setLevel(logging.DEBUG)

# 创建文件处理器
file_handler = TimedRotatingFileHandler('app.log', when='midnight', interval=1, backupCount=7)
file_handler.setLevel(logging.DEBUG)

# 创建日志格式
formatter = logging.Formatter('%(asctime)s - [%(levelname)s] - %(message)s')
file_handler.setFormatter(formatter)

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

# 记录日志
logger.debug('这是一条 debug 信息')
logger.info('这是一条 info 信息')
logger.warning('这是一条 warning 信息')
logger.error('这是一条 error 信息')
logger.critical('这是一条 critical 信息')

实战案例分析

假设你正在开发一个 Web 应用程序,需要记录用户请求和系统状态。下面是一个更复杂的示例,展示了如何使用 logging 模块来记录这些信息,并进行更详细的配置。

示例:Web 应用程序日志记录

import logging
from logging.handlers import TimedRotatingFileHandler

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

# 设置日志级别为 INFO
logger.setLevel(logging.INFO)

# 创建文件处理器
file_handler = TimedRotatingFileHandler('web_app.log', when='midnight', interval=1, backupCount=7)
file_handler.setLevel(logging.INFO)

# 创建日志格式
formatter = logging.Formatter('%(asctime)s - [%(levelname)s] - [%(name)s] - %(message)s')
file_handler.setFormatter(formatter)

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

def handle_request(user_id, request_type, response_status):
    logger.info(f'用户 {user_id} 发起了 {request_type} 请求,响应状态码为 {response_status}')

# 模拟处理请求
handle_request(1, 'GET', 200)
handle_request(2, 'POST', 201)
handle_request(3, 'PUT', 400)
handle_request(4, 'DELETE', 404)

输出:web_app.log 文件内容如下:

2023-10-03 14:50:23 - [INFO] - [web_app] - 用户 1 发起了 GET 请求,响应状态码为 200
2023-10-03 14:50:23 - [INFO] - [web_app] - 用户 2 发起了 POST 请求,响应状态码为 201
2023-10-03 14:50:23 - [INFO] - [web_app] - 用户 3 发起了 PUT 请求,响应状态码为 400
2023-10-03 14:50:23 - [INFO] - [web_app] - 用户 4 发起了 DELETE 请求,响应状态码为 404

在这个示例中,我们不仅记录了用户的请求类型,还记录了响应状态码。这样可以更全面地了解系统的运行情况。

多处理器配置

在实际应用中,你可能需要同时将日志输出到控制台和文件。这样可以在开发阶段实时查看日志,同时将完整的日志记录保存到文件中以备后续分析。

示例:多处理器配置

import logging
from logging.handlers import TimedRotatingFileHandler

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

# 设置日志级别为 INFO
logger.setLevel(logging.INFO)

# 创建文件处理器
file_handler = TimedRotatingFileHandler('web_app.log', when='midnight', interval=1, backupCount=7)
file_handler.setLevel(logging.INFO)

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

# 创建日志格式
formatter = logging.Formatter('%(asctime)s - [%(levelname)s] - [%(name)s] - %(message)s')
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)

# 将处理器添加到记录器
logger.addHandler(file_handler)
logger.addHandler(console_handler)

def handle_request(user_id, request_type, response_status):
    logger.info(f'用户 {user_id} 发起了 {request_type} 请求,响应状态码为 {response_status}')

# 模拟处理请求
handle_request(1, 'GET', 200)
handle_request(2, 'POST', 201)
handle_request(3, 'PUT', 400)
handle_request(4, 'DELETE', 404)

输出:控制台输出如下:

2023-10-03 14:50:23 - [INFO] - [web_app] - 用户 1 发起了 GET 请求,响应状态码为 200
2023-10-03 14:50:23 - [INFO] - [web_app] - 用户 2 发起了 POST 请求,响应状态码为 201
2023-10-03 14:50:23 - [INFO] - [web_app] - 用户 3 发起了 PUT 请求,响应状态码为 400
2023-10-03 14:50:23 - [INFO] - [web_app] - 用户 4 发起了 DELETE 请求,响应状态码为 404

文件 web_app.log 输出如下:

2023-10-03 14:50:23 - [INFO] - [web_app] - 用户 1 发起了 GET 请求,响应状态码为 200
2023-10-03 14:50:23 - [INFO] - [web_app] - 用户 2 发起了 POST 请求,响应状态码为 201
2023-10-03 14:50:23 - [INFO] - [web_app] - 用户 3 发起了 PUT 请求,响应状态码为 400
2023-10-03 14:50:23 - [INFO] - [web_app] - 用户 4 发起了 DELETE 请求,响应状态码为 404

在这个示例中,我们同时使用了文件处理器和控制台处理器,确保日志既能实时显示在控制台上,又能完整保存在文件中。

自定义日志格式

除了基本的日志格式,你还可以根据需要自定义日志格式。例如,可以包含更多的信息,如用户的 IP 地址、请求 URL 等。

示例:自定义日志格式

import logging
from logging.handlers import TimedRotatingFileHandler

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

# 设置日志级别为 INFO
logger.setLevel(logging.INFO)

# 创建文件处理器
file_handler = TimedRotatingFileHandler('web_app.log', when='midnight', interval=1, backupCount=7)
file_handler.setLevel(logging.INFO)

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

# 创建自定义日志格式
formatter = logging.Formatter('%(asctime)s - [%(levelname)s] - [%(name)s] - [%(client_ip)s] - [%(request_url)s] - %(message)s')
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)

# 将处理器添加到记录器
logger.addHandler(file_handler)
logger.addHandler(console_handler)

def handle_request(user_id, request_type, response_status, client_ip, request_url):
    logger.info(f'用户 {user_id} 发起了 {request_type} 请求,客户端 IP 为 {client_ip},请求 URL 为 {request_url},响应状态码为 {response_status}')

# 模拟处理请求
handle_request(1, 'GET', 200, '192.168.1.1', '/api/v1/users')
handle_request(2, 'POST', 201, '192.168.1.2', '/api/v1/posts')
handle_request(3, 'PUT', 400, '192.168.1.3', '/api/v1/comments/1')
handle_request(4, 'DELETE', 404, '192.168.1.4', '/api/v1/comments/2')

输出:控制台输出如下:

2023-10-03 14:50:23 - [INFO] - [web_app] - [192.168.1.1] - [/api/v1/users] - 用户 1 发起了 GET 请求,客户端 IP 为 192.168.1.1,请求 URL 为 /api/v1/users,响应状态码为 200
2023-10-03 14:50:23 - [INFO] - [web_app] - [192.168.1.2] - [/api/v1/posts] - 用户 2 发起了 POST 请求,客户端 IP 为 192.168.1.2,请求 URL 为 /api/v1/posts,响应状态码为 201
2023-10-03 14:50:23 - [INFO] - [web_app] - [192.168.1.3] - [/api/v1/comments/1] - 用户 3 发起了 PUT 请求,客户端 IP 为 192.168.1.3,请求 URL 为 /api/v1/comments/1,响应状态码为 400
2023-10-03 14:50:23 - [INFO] - [web_app] - [192.168.1.4] - [/api/v1/comments/2] - 用户 4 发起了 DELETE 请求,客户端 IP 为 192.168.1.4,请求 URL 为 /api/v1/comments/2,响应状态码为 404

文件 web_app.log 输出如下:

2023-10-03 14:50:23 - [INFO] - [web_app] - [192.168.1.1] - [/api/v1/users] - 用户 1 发起了 GET 请求,客户端 IP 为 192.168.1.1,请求 URL 为 /api/v1/users,响应状态码为 200
2023-10-03 14:50:23 - [INFO] - [web_app] - [192.168.1.2] - [/api/v1/posts] - 用户 2 发起了 POST 请求,客户端 IP 为 192.168.1.2,请求 URL 为 /api/v1/posts,响应状态码为 201
2023-10-03 14:50:23 - [INFO] - [web_app] - [192.168.1.3] - [/api/v1/comments/1] - 用户 3 发起了 PUT 请求,客户端 IP 为 192.168.1.3,请求 URL 为 /api/v1/comments/1,响应状态码为 400
2023-10-03 14:50:23 - [INFO] - [web_app] - [192.168.1.4] - [/api/v1/comments/2] - 用户 4 发起了 DELETE 请求,客户端 IP 为 192.168.1.4,请求 URL 为 /api/v1/comments/2,响应状态码为 404

在这个示例中,我们增加了客户端 IP 和请求 URL 两个字段,使得日志信息更加丰富和有用。

高级技巧:使用上下文管理器

在一些情况下,你可能需要在特定的上下文中记录日志。例如,在某个函数内部记录日志时,可以使用上下文管理器来确保日志的正确性。

示例:使用上下文管理器

import logging
from contextlib import contextmanager

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

# 设置日志级别为 INFO
logger.setLevel(logging.INFO)

# 创建文件处理器
file_handler = TimedRotatingFileHandler('web_app.log', when='midnight', interval=1, backupCount=7)
file_handler.setLevel(logging.INFO)

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

# 创建日志格式
formatter = logging.Formatter('%(asctime)s - [%(levelname)s] - [%(name)s] - [%(client_ip)s] - [%(request_url)s] - %(message)s')
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)

# 将处理器添加到记录器
logger.addHandler(file_handler)
logger.addHandler(console_handler)

@contextmanager
def log_context(client_ip, request_url):
    try:
        yield
    finally:
        logger.info(f'客户端 IP 为 {client_ip},请求 URL 为 {request_url}')

def handle_request(user_id, request_type, response_status, client_ip, request_url):
    with log_context(client_ip, request_url):
        logger.info(f'用户 {user_id} 发起了 {request_type} 请求,响应状态码为 {response_status}')

# 模拟处理请求
handle_request(1, 'GET', 200, '192.168.1.1', '/api/v1/users')
handle_request(2, 'POST', 201, '192.168.1.2', '/api/v1/posts')
handle_request(3, 'PUT', 400, '192.168.1.3', '/api/v1/comments/1')
handle_request(4, 'DELETE', 404, '192.168.1.4', '/api/v1/comments/2')

输出:控制台输出如下:

2023-10-03 14:50:23 - [INFO] - [web_app] - [192.168.1.1] - [/api/v1/users] - 用户 1 发起了 GET 请求,响应状态码为 200
2023-10-03 14:50:23 - [INFO] - [web_app] - [192.168.1.1] - [/api/v1/users]
2023-10-03 14:50:23 - [INFO] - [web_app] - [192.168.1.2] - [/api/v1/posts] - 用户 2 发起了 POST 请求,响应状态码为 201
2023-10-03 14:50:23 - [INFO] - [web_app] - [192.168.1.2] - [/api/v1/posts]
2023-10-03 14:50:23 - [INFO] - [web_app] - [192.168.1.3] - [/api/v1/comments/1] - 用户 3 发起了 PUT 请求,响应状态码为 400
2023-10-03 14:50:23 - [INFO] - [web_app] - [192.168.1.3] - [/api/v1/comments/1]
2023-10-03 14:50:23 - [INFO] - [web_app] - [192.168.1.4] - [/api/v1/comments/2] - 用户 4 发起了 DELETE 请求,响应状态码为 404
2023-10-03 14:50:23 - [INFO] - [web_app] - [192.168.1.4] - [/api/v1/comments/2]

文件 web_app.log 输出如下:

2023-10-03 14:50:23 - [INFO] - [web_app] - [192.168.1.1] - [/api/v1/users] - 用户 1 发起了 GET 请求,响应状态码为 200
2023-10-03 14:50:23 - [INFO] - [web_app] - [192.168.1.1] - [/api/v1/users]
2023-10-03 14:50:23 - [INFO] - [web_app] - [192.168.1.2] - [/api/v1/posts] - 用户 2 发起了 POST 请求,响应状态码为 201
2023-10-03 14:50:23 - [INFO] - [web_app] - [192.168.1.2] - [/api/v1/posts]
2023-10-03 14:50:23 - [INFO] - [web_app] - [192.168.1.3] - [/api/v1/comments/1] - 用户 3 发起了 PUT 请求,响应状态码为 400
2023-10-03 14:50:23 - [INFO] - [web_app] - [192.168.1.3] - [/api/v1/comments/1]
2023-10-03 14:50:23 - [INFO] - [web_app] - [192.168.1.4] - [/api/v1/comments/2] - 用户 4 发起了 DELETE 请求,响应状态码为 404
2023-10-03 14:50:23 - [INFO] - [web_app] - [192.168.1.4] - [/api/v1/comments/2]

在这个示例中,我们使用了上下文管理器来确保每次处理请求时都会记录客户端 IP 和请求 URL。这样可以确保日志的一致性和准确性。