GUI组件截取log4j日志并输出到

本文介绍了一种将Log4j日志信息输出到GUI界面的方法,通过自定义Appender实现了日志信息在JLabel和JTextArea组件上的显示。

这几天在为自己写的一个消息发送工具加一个UI控制界面,之前的爬虫核心是以命令行启动的,所以日志信息由log4j直接输出的控制台,可是现在有了UI,就不能再将日志信息输出到控制台了,必须将日志信息以某种方式截取,输出到界面上。

从网上找了篇文章,直接拿过来发布了,欢迎指正。

废话不多说,看代码: 一起jquery,17jquery

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cn.ysh.studio.gui.log;

import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;
import java.io.Writer;
import org.apache.log4j.Logger;
import org.apache.log4j.Appender;
import org.apache.log4j.WriterAppender;

/**
 * 
 * 类描述:
 * 重置log4j的Appender的Writer
 * @version 1.0
 */
public abstract class LogAppender extends Thread {

    protected PipedReader reader;

    public LogAppender(String appenderName) throws IOException {
        Logger root = Logger.getRootLogger();
        // 获取子记录器的输出源 
        Appender appender = root.getAppender(appenderName);
        // 定义一个未连接的输入流管道
        reader = new PipedReader();
        // 定义一个已连接的输出流管理,并连接到reader
        Writer writer = new PipedWriter(reader);
        // 设置 appender 输出流
        ((WriterAppender) appender).setWriter(writer);
    }
}
  

这个类是一个基类,实际上是不能够直接使用的,由它的子类负责将来自控制台的日志信息输出到UI组件。

比如现在需要将日志信息截获,输出到一个JLabel组件,代码如下: 内容来自17jquery

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cn.ysh.studio.gui.log;

import java.io.IOException;
import java.util.Scanner;
import javax.swing.JLabel;

/**
 * 
 * 类描述:
 * 不间断地扫描输入流
 * 将扫描到的字符流显示在JLabel上
 * @version 1.0
 */
public class LabelLogAppender extends LogAppender {

    private JLabel label;

    /**
     * 默认的构造
     * @param label 记录器名称,该记录器输出的日志信息将被截取并输出到指定的JLabel组件
     * @throws IOException 
     */
    public LabelLogAppender(JLabel label) throws IOException {
        super("label");
        this.label = label;
    }

    @Override
    public void run() {
        // 不间断地扫描输入流
        Scanner scanner = new Scanner(reader);
        // 将扫描到的字符流显示在指定的JLabel上
        while (scanner.hasNextLine()) {
            try {
                //睡眠
                Thread.sleep(100);
                String line = scanner.nextLine();
                label.setText(line);
                line = null;
                 } catch (Exception ex) {
                //异常信息不作处理
            }
        }
    }
}
 17jquery.com 

实际上,将日志信息输出到JTextArea或其他多行文本组件更加常见,比如MyEclipse或NetBeans等IDE。那么一下面的代码就展示了如何将日志截获并输出到JTextArea组件,同时自动使垂直滚动条跟随。请看到代码:

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cn.ysh.studio.gui.log;

import java.io.IOException;
import java.util.Scanner;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

/**
 * 
 * 类描述:
 * 不间断地扫描输入流
 * 将扫描到的字符流显示在JTextArea上
 * @version 1.0
 */
public class TextAreaLogAppender extends LogAppender {

    private JTextArea textArea;
    private JScrollPane scroll;

    /**
     * 默认的构造
     * @param textArea 记录器名称,该记录器输出的日志信息将被截取并输出到指定的JTextArea组件
     * @param scroll JTextArea组件使用的滚动面板,因为在JTextArea中输出日志时,默认会使垂直滚动条自动向下滚动,若不需要此功能,此参数可省略
     * @throws IOException 
     */
    public TextAreaLogAppender(JTextArea textArea, JScrollPane scroll) throws IOException {
        super("textArea");
        this.textArea = textArea;
        this.scroll = scroll;
    }

    @Override
    public void run() {
        // 不间断地扫描输入流
        Scanner scanner = new Scanner(reader);
        // 将扫描到的字符流输出到指定的JTextArea组件
        while (scanner.hasNextLine()) {
            try {
                //睡眠
                Thread.sleep(100);
                String line = scanner.nextLine();
                textArea.append(line);
                textArea.append("
");
                line = null;
                //使垂直滚动条自动向下滚动
                scroll.getVerticalScrollBar().setValue(scroll.getVerticalScrollBar().getMaximum());
                            } catch (Exception ex) {
                //异常不做处理
            }
        }
    }
}
  

按照上述方式封转之后,他们的使用就非常简单了。在窗体组件绘制完成后,就可以启动他们了:17jquery.com

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cn.ysh.studio.gui.test;

import cn.ysh.studio.gui.log.LabelLogAppender;
import cn.ysh.studio.gui.log.TextAreaLogAppender;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 
 * 类描述:
 * 测试日志输出到UI组件
 * @version 1.0
 */
public class LogDemoJFrame extends JFrame {

    private JLabel logLabel;
    private JScrollPane logScrollPane;
    private JTextArea logTextArea;
    private final static Log log = LogFactory.getLog(LogDemoJFrame.class);

    public LogDemoJFrame() {
        logLabel = new javax.swing.JLabel();
        logScrollPane = new javax.swing.JScrollPane();
        logTextArea = new javax.swing.JTextArea();
        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        logLabel.setText(" ");

        logTextArea.setColumns(20);
        logTextArea.setRows(5);
        logScrollPane.setViewportView(logTextArea);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
                layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addGroup(layout.createSequentialGroup().addContainerGap().addComponent(logLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 610, Short.MAX_VALUE).addContainerGap()).addComponent(logScrollPane, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 630, Short.MAX_VALUE));
        layout.setVerticalGroup(
                layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addGroup(layout.createSequentialGroup().addContainerGap().addComponent(logLabel).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addComponent(logScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 305, Short.MAX_VALUE)));

        pack();
    }

    public void initLog() {
        try {
            Thread t1, t2;
            t1 = new LabelLogAppender(logLabel);
            t2 = new TextAreaLogAppender(logTextArea, logScrollPane);
            t1.start();
            t2.start();
        } catch (Exception e) {
            JOptionPane.showMessageDialog(this, e, "绑定日志输出组件错误", JOptionPane.ERROR_MESSAGE);
        }
    }

    public static void main(String[] s) {
        LogDemoJFrame logDemoFrame = new LogDemoJFrame();
        logDemoFrame.initLog();
        logDemoFrame.setVisible(true);
        for (int i = 0; i < 1000; i++) {
            log.info("测试日志输出:" + i);
        }
    }
}
  

很简单的一个Demo,仅供参考。 17jquery.com

下面是我的Log4j日志配置信息:

log4j.appender.label=org.apache.log4j.ConsoleAppender
log4j.appender.label.layout=org.apache.log4j.PatternLayout
log4j.appender.label.layout.ConversionPattern=%m%n

log4j.appender.textArea=org.apache.log4j.ConsoleAppender
log4j.appender.textArea.layout=org.apache.log4j.PatternLayout
log4j.appender.textArea.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss}  [ %p ]  %m%n

log4j.rootLogger=INFO,label,textArea  

其实鉴于上述方法,还可以实现更多自定义log4j日志输出功能,在此不再赘述。


<think> 在PyQt6中添加精确到毫秒的日志时间戳,可以通过自定义日志格式来实现。这里提供两种方法:一种是使用Python内置的`logging`模块,另一种是自定义日志类。两种方法都可以将时间戳精确到毫秒。 ### 方法1:使用Python的`logging`模块(推荐) 这是最标准的方法,利用`logging`模块的强大功能,特别是其内置的时间格式化能力。 ```python import logging from PyQt6.QtCore import QObject, pyqtSignal class QtLogHandler(QObject): """PyQt6兼容的日志处理器,支持毫秒级时间戳""" log_signal = pyqtSignal(str) # 用于跨线程日志传递的信号 def __init__(self): super().__init__() # 创建logger实例 self.logger = logging.getLogger('PyQt6App') self.logger.setLevel(logging.DEBUG) # 定义毫秒级时间戳格式 formatter = logging.Formatter( fmt='[%(asctime)s.%(msecs)03d][%(levelname)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) # 控制台处理器(示例) console_handler = logging.StreamHandler() console_handler.setFormatter(formatter) self.logger.addHandler(console_handler) # 文件处理器(可选,同样支持毫秒) # file_handler = logging.FileHandler('app.log') # file_handler.setFormatter(formatter) # self.logger.addHandler(file_handler) # 连接日志记录方法到信号 self.log_signal.connect(self.handle_log) def handle_log(self, record): """实际记录日志的方法(主线程执行)""" self.logger.handle(record) def log(self, level, message): """线程安全的日志记录入口""" # 创建日志记录对象 record = self.logger.makeRecord( name=self.logger.name, level=level, fn='', lno=0, msg=message, args=(), exc_info=None ) # 通过信号发送记录(确保在主线程处理) self.log_signal.emit(record) # 使用示例 if __name__ == "__main__": import sys from PyQt6.QtWidgets import QApplication app = QApplication(sys.argv) logger = QtLogHandler() logger.log(logging.INFO, "测试毫秒级时间戳日志") # 输出示例: [2023-08-15 15:30:45.123][INFO] 测试毫秒级时间戳日志 sys.exit(app.exec()) ``` **关键说明**: - `%(asctime)s.%(msecs)03d`:在标准时间戳后添加毫秒部分,格式化为3位数字(如123毫秒)[^1]。 - `datefmt`参数定义日期时间的显示格式,但不包括毫秒,因此需要在`fmt`中单独添加。 - 通过`pyqtSignal`确保日志记录操作在主线程执行,避免多线程问题。 ### 方法2:完全自定义时间戳生成 如果不想依赖`logging`模块,也可以手动生成毫秒时间戳: ```python import time from PyQt6.QtCore import QObject, pyqtSignal class TimestampLogger(QObject): log_signal = pyqtSignal(str) def __init__(self): super().__init__() self.log_signal.connect(print) # 默认连接print,实际可连接GUI组件 def log(self, level, message): # 获取当前时间(精确到毫秒) now = time.time() ms = int((now - int(now)) * 1000) timestamp = time.strftime("%Y-%m-%d %H:%M:%S") + f".{ms:03d}" # 格式化日志 log_entry = f"[{timestamp}][{level}] {message}" self.log_signal.emit(log_entry) # 使用示例 logger = TimestampLogger() logger.log("DEBUG", "手动生成毫秒时间戳") # 输出: [2023-08-15 15:45:30.789][DEBUG] 手动生成毫秒时间戳 ``` ### 两种方法对比 | 特性 | `logging`模块方法 | 自定义方法 | |--------------------|--------------------------------|------------------------| | 时间精度 | 毫秒级 | 毫秒级 | | 多线程安全性 | 通过信号机制保证 | 通过信号机制保证 | | 日志分级管理 | 内置(DEBUG/INFO/WARNING等) | 需手动实现 | | 输出目标扩展性 | 支持多处理器(文件/网络/等) | 只能通过信号单一输出 | | 格式化灵活性 | 中等(需遵循Formatter规则) | 完全自由 | | 推荐使用场景 | 需要完整日志功能的正式应用程序 | 简单日志需求或快速原型 | ### 毫秒时间戳的数学表示 时间戳的数学定义为: $$ t_{\text{timestamp}} = \lfloor t_{\text{unix}} \rfloor_{\text{date}} + \frac{\lfloor (t_{\text{unix}} - \lfloor t_{\text{unix}} \rfloor) \times 1000 \rfloor}{1000} $$ 其中: - $t_{\text{unix}}$ 是Unix时间戳(秒为单位,小数部分为亚秒) - $\lfloor \cdot \rfloor$ 表示向下取整 ### 相关问题 §§相关问题§§ 1. 如何在PyQt6中实现日志分级(DEBUG/INFO/WARN/ERROR)? 2. 如何将日志同时输出到PyQt6的GUI界面和文件? 3. 如何解决高发场景下的日志性能问题? [^1]: 参考Python官方文档logging.Formatter的格式定义 </think> ### 在PyQt6中添加毫秒级时间戳的完整方案 以下是两种实现毫秒级时间戳日志的方法,均支持精确到毫秒的时间记录: --- #### 方法1:使用Python标准库的logging模块(推荐) ```python import logging from logging.handlers import RotatingFileHandler from PyQt6.QtCore import QObject, pyqtSignal import datetime class QtLogger(QObject): log_signal = pyqtSignal(str) def __init__(self): super().__init__() self.logger = logging.getLogger("PyQt6App") self.logger.setLevel(logging.DEBUG) # 毫秒级时间戳格式 formatter = logging.Formatter( fmt='[%(asctime)s.%(msecs)03d][%(levelname)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) # 控制台输出 console_handler = logging.StreamHandler() console_handler.setFormatter(formatter) self.logger.addHandler(console_handler) # 文件输出(带自动轮转) file_handler = RotatingFileHandler( 'app.log', maxBytes=10*1024*1024, # 10MB backupCount=5 ) file_handler.setFormatter(formatter) self.logger.addHandler(file_handler) self.log_signal.connect(self._handle_log) def _handle_log(self, log_data): """实际处理日志记录的方法""" level, message = log_data if level == "DEBUG": self.logger.debug(message) elif level == "INFO": self.logger.info(message) elif level == "WARNING": self.logger.warning(message) elif level == "ERROR": self.logger.error(message) elif level == "CRITICAL": self.logger.critical(message) def log(self, level, message): """线程安全的日志接口""" self.log_signal.emit((level, message)) # 使用示例 logger = QtLogger() logger.log("INFO", "用户登录成功") # 输出: [2023-08-15 16:45:30.452][INFO] 用户登录成功 ``` **关键特性**: 1. **毫秒级时间戳**:使用`%(asctime)s.%(msecs)03d`格式组合 - `%(asctime)s`:标准日期时间 - `.%(msecs)03d`:毫秒部分(3位补零) 2. **时间格式化原理**: $$ t_{\text{格式化}} = \text{datefmt} + \text{fmt}_{\text{毫秒}} $$ 其中毫秒部分通过日志记录对象的`msecs`属性获取[^1] --- #### 方法2:手动生成毫秒级时间戳 ```python from PyQt6.QtCore import QObject, pyqtSignal import datetime class PrecisionLogger(QObject): log_signal = pyqtSignal(str) def __init__(self): super().__init__() self.log_signal.connect(print) # 实际应用中可连接到GUI组件 def log(self, level, message): """生成含毫秒的时间戳""" # 获取当前时间(精确到毫秒) now = datetime.datetime.now() timestamp = now.strftime("%Y-%m-%d %H:%M:%S") + f".{now.microsecond//1000:03d}" # 格式化日志 log_entry = f"[{timestamp}][{level}] {message}" self.log_signal.emit(log_entry) # 使用示例 logger = PrecisionLogger() logger.log("DEBUG", "数据校验通过") # 输出: [2023-08-15 16:47:15.763][DEBUG] 数据校验通过 ``` **毫秒提取原理**: $$ \text{毫秒值} = \left\lfloor \frac{\text{微秒值}}{1000} \right\rfloor $$ 其中: - 微秒值通过`datetime.now().microsecond`获取 - 使用整数除法截取前3位毫秒数字[^2] --- ### 毫秒时间戳的数学表示 精确时间戳的数学定义为: $$ t_{\text{full}} = t_{\text{date}} + \frac{t_{\text{ms}}}{1000} + \frac{t_{\mu s}}{1000000} $$ 实际应用中通常截断为: $$ t_{\text{log}} = t_{\text{date}} + \frac{\left\lfloor t_{\mu s}/1000 \right\rfloor}{1000} $$ 其中: - $t_{\text{date}}$ = 日期时间(年月日时分秒) - $t_{\text{ms}}$ = 毫秒部分(0-999) - $t_{\mu s}$ = 微秒部分(0-999999) --- ### 在TCP服务器中的应用示例 ```python class TCPServer(QObject): def __init__(self): super().__init__() self.logger = QtLogger() # 使用方法1的日志器 def handle_client(self, client_socket): try: data = client_socket.recv(1024) self.logger.log("DEBUG", f"收到数据: {data.hex()}") # 业务处理逻辑... self.logger.log("INFO", "数据处理完成") except Exception as e: self.logger.log("ERROR", f"处理异常: {str(e)}") self.logger.log("DEBUG", "完整异常信息", exc_info=True) ``` --- ### 注意事项 1. **时间精度**:Windows系统默认时钟精度约15ms,可通过`time.get_clock_info('monotonic')`查看 2. **性能优化**:高频日志场景建议启用队列缓冲 ```python from logging.handlers import QueueHandler, QueueListener ``` 3. **时区处理**:如需UTC时间,使用`datetime.datetime.utcnow()`
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值