一文全面掌握Python logging库

目录

  • 1. 前言
  • 1.1 为什么需要日志
  • 1.2 Python `logging` 库简介
  • 2. `logging` 库的基本概念
  • 2.1 Logger、Handler、Formatter 和 Filter
  • 2.2 日志级别
  • 3. `logging` 库的基本用法
  • 3.1 配置日志
  • 3.2 使用基本配置
  • 3.3 使用 Logger 对象
  • 4. 高级用法
  • 4.1 自定义 Handler 和 Formatter
  • 4.2 日志的分级管理
  • 4.3 多模块日志管理
  • 5. 日志实践案例
  • 5.1 简单日志记录示例
  • 5.2 日志文件分割与轮转
  • 5.3 远程日志收集
  • Ref
  • 1. 前言

    在软件开发过程中,日志记录是不可或缺的一部分。它不仅帮助开发者调试和监控应用程序的运行状态,还在问题排查和性能优化中扮演着重要角色。Python 提供了内置的 logging 库,为开发者提供了灵活且强大的日志记录功能。本文将深入探讨 Python 的 logging 库,从基础概念到高级用法,结合实际案例,帮助读者全面掌握日志记录的技巧与最佳实践。

    1.1 为什么需要日志

    日志是记录应用程序运行过程中的事件和状态信息的工具。通过日志,开发者可以:

    1. 调试:快速定位代码中的错误和异常。
    2. 监控:实时了解应用程序的运行状况和性能指标。
    3. 审计:记录关键操作和用户行为,满足安全和合规需求。
    4. 分析:收集和分析日志数据,优化系统性能和用户体验。

    尽管可以使用简单的 print 语句进行调试,但随着项目规模的扩大和复杂性的增加,logging 库提供了更为系统化和可扩展的解决方案。

    1.2 Python logging 库简介

    Python 的 logging 库是一个内置模块,旨在为开发者提供灵活的日志记录功能。它支持多种日志级别、日志格式化、日志分发(如输出到控制台、文件、远程服务器等)以及日志轮转等特性。通过配置和使用 logging 库,开发者可以轻松管理和记录应用程序的运行信息。

    2. logging 库的基本概念

    要充分利用 logging 库,需要理解其核心组件和工作机制。以下是 logging 库的几个关键概念:

    2.1 Logger、Handler、Formatter 和 Filter

  • Logger 是日志系统的入口点,用于生成日志消息。每个 Logger 都有一个名称,通常与应用程序的模块名称对应。Logger 根据设置的日志级别决定是否处理特定级别的日志消息。
  • Handler 负责将 Logger 生成的日志消息分发到不同的目标,如控制台、文件、网络等。一个 Logger 可以关联多个 Handler,实现多种输出方式。
  • Formatter 定义了日志消息的最终输出格式。通过 Formatter,可以自定义日志消息的结构,包括时间戳、日志级别、消息内容等。
  • Filter 用于进一步细化日志记录的控制,可以基于特定条件过滤掉不需要的日志消息。
  • 2.2 日志级别

    logging 库定义了以下几个日志级别,按严重程度从低到高排序:

  • DEBUG:详细的信息,通常用于诊断问题。
  • INFO:确认程序按预期运行的信息。
  • WARNING:提示存在潜在问题的信息。
  • ERROR:运行时错误,导致某些功能无法正常工作。
  • CRITICAL:严重错误,导致程序崩溃或无法继续运行。
  • 开发者可以根据需要设置合适的日志级别,以控制日志消息的输出。

    3. logging 库的基本用法

    掌握了基本概念后,接下来介绍 logging 库的实际使用方法,包括配置日志、记录日志以及常见的用法示例。

    3.1 配置日志

    logging 库提供了多种配置方式,包括代码内配置和配置文件配置。这里主要介绍代码内配置的方法。

    通过 logging.basicConfig() 方法,可以快速配置日志系统的基本参数,如日志级别、格式、输出位置等。

    import logging
    
    # 配置日志
    logging.basicConfig(
        level=logging.DEBUG,  # 设置日志级别
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',  # 设置日志格式
        datefmt='%Y-%m-%d %H:%M:%S',  # 设置时间格式
        handlers=[
            logging.StreamHandler(),  # 输出到控制台
            logging.FileHandler('app.log')  # 输出到文件
        ]
    )
    
    # 创建 Logger
    logger = logging.getLogger(__name__)
    
    # 记录日志
    logger.debug('这是一个调试信息')
    logger.info('这是一个普通信息')
    logger.warning('这是一个警告信息')
    logger.error('这是一个错误信息')
    logger.critical('这是一个严重错误信息')
    

    在上述示例中,通过 basicConfig 方法配置了日志级别、格式和处理器。日志消息将同时输出到控制台和 app.log 文件中。

    对于复杂的应用程序,可以使用配置文件(如 JSON 或 YAML)来管理日志配置。这种方式便于维护和修改日志设置,而无需更改代码。

    # logging_config.yaml
    version: 1
    disable_existing_loggers: False
    
    formatters:
      simple:
        format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
      detailed:
        format: '%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s'
    
    handlers:
      console:
        class: logging.StreamHandler
        level: DEBUG
        formatter: simple
        stream: ext://sys.stdout
    
      file:
        class: logging.FileHandler
        level: INFO
        formatter: detailed
        filename: app.log
        encoding: utf8
    
    loggers:
      my_module:
        level: DEBUG
        handlers: [console, file]
        propagate: no
    
    root:
      level: WARNING
      handlers: [console]
    

    加载配置文件的代码示例

    import logging
    import logging.config
    import yaml
    
    # 加载 YAML 配置文件
    with open('logging_config.yaml', 'r') as f:
        config = yaml.safe_load(f.read())
        logging.config.dictConfig(config)
    
    # 创建 Logger
    logger = logging.getLogger('my_module')
    
    # 记录日志
    logger.debug('这是一个调试信息')
    logger.info('这是一个普通信息')
    logger.warning('这是一个警告信息')
    logger.error('这是一个错误信息')
    logger.critical('这是一个严重错误信息')
    

    通过配置文件,可以更灵活地管理不同模块的日志设置,增强日志系统的可维护性。

    3.2 使用基本配置

    在实际开发中,常常需要快速记录日志信息。以下是一些常见的日志记录方法:

    import logging
    
    # 配置基本日志
    logging.basicConfig(level=logging.INFO)
    
    # 记录不同级别的日志
    logging.debug('调试信息:变量x的值为10')
    logging.info('信息:程序开始运行')
    logging.warning('警告:磁盘空间不足')
    logging.error('错误:无法连接到数据库')
    logging.critical('严重错误:系统崩溃')
    

    输出:

    INFO:root:信息:程序开始运行
    WARNING:root:警告:磁盘空间不足
    ERROR:root:错误:无法连接到数据库
    CRITICAL:root:严重错误:系统崩溃
    

    注意,默认情况下,basicConfig 的日志级别为 WARNING,因此只有 WARNING 及以上级别的日志会被输出。通过设置 level=logging.INFO,可以输出 INFO 及以上级别的日志。

    3.3 使用 Logger 对象

    对于大型项目,建议为不同的模块创建独立的 Logger 对象,以便更好地管理日志输出。

    import logging
    
    # 创建 Logger
    logger = logging.getLogger('my_module')
    
    # 设置 Logger 级别
    logger.setLevel(logging.DEBUG)
    
    # 创建 Handler
    console_handler = logging.StreamHandler()
    file_handler = logging.FileHandler('my_module.log')
    
    # 创建 Formatter
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    
    # 绑定 Formatter 到 Handler
    console_handler.setFormatter(formatter)
    file_handler.setFormatter(formatter)
    
    # 绑定 Handler 到 Logger
    logger.addHandler(console_handler)
    logger.addHandler(file_handler)
    
    # 记录日志
    logger.debug('这是一个调试信息')
    logger.info('这是一个普通信息')
    logger.warning('这是一个警告信息')
    logger.error('这是一个错误信息')
    logger.critical('这是一个严重错误信息')
    

    通过为不同模块创建独立的 Logger,可以实现更精细的日志管理,例如不同模块使用不同的日志级别或输出到不同的目标。

    4. 高级用法

    除了基础的日志记录功能,logging 库还提供了许多高级特性,帮助开发者更好地控制和优化日志系统。

    4.1 自定义 Handler 和 Formatter

    有时,内置的 Handler 无法满足特定需求,此时可以通过继承 logging.Handler 类来自定义 Handler

    import logging
    import socket
    
    class RemoteHandler(logging.Handler):
        def __init__(self, host, port):
            super().__init__()
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.sock.connect((host, port))
        
        def emit(self, record):
            try:
                msg = self.format(record)
                self.sock.sendall(msg.encode('utf-8'))
            except Exception:
                self.handleError(record)
    
    # 使用自定义 Handler
    logger = logging.getLogger('remote_logger')
    logger.setLevel(logging.ERROR)
    
    remote_handler = RemoteHandler('localhost', 9999)
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    remote_handler.setFormatter(formatter)
    
    logger.addHandler(remote_handler)
    
    # 记录日志
    logger.error('这是一个需要发送到远程服务器的错误信息')
    

    通过继承 logging.Formatter 类,可以自定义日志消息的格式化方式。

    import logging
    
    class ColorFormatter(logging.Formatter):
        COLOR_MAP = {
            'DEBUG': '\033[94m',    # 蓝色
            'INFO': '\033[92m',     # 绿色
            'WARNING': '\033[93m',  # 黄色
            'ERROR': '\033[91m',    # 红色
            'CRITICAL': '\033[95m', # 紫色
        }
        RESET = '\033[0m'
    
        def format(self, record):
            color = self.COLOR_MAP.get(record.levelname, self.RESET)
            message = super().format(record)
            return f"{color}{message}{self.RESET}"
    
    # 使用自定义 Formatter
    logger = logging.getLogger('color_logger')
    logger.setLevel(logging.DEBUG)
    
    console_handler = logging.StreamHandler()
    color_formatter = ColorFormatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    console_handler.setFormatter(color_formatter)
    
    logger.addHandler(console_handler)
    
    # 记录日志
    logger.debug('这是一个蓝色的调试信息')
    logger.info('这是一个绿色的普通信息')
    logger.warning('这是一个黄色的警告信息')
    logger.error('这是一个红色的错误信息')
    logger.critical('这是一个紫色的严重错误信息')
    

    通过自定义 Formatter,可以实现更丰富的日志输出效果,如添加颜色、高亮关键字等。

    4.2 日志的分级管理

    在大型项目中,不同模块可能需要不同的日志级别和输出方式。通过配置多个 LoggerHandler,可以实现日志的分级管理。

    不同模块不同日志级别:

    import logging
    
    # 配置根 Logger
    logging.basicConfig(level=logging.WARNING, format='%(asctime)s - %(levelname)s - %(message)s')
    
    # 创建模块 A 的 Logger
    logger_a = logging.getLogger('moduleA')
    logger_a.setLevel(logging.DEBUG)
    handler_a = logging.FileHandler('moduleA.log')
    formatter_a = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
    handler_a.setFormatter(formatter_a)
    logger_a.addHandler(handler_a)
    
    # 创建模块 B 的 Logger
    logger_b = logging.getLogger('moduleB')
    logger_b.setLevel(logging.ERROR)
    handler_b = logging.FileHandler('moduleB.log')
    formatter_b = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
    handler_b.setFormatter(formatter_b)
    logger_b.addHandler(handler_b)
    
    # 记录日志
    logger_a.debug('模块A的调试信息')
    logger_a.info('模块A的普通信息')
    logger_a.warning('模块A的警告信息')
    
    logger_b.error('模块B的错误信息')
    logger_b.critical('模块B的严重错误信息')
    

    在上述示例中,moduleA 的日志级别设置为 DEBUG,所有级别的日志都会记录到 moduleA.log 文件中;而 moduleB 的日志级别设置为 ERROR,只有 ERRORCRITICAL 级别的日志会记录到 moduleB.log 文件中。

    4.3 多模块日志管理

    在多模块项目中,合理管理日志非常重要。可以通过设置不同模块的 Logger 名称,确保日志信息的清晰和有序。

    import logging
    
    # 配置根 Logger
    logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    
    # 模块 A
    def module_a():
        logger = logging.getLogger('app.moduleA')
        logger.info('模块A开始执行')
        logger.debug('模块A的调试信息')
        logger.warning('模块A的警告信息')
    
    # 模块 B
    def module_b():
        logger = logging.getLogger('app.moduleB')
        logger.info('模块B开始执行')
        logger.error('模块B的错误信息')
    
    if __name__ == '__main__':
        module_a()
        module_b()
    

    通过为每个模块创建独立的 Logger,可以更好地追踪和管理不同模块的日志信息,有助于快速定位问题。

    5. 日志实践案例

    为了更好地理解和应用 logging 库,以下通过几个实际案例,展示日志记录在不同场景下的应用。

    5.1 简单日志记录示例

    以下是一个简单的日志记录示例,展示如何配置日志并记录不同级别的日志消息。

    import logging
    
    def main():
        # 配置日志
        logging.basicConfig(
            level=logging.DEBUG,
            format='%(asctime)s - %(levelname)s - %(message)s',
            datefmt='%Y-%m-%d %H:%M:%S',
            handlers=[
                logging.StreamHandler(),
                logging.FileHandler('simple.log', mode='w')
            ]
        )
    
        logger = logging.getLogger('simple_logger')
    
        # 记录日志
        logger.debug('这是一个调试信息')
        logger.info('这是一个普通信息')
        logger.warning('这是一个警告信息')
        logger.error('这是一个错误信息')
        logger.critical('这是一个严重错误信息')
    
    if __name__ == '__main__':
        main()
    

    输出:

    2024-04-27 12:00:00 - DEBUG - 这是一个调试信息
    2024-04-27 12:00:00 - INFO - 这是一个普通信息
    2024-04-27 12:00:00 - WARNING - 这是一个警告信息
    2024-04-27 12:00:00 - ERROR - 这是一个错误信息
    2024-04-27 12:00:00 - CRITICAL - 这是一个严重错误信息
    

    同时,simple.log 文件中也会记录相同的日志信息。

    5.2 日志文件分割与轮转

    在实际应用中,日志文件可能会随着时间的推移变得庞大,为了避免日志文件过大,可以使用日志轮转功能。logging 库提供了 RotatingFileHandlerTimedRotatingFileHandler 来实现日志轮转。

    RotatingFileHandler 根据文件大小进行轮转,当日志文件达到指定大小时,会自动生成新的日志文件。

    import logging
    from logging.handlers import RotatingFileHandler
    
    def main():
        # 创建 Logger
        logger = logging.getLogger('rotating_logger')
        logger.setLevel(logging.INFO)
    
        # 创建 RotatingFileHandler
        handler = RotatingFileHandler('rotating.log', maxBytes=1024, backupCount=3)
        formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
        handler.setFormatter(formatter)
    
        # 绑定 Handler 到 Logger
        logger.addHandler(handler)
    
        # 记录大量日志以触发轮转
        for i in range(100):
            logger.info(f'日志信息 {i}')
    
    if __name__ == '__main__':
        main()
    

    说明:

  • maxBytes:指定日志文件的最大大小(字节)。当日志文件达到此大小时,会进行轮转。
  • backupCount:指定保留的旧日志文件数量。超过数量的旧日志文件会被自动删除。
  • TimedRotatingFileHandler 根据时间间隔进行轮转,例如每天、每小时等。

    import logging
    from logging.handlers import TimedRotatingFileHandler
    
    def main():
        # 创建 Logger
        logger = logging.getLogger('timed_rotating_logger')
        logger.setLevel(logging.INFO)
    
        # 创建 TimedRotatingFileHandler
        handler = TimedRotatingFileHandler('timed_rotating.log', when='midnight', interval=1, backupCount=7)
        formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
        handler.setFormatter(formatter)
    
        # 绑定 Handler 到 Logger
        logger.addHandler(handler)
    
        # 记录日志
        logger.info('这是一个定时轮转的日志信息')
    
    if __name__ == '__main__':
        main()
    

    说明:

  • when:指定轮转的时间单位,如 midnight(每天午夜)、S(秒)、M(分钟)、H(小时)、D(天)等。
  • interval:轮转的时间间隔。例如,when='H'interval=2 表示每两小时轮转一次。
  • backupCount:指定保留的旧日志文件数量。
  • 通过日志轮转,可以有效管理日志文件,避免单个日志文件过大,占用过多磁盘空间。

    5.3 远程日志收集

    在分布式系统或需要集中管理日志的场景下,远程日志收集显得尤为重要。通过自定义 Handler,可以将日志发送到远程服务器,如日志聚合平台、数据库或云服务。

    使用 SocketHandler 发送日志到远程服务器:

    import logging
    import logging.handlers
    import socketserver
    
    # 远程日志服务器
    class LogRecordHandler(socketserver.StreamRequestHandler):
        def handle(self):
            while True:
                record = self.rfile.readline()
                if not record:
                    break
                record = record.strip().decode('utf-8')
                print(f"远程日志接收: {record}")
    
    def start_log_server(host='localhost', port=9999):
        server = socketserver.ThreadingTCPServer((host, port), LogRecordHandler)
        print(f"日志服务器启动,监听 {host}:{port}")
        server.serve_forever()
    
    # 客户端代码
    def client_logging():
        logger = logging.getLogger('remote_logger')
        logger.setLevel(logging.INFO)
    
        # 创建 SocketHandler
        socket_handler = logging.handlers.SocketHandler('localhost', 9999)
        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        socket_handler.setFormatter(formatter)
    
        # 绑定 Handler 到 Logger
        logger.addHandler(socket_handler)
    
        # 记录日志
        logger.info('这是发送到远程服务器的日志信息')
    
    if __name__ == '__main__':
        import threading
    
        # 启动日志服务器
        server_thread = threading.Thread(target=start_log_server, daemon=True)
        server_thread.start()
    
        # 客户端记录日志
        client_logging()
    

    说明:

    1. 日志服务器:运行 start_log_server 函数,启动一个简单的日志服务器,监听指定的主机和端口,接收并打印日志消息。
    2. 客户端:通过 SocketHandler 将日志消息发送到远程日志服务器。

    此示例展示了如何使用 SocketHandler 将日志发送到远程服务器。在实际应用中,可以将日志服务器替换为专业的日志聚合平台,如 ELK(Elasticsearch、Logstash、Kibana)栈、Graylog 或 Splunk 等。


    Ref

    [1] https://docs.python.org/zh-cn/3/library/logging.html
    [2] https://realpython.com/python-logging/

    作者:Iareges

    物联沃分享整理
    物联沃-IOTWORD物联网 » 一文全面掌握Python logging库

    发表回复