Qt编程:sokit工具中Logger

#ifndef __LOGGER_H__
#define __LOGGER_H__

#include <QFile>
#include <QTreeView>
#include <QCheckBox>
#include <QMenu>

class QTreeWidget;
class QTreeWidgetItem;
class QPlainTextEdit;
class Logger : public QObject
{
	Q_OBJECT

public:
	Logger(QObject *parent = 0);
	~Logger();

	void init(QTreeWidget* o, QCheckBox* w, QPlainTextEdit* d);

	void clear();
	void output(const QString& title, const QString& info);
	void output(const char* buf, quint32 len);

signals:
	void clearLog();

public slots:
	void output(const QString& info);
	void output(const QString& title, const char* buf, quint32 len);

private slots:
	void ctxmenu(const QPoint& pos);
	void copy();
	void syncOutput();

private:
	const QString getLogFileName();
	void writeLogFile(const QString& info);
	void pack();
	QTreeWidgetItem* appendLogEntry(QTreeWidgetItem* p, const QString& t);

private:
	QString m_dir;
	QFile m_file;

	QMenu m_cmlog, m_cmtxt;
    QCheckBox* m_chkWrite;
    QTreeWidget* m_treeOut;
    QPlainTextEdit* m_textOut;
};

#endif // __LOGGER_H__

#include <QDate>
#include <QDir>
#include <QTextStream>
#include <QKeySequence>
#include <QApplication>
#include <QClipboard>
#include <QTreeWidget>
#include <QPlainTextEdit>
#include "toolkit.h"
#include "setting.h"
#include "logger.h"

#define SET_MAX_LOGITM  100
#define SET_MAX_LOGTRM  30

Logger::Logger(QObject *parent)
: QObject(parent),m_chkWrite(0),m_treeOut(0),m_textOut(0)
{
}

Logger::~Logger()
{
	m_file.close();
}

void Logger::init(QTreeWidget* o, QCheckBox* w, QPlainTextEdit* d)
{
	m_cmlog.clear();
	m_cmtxt.clear();

	if (m_treeOut)
		m_treeOut->disconnect(this);

	if (m_textOut)
		m_textOut->disconnect(this);

	if (m_chkWrite)
		m_chkWrite->disconnect(this);

    m_treeOut = o;
	m_textOut = d;
    m_chkWrite = w;

	if (m_treeOut && m_textOut && m_chkWrite)
	{
		QList<QKeySequence> ks;
		ks << QKeySequence(Qt::CTRL + Qt::Key_D);

		QAction* copy = new QAction(tr("Copy"), this);
		copy->setShortcuts(QKeySequence::Copy);
		connect(copy, SIGNAL(triggered()), this, SLOT(copy()));

		QAction* clear = new QAction(tr("Clear"), this);
		clear->setShortcuts(ks);
		connect(clear, SIGNAL(triggered()), this, SIGNAL(clearLog()));

		QAction* all = new QAction(tr("Select All"), this);
		all->setShortcuts(QKeySequence::SelectAll);
        connect(all, SIGNAL(triggered()), m_textOut, SLOT(selectAll()));

		m_cmlog.addAction(copy);
		m_cmlog.addSeparator();
		m_cmlog.addAction(clear);

		m_cmtxt.addAction(copy);
		m_cmtxt.addSeparator();
		m_cmtxt.addAction(all);

		QPalette pal = m_textOut->palette();
		pal.setBrush(QPalette::Base, m_treeOut->palette().brush(QPalette::Window));
		m_textOut->setPalette(pal);

		m_treeOut->setContextMenuPolicy(Qt::CustomContextMenu);
		connect(m_treeOut, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(ctxmenu(const QPoint&)));
		connect(m_treeOut, SIGNAL(itemSelectionChanged()), this, SLOT(syncOutput()));

		m_textOut->setContextMenuPolicy(Qt::CustomContextMenu);
		connect(m_textOut, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(ctxmenu(const QPoint&)));
	}
}

void Logger::syncOutput()
{
	QList<QTreeWidgetItem*> list = m_treeOut->selectedItems();
	if (!list.isEmpty())
		m_textOut->setPlainText(list.first()->text(0));
	else
		m_textOut->clear();
}

void Logger::ctxmenu(const QPoint& pos)
{
	if (sender() == (QObject*)m_treeOut)
		m_cmlog.exec(m_treeOut->mapToGlobal(pos));
	else
		m_cmtxt.exec(m_textOut->mapToGlobal(pos));
}

void Logger::copy()
{
	if (sender() == (QObject*)m_treeOut)
	{
		QList<QTreeWidgetItem*> list = m_treeOut->selectedItems();
		if (!list.isEmpty())
			QApplication::clipboard()->setText(list.first()->text(0));
	}
	else
	{
		m_textOut->copy();
	}
}

const QString Logger::getLogFileName()
{
	int i = 0;
	while (2 > i++)
	{
		if (!m_dir.isEmpty())
		{
			QDir d;
			if (d.exists(m_dir) || d.mkpath(m_dir)) {
				i = 0;
				break;
			}
		}

        m_dir = Setting::path() + "/" + property(SET_SEC_DIR).toString();
	}

	return (i==2) ? QString() : m_dir + QDir::separator() + 
		QDate::currentDate().toString("yyyyMMdd.log");
}

void Logger::writeLogFile(const QString& info)
{
	if (!m_chkWrite->isChecked())
		return;

	m_file.close();
	m_file.setFileName(getLogFileName());

	if (m_file.open(QIODevice::Append|
					QIODevice::WriteOnly|
					QIODevice::Text))
	{
		QByteArray a(info.toUtf8());

		const char* d = a.data();
		for (int n=a.size(); n>0;)
		{
			int w = m_file.write(d, n);

			d += w;
			n -= w;
		}

		m_file.close();
	}
}

void Logger::clear()
{
	m_treeOut->clear();
	m_textOut->clear();
}

void Logger::output(const QString& info)
{
	output("MSG", info);
}

void Logger::output(const char* buf, uint len)
{
	output("DAT", buf, len);
}

void Logger::pack()
{
	if (m_treeOut->topLevelItemCount() > SET_MAX_LOGITM)
		m_treeOut->model()->removeRows(0, SET_MAX_LOGTRM);

	m_treeOut->scrollToBottom();
}

QTreeWidgetItem* Logger::appendLogEntry(QTreeWidgetItem* p, const QString& t)
{
	QTreeWidgetItem* res = new QTreeWidgetItem(p);
	if (res)
	{
		res->setText(0, t);

		if (p)
		{
			p->addChild(res);
		}
		else
		{
			m_treeOut->addTopLevelItem(res);
			m_textOut->setPlainText(t);
		}
	}
	return res;
}

void Logger::output(const QString& title, const QString& info)
{
	QTreeWidgetItem* it = new QTreeWidgetItem(0);
	if (!it) return;

	QString lab(QTime::currentTime().toString("HH:mm:ss "));
	
	lab += title;
	lab += ' ';
	lab += info;

	appendLogEntry(0, lab);

	pack();

	lab += '\n';
	lab += '\n';

	writeLogFile(lab);
}

void Logger::output(const QString& title, const char* buf, quint32 len)
{
	QString lab(QTime::currentTime().toString("HH:mm:ss "));
	
	QTextStream out(&lab);

	out << title
		<< " <" << len << "> "
		<< TK::bin2ascii(buf, len);

	QString hex = TK::bin2hex(buf, len);

	QTreeWidgetItem* it = appendLogEntry(0, lab);
	if (it)
	{
		appendLogEntry(it, hex);

		pack();
	}

	out << '\n' << hex << '\n' << '\n';

	writeLogFile(lab);
}

 核心功能

(1) 日志输出
  • 多格式支持

    • 文本日志:output(const QString& info)

    • 带标题的文本:output(const QString& title, const QString& info)

    • 二进制数据:output(const char* buf, quint32 len)(自动转十六进制和ASCII)

  • UI 同步显示

    • 日志条目显示在 QTreeWidget 中,详情在 QPlainTextEdit 中展示。

(2) 日志管理
  • 自动清理:超过 SET_MAX_LOGITM(100条)时,删除最早 SET_MAX_LOGTRM(30条)。

  • 文件存储:按日期生成日志文件(如 yyyyMMdd.log),可选是否写入文件(通过 QCheckBox 控制)。

(3) 用户交互
  • 右键菜单

    • 复制日志内容(支持快捷键 Ctrl+C)。

    • 清空日志(快捷键 Ctrl+D)。

    • 全选文本(仅 QPlainTextEdit)。

  • 条目同步:点击 QTreeWidget 中的日志条目,内容自动显示在 QPlainTextEdit 中。

2. 关键代码解析

(1) 初始化与UI绑定
void Logger::init(QTreeWidget* o, QCheckBox* w, QPlainTextEdit* d) {
    m_treeOut = o;  // 日志树形列表
    m_textOut = d;  // 日志详情文本框
    m_chkWrite = w; // 是否写入文件的复选框

    // 设置右键菜单动作
    QAction* copy = new QAction(tr("Copy"), this);
    connect(copy, &QAction::triggered, this, &Logger::copy);

    // 绑定信号槽
    connect(m_treeOut, &QTreeWidget::customContextMenuRequested, 
            this, &Logger::ctxmenu);
    connect(m_treeOut, &QTreeWidget::itemSelectionChanged, 
            this, &Logger::syncOutput);
}
  • 作用:将日志组件与UI控件关联,初始化菜单和交互逻辑。

(2) 日志输出逻辑
void Logger::output(const QString& title, const char* buf, quint32 len) {
    QString lab = QTime::currentTime().toString("HH:mm:ss ") + title;
    lab += " <" + QString::number(len) + "> " + TK::bin2ascii(buf, len);

    // 添加到UI
    QTreeWidgetItem* it = appendLogEntry(0, lab);
    if (it) {
        appendLogEntry(it, TK::bin2hex(buf, len)); // 十六进制子条目
        pack(); // 清理超限日志
    }

    // 写入文件
    writeLogFile(lab + "\n" + TK::bin2hex(buf, len) + "\n\n");
}
  • 流程

    1. 格式化时间、标题和数据信息。

    2. 在 QTreeWidget 中添加主条目和十六进制子条目。

    3. 调用 pack() 清理旧日志。

    4. 根据复选框状态决定是否写入文件。

(3) 文件存储
void Logger::writeLogFile(const QString& info) {
    if (!m_chkWrite->isChecked()) return;

    m_file.setFileName(getLogFileName()); // 生成路径如 "logs/20230815.log"
    if (m_file.open(QIODevice::Append | QIODevice::Text)) {
        m_file.write(info.toUtf8());
        m_file.close();
    }
}
  • 路径生成:通过 Setting::path() 获取基础目录,默认存储在 ./logs/ 下。

(4) 自动清理(pack)

void Logger::pack() { if (m_treeOut->topLevelItemCount() > SET_MAX_LOGITM) { m_treeOut->model()->removeRows(0, SET_MAX_LOGTRM); // 删除前30条 } m_treeOut->scrollToBottom(); // 滚动到底部 }

3. 信号与槽

信号/槽功能描述
clearLog()清空所有日志(触发 clear()
syncOutput()同步选中日志内容到文本框
ctxmenu()弹出右键菜单(区分树形控件和文本框)
copy()复制选中文本或日志条目

4. 使用示例

(1) 初始化Logger
Logger logger;
logger.init(ui->treeWidget, ui->chkWriteLog, ui->plainTextEdit);

// 连接清空日志信号
connect(&logger, &Logger::clearLog, [&]() { 
    ui->treeWidget->clear(); 
    ui->plainTextEdit->clear();
});
(2) 记录日志
// 文本日志
logger.output("System", "Server started on port 8080");

// 二进制数据(如网络包)
const char data[] = {0x48, 0x65, 0x6C, 0x6C, 0x6F}; // "Hello"
logger.output("NET", data, sizeof(data));

5. 改进建议

  1. 线程安全

    • 若在多线程中调用 output(),需加锁保护 m_treeOut 和 m_textOut

  2. 性能优化

    • 大量日志时,QTreeWidget 可能卡顿,可改用 QListView + QStandardItemModel

  3. 扩展功能

    • 添加日志级别(INFO/WARN/ERROR)和过滤功能。

    • 支持日志文件按大小分割(避免单个文件过大)。

总结

  • 适用场景:调试工具、网络监控、系统日志记录。

  • 优势:集成UI显示与文件存储,支持二进制数据可视化。

  • 依赖项:需配合 Setting 类管理路径,TK 工具类提供数据转换(如 bin2hex)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值