Python 的 logging 库是提供了日志记录的功能,本文将介绍 Python logging 库的基础知识。

# 基本概念

# 日志级别

Python logging 库定义了几个标准的日志级别,用于表示日志消息的重要性。这些级别按从低到高的顺序分别是:

  • DEBUG :最详细的信息,主要用于调试。
  • INFO :用于确认事情按预期工作。
  • WARNING :表示有一些意外情况,或者某些不常见的情况。
  • ERROR :表示更严重的问题,但应用程序仍能继续运行。
  • CRITICAL :表示严重错误,可能导致应用程序终止。

# 模块化组件

# Logger

Logger 类是 logging 库的核心组件之一,用于创建和管理日志记录器。每个日志记录器都有一个名称,这个名称通常对应于模块名或者与应用程序的不同部分相关的标识符。通过使用 getLogger 方法,可以获取或创建一个具有特定名称的日志记录器。

import logging
logger = logging.getLogger ("my_logger")

# Handler

Handler 对象负责将日志消息发送到指定的目的地。例如,可以使用 StreamHandler 将日志消息输出到标准输出,或者使用 FileHandler 将日志记录到文件中。

stream_handler = logging.StreamHandler ()
file_handler = logging.FileHandler ("app.log")

# Formatter

Formatter 对象用于定义日志消息的输出格式。通过将格式器分配给处理程序,可以自定义日志消息的显示方式。

formatter = logging.Formatter ('%(asctime) s - %(levelname) s - %(message) s')
stream_handler.setFormatter (formatter)
file_handler.setFormatter (formatter)

# 层级继承机制

Logger 有一个层级结构,可以继承上级 Logger 的属性。这有助于在不同模块或子系统中组织日志记录。

# 配置 Logging

# 基本配置

对于一些简单的脚本、小型项目或者快速开发阶段,我们并不需要复杂的日志配置或者想尽快看到日志数据,就可以使用 logging.basicConfig 进行简单日志配置。

logging.basicConfiglogging 模块中一个非常方便的函数,用于进行快速且简单的日志配置。它允许你一次性设置日志级别、格式、输出目标等,而不需要显式创建 Logger、HandlerFormatter 对象。

import logging
logging.basicConfig (filename='app.log', encoding='utf-8', filemode='w', level=logging.DEBUG, format=format='%(asctime) s - %(name) s - %(levelname) s - %(message) s')
logger = logging.getLogger (__name__)	# 创建模块级日志器,用来表示报错来自于哪个模块,对应于 format 中的 %(name) s
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')

logging.basicConfig 的几个常用参数: levelformatfilename

# level

level=logging.WARNING : 设置日志的级别为 WARNING。

logging 模块定义了五个标准日志级别,按严重性递增排序为: DEBUG; INFO; WARNING; ERROR; CRITICAL 。这些级别帮助开发者区分日志的重要性,并在不同环境下灵活控制输出内容。

设定一个级别后,仅记录等于或高于该级别的日志。例如设为 INFO 时,DEBUG 日志会被过滤,但 INFO 及以上会保留。

# format

format="%(asctime) s - %(name) s - %(levelname) s - %(message) s" 设置日志的格式。这里定义的格式包含以下部分。

  • %(asctime) s:日志事件发生的时间。
  • %(name) s:Logger 的名称。
  • %(levelname) s:日志级别名称(如 WARNING、ERROR 等)。
  • %(message) s:日志消息内容。

# filename

filename='app.log', encoding='utf-8', filemode='w' 指定以写模式打开 app.log ,编码为 'utf-8' ,将日志输出到文件 app.log

如果不指定 filename,日志将输出到标准输出(通常是控制台)。

# 进阶配置

对于复杂的应用程序,使用配置文件来配置 logging 更为方便。可以通过 fileConfig 函数加载配置文件,其中配置文件采用 INI 格式。

import logging.config
logging.config.fileConfig ('logging.conf')
[loggers]
keys=root,sampleLogger
[handlers]
keys=consoleHandler
[formatters]
keys=sampleFormatter
[logger_root]
level=DEBUG
handlers=consoleHandler
[logger_sampleLogger]
level=DEBUG
handlers=consoleHandler
qualname=sampleLogger
propagate=0
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=sampleFormatter
args=(sys.stdout,)
[formatter_sampleFormatter]
format=%(asctime) s - %(name) s - %(levelname) s - %(message) s
datefmt=% Y-% m-% d % H:% M:% S

# 使用示例

下面我们将展示一些更具体的示例,以便更全面地了解 logging 库的功能。

# 多模块调用

设置一个基础的日志模块,所有其他模块统一调用基础模块记录日志

# -*- coding: utf-8 -*-
# logger_base.py
# 基础日志模块,提供日志配置和获取 logger 实例的功能
import logging
import os
# 配置基础日志模块
def setup_logging ():
    logger = logging.getLogger ()
    logger.setLevel (logging.DEBUG)
    
    # 控制台处理器
    console_handler = logging.StreamHandler ()
    console_handler.setLevel (logging.INFO)
    
    # 文件处理器
    log_file = os.path.join (os.path.dirname (__file__), 'app.log')
    file_handler = logging.FileHandler (log_file)
    file_handler.setLevel (logging.DEBUG)
    
    # 格式器
    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 实例的函数
def get_logger (name):
    return logging.getLogger (name)
# 初始化日志配置(在模块导入时执行)
setup_logging ()

在其余模块中调用:

import logger_base
if __name__ == "__main__":
    logger = logger_base.get_logger (__name__)
    logger.info ("This is an info message")
    logger.debug ("This is a debug message")
    logger.error ("This is an error message")

# 在 Qt 中使用

我们希望将日志信息输出到一个 QTextEdit 中,首先调整基础日志模块,去除掉终端输出并新增 UI 输出

# -*- coding: utf-8 -*-
# logger_base.py
# 自定义日志处理器,将日志输出到 PySide6 的 QTextEdit 控件
import logging
import os
class UIHandler (logging.Handler):
    def __init__(self, output_widget):
        super ().__init__()
        self.output_widget = output_widget
    
    def emit (self, record):
        msg = self.format (record)
        # 调用 output_widget(QTextEdit) 的 append 方法
        self.output_widget.append (msg)
# 配置文件日志模块
def setup_logging ():
    logger = logging.getLogger ()
    logger.setLevel (logging.DEBUG)
    log_file = os.path.join (os.path.dirname (__file__), 'app.log')
    file_handler = logging.FileHandler (log_file)
    file_handler.setLevel (logging.DEBUG)
    formatter = logging.Formatter ('%(asctime) s - %(name) s - %(levelname) s - %(message) s')
    file_handler.setFormatter (formatter)
    logger.addHandler (file_handler)
# 配置 UI 日志模块,将日志输出到指定的 QTextEdit 控件
def set_ui_handler (output_widget):
    logger = logging.getLogger ()
    ui_handler = UIHandler (output_widget)
    ui_handler.setLevel (logging.INFO)  # 可根据需要调整级别
    ui_handler.setFormatter (logging.Formatter ('%(asctime) s - %(name) s - %(levelname) s - %(message) s'))
    logger.addHandler (ui_handler)
# 获取 logger 实例的函数
def get_logger (name):
    return logging.getLogger (name)
# 初始化日志配置(在模块导入时执行)
setup_logging ()

随后,在 Qt 中,只需要使用 self.logger.info (message) 即可让日志信息输出到 UI 中,示例如下

from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget, QLineEdit, QTextEdit
import logger_base
class MainWindow (QMainWindow):
    def __init__(self):
        super ().__init__()
        self.setWindowTitle ("Simple PySide6 App")
        # 设置初始大小
        self.resize (600, 600)
        layout = QVBoxLayout ()
        self.button = QPushButton ("Click Me")
        self.text_input = QLineEdit ()
        self.text_input.setPlaceholderText ("Enter some text here...")
        self.output_box = QTextEdit ()
        self.output_box.setReadOnly (True)
        
        layout.addWidget (self.button)
        layout.addWidget (self.text_input)
        layout.addWidget (self.output_box)
        container = QWidget ()
        container.setLayout (layout)
        self.setCentralWidget (container)
        self.bind ()
        # 获取 logger 实例
        self.logger = logger_base.get_logger (__name__)
        # 设置 UI handler
        logger_base.set_ui_handler (self.output_box)
    
    def bind (self):
        self.button.clicked.connect (self.on_button_click)
        self.text_input.returnPressed.connect (self.on_text_input)
    def on_button_click (self):
        message = "Button clicked!"
        self.logger.info (message)
    
    def on_text_input (self):
        text = self.text_input.text ()
        message = f"You entered: {text}"
        self.logger.info (message)
        self.text_input.clear ()
if __name__ == "__main__":
    app = QApplication ([])
    window = MainWindow ()
    window.show ()
    app.exec ()

# 自定义处理程序

下面是一个简单的使用 Python logging 库的示例:

import logging
# 配置日志记录器
logging.basicConfig (filename='app.log', level=logging.DEBUG, format='%(asctime) s - %(levelname) s - %(message) s')
# 创建一个日志记录器
logger = logging.getLogger ("my_logger")
# 创建一个处理程序,并将其关联到日志记录器
stream_handler = logging.StreamHandler ()
logger.addHandler (stream_handler)
# 创建一个格式器,并将其关联到处理程序
formatter = logging.Formatter ('%(asctime) s - %(levelname) s - %(message) s')
stream_handler.setFormatter (formatter)
# 记录不同级别的日志消息
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.")

除了使用内置的处理程序外,我们可以自定义处理程序来满足特定需求。例如,我们可以创建一个将日志消息发送到邮件的处理程序:

import logging
import smtplib
from email.mime.text import MIMEText
class EmailHandler (logging.Handler):
    def __init__(self, mailhost, from_addr, to_addr, subject):
        super ().__init__()
        self.mailhost = mailhost
        self.from_addr = from_addr
        self.to_addr = to_addr
        self.subject = subject
    def emit (self, record):
        msg = MIMEText (self.format (record))
        msg ['Subject'] = self.subject
        msg ['From'] = self.from_addr
        msg ['To'] = self.to_addr
        with smtplib.SMTP (self.mailhost) as server:
            server.sendmail (self.from_addr, [self.to_addr], msg.as_string ())
# 配置邮件处理程序
mail_handler = EmailHandler (mailhost='smtp.example.com',
                            from_addr='sender@example.com',
                            to_addr='recipient@example.com',
                            subject='Error in the application')
mail_handler.setLevel (logging.ERROR)
# 将邮件处理程序添加到日志记录器
logger.addHandler (mail_handler)
# 记录一条错误消息
logger.error ("This is an error message sent via email.")

# 过滤器

过滤器允许在消息到达处理程序之前进行进一步的控制。例如,我们可以使用过滤器仅记录特定模块的消息:

class ModuleFilter (logging.Filter):
    def __init__(self, module_name):
        super ().__init__()
        self.module_name = module_name
    def filter (self, record):
        return record.name.startswith (self.module_name)
# 创建一个过滤器实例
module_filter = ModuleFilter (module_name='my_module')
# 将过滤器添加到日志记录器
logger.addFilter (module_filter)
# 记录一条消息,但只有当消息来自 'my_module' 时才会被处理
logger.info ("This message is from my_module.")
logger.info ("This message is from another_module.")

# 使用装饰器记录函数调用

通过使用装饰器,记录函数的调用和执行时间:

import logging
import time
def log_function_call (func):
    def wrapper (*args, **kwargs):
        start_time = time.time ()
        result = func (*args, **kwargs)
        end_time = time.time ()
        elapsed_time = end_time - start_time
        logging.info (f"{func.__name__} executed in {elapsed_time:.2f} seconds.")
        return result
    return wrapper
logging.basicConfig (level=logging.DEBUG, format='%(asctime) s - %(levelname) s - %(message) s')
# 应用装饰器来记录函数调用
@log_function_call
def example_function ():
    # 一些需要记录执行时间的代码
    time.sleep (2)
# 调用带有装饰器的函数
example_function ()
# 输出示例
>>> 2025-09-21 20:09:18,570 - INFO - example_function executed in 2.00 seconds.