#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");
}
-
流程:
-
格式化时间、标题和数据信息。
-
在
QTreeWidget
中添加主条目和十六进制子条目。 -
调用
pack()
清理旧日志。 -
根据复选框状态决定是否写入文件。
-
(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. 改进建议
-
线程安全:
-
若在多线程中调用
output()
,需加锁保护m_treeOut
和m_textOut
。
-
-
性能优化:
-
大量日志时,
QTreeWidget
可能卡顿,可改用QListView
+QStandardItemModel
。
-
-
扩展功能:
-
添加日志级别(INFO/WARN/ERROR)和过滤功能。
-
支持日志文件按大小分割(避免单个文件过大)。
-
总结
-
适用场景:调试工具、网络监控、系统日志记录。
-
优势:集成UI显示与文件存储,支持二进制数据可视化。
-
依赖项:需配合
Setting
类管理路径,TK
工具类提供数据转换(如bin2hex
)。