Qt开发一个日志分析工具可以帮助用户更高效地处理和理解日志文件中的信息。使用Qt框架来构建这样的工具,可以利用其丰富的GUI组件库以及强大的数据处理能力。包含日志加载、搜索、多标签支持、图表分析、正则搜索、日志高亮和自动刷新等功能。
功能设计
- 日志加载:支持从本地文件系统加载日志文件,可能需要支持多种格式(如文本、CSV等)。
- 搜索功能:允许用户通过关键词搜索特定的日志条目。
- 过滤器:提供基于时间戳、严重性等级(例如INFO, WARN, ERROR等)、源IP地址等条件的过滤功能。
- 展示与导出:以表格形式展示结果,并提供导出为CSV或其他格式的功能。
技术实现
-
主界面设计:可以使用
QMainWindow
作为主窗口,包含菜单栏、工具栏和状态栏。在中央区域使用QTableView
或QTreeView
来显示日志数据。 -
模型与视图:
- 使用
QStandardItemModel
或自定义的QAbstractTableModel
来存储和管理日志数据。 QTableView
用于显示日志数据,可以通过设置不同的列来展示不同类型的日志信息(例如时间戳、级别、消息等)。
- 使用
-
文件读取:利用
QFile
和QTextStream
读取日志文件内容。根据日志格式解析每一行的数据,并将其添加到模型中。 -
搜索与过滤:
- 实现搜索框,监听输入并更新模型中的数据显示。
- 过滤可以通过对原始数据进行筛选后重新设置模型数据,或者使用代理模型(
QSortFilterProxyModel
)来动态过滤视图中的显示数据。
-
导出功能:可以使用
QFile
和QTextStream
将当前显示的数据写入到一个新的文件中,支持CSV等格式。
1. 主窗口与多标签实现
// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTabWidget>
#include <QFileSystemWatcher>
class LogWidget; // 前向声明
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
protected:
void closeEvent(QCloseEvent *event) override;
private slots:
void openFile();
void closeTab(int index);
void updateWindowTitle();
void fileChanged(const QString &path);
private:
void createActions();
void createMenus();
void createToolBars();
void createStatusBar();
void readSettings();
void writeSettings();
QTabWidget *tabWidget;
QFileSystemWatcher *fileWatcher;
QMenu *fileMenu;
QMenu *viewMenu;
QMenu *helpMenu;
QToolBar *fileToolBar;
QAction *openAct;
QAction *exitAct;
QAction *aboutAct;
};
#endif // MAINWINDOW_H
cpp实现
// mainwindow.cpp
#include "mainwindow.h"
#include "logwidget.h"
#include <QFileDialog>
#include <QMessageBox>
#include <QSettings>
#include <QCloseEvent>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
tabWidget = new QTabWidget(this);
tabWidget->setTabsClosable(true);
tabWidget->setMovable(true);
setCentralWidget(tabWidget);
fileWatcher = new QFileSystemWatcher(this);
createActions();
createMenus();
createToolBars();
createStatusBar();
connect(tabWidget, &QTabWidget::tabCloseRequested, this, &MainWindow::closeTab);
connect(fileWatcher, &QFileSystemWatcher::fileChanged, this, &MainWindow::fileChanged);
readSettings();
setWindowTitle(tr("日志分析工具"));
}
void MainWindow::openFile()
{
QString fileName = QFileDialog::getOpenFileName(this, tr("打开日志文件"), "",
tr("日志文件 (*.log *.txt);;所有文件 (*.*)"));
if (!fileName.isEmpty()) {
// 检查是否已经打开
for (int i = 0; i < tabWidget->count(); ++i) {
LogWidget *widget = qobject_cast<LogWidget*>(tabWidget->widget(i));
if (widget && widget->currentFile() == fileName) {
tabWidget->setCurrentIndex(i);
return;
}
}
LogWidget *logWidget = new LogWidget(fileName, this);
int tabIndex = tabWidget->addTab(logWidget, QFileInfo(fileName).fileName());
tabWidget->setCurrentIndex(tabIndex);
fileWatcher->addPath(fileName);
connect(logWidget, &LogWidget::titleChanged, this, &MainWindow::updateWindowTitle);
}
}
void MainWindow::closeTab(int index)
{
LogWidget *widget = qobject_cast<LogWidget*>(tabWidget->widget(index));
if (widget) {
fileWatcher->removePath(widget->currentFile());
widget->deleteLater();
}
tabWidget->removeTab(index);
if (tabWidget->count() == 0) {
setWindowTitle(tr("日志分析工具"));
}
}
void MainWindow::fileChanged(const QString &path)
{
for (int i = 0; i < tabWidget->count(); ++i) {
LogWidget *widget = qobject_cast<LogWidget*>(tabWidget->widget(i));
if (widget && widget->currentFile() == path) {
widget->reloadFile();
break;
}
}
}
// ...其他实现...
2. 日志显示组件实现
// logwidget.h
#ifndef LOGWIDGET_H
#define LOGWIDGET_H
#include <QWidget>
#include <QSplitter>
#include <QTableView>
#include <QChartView>
#include <QStandardItemModel>
#include <QSortFilterProxyModel>
#include <QRegularExpression>
QT_CHARTS_USE_NAMESPACE
class LogWidget : public QWidget
{
Q_OBJECT
public:
explicit LogWidget(const QString &fileName, QWidget *parent = nullptr);
~LogWidget();
QString currentFile() const { return m_currentFile; }
void reloadFile();
signals:
void titleChanged(const QString &title);
private slots:
void updateFilter();
void updateStats();
void showContextMenu(const QPoint &pos);
void exportToCsv();
void copySelected();
private:
void loadFile();
void parseLogLine(const QString &line, int lineNumber);
void setupUi();
void setupChart();
void applyHighlighting();
QString m_currentFile;
QStandardItemModel *m_logModel;
LogFilterProxyModel *m_proxyModel;
QTableView *m_logView;
QChartView *m_chartView;
QChart *m_chart;
// UI控件
QLineEdit *m_searchEdit;
QComboBox *m_levelCombo;
QCheckBox *m_regexCheck;
QCheckBox *m_caseSensitiveCheck;
QCheckBox *m_autoRefreshCheck;
QDateTimeEdit *m_fromDateTime;
QDateTimeEdit *m_toDateTime;
};
class LogFilterProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
LogFilterProxyModel(QObject *parent = nullptr);
void setFilterRegex(const QRegularExpression ®ex);
void setFilterLevel(const QString &level);
void setFilterTimeRange(qint64 from, qint64 to);
void setFilterCaseSensitive(bool caseSensitive);
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
private:
QRegularExpression m_regex;
QString m_level;
qint64 m_fromTime = 0;
qint64 m_toTime = 0;
bool m_caseSensitive = false;
};
#endif // LOGWIDGET_H
3. 日志组件实现
// logwidget.cpp
#include "logwidget.h"
#include <QFile>
#include <QTextStream>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGroupBox>
#include <QLabel>
#include <QMenu>
#include <QClipboard>
#include <QFileDialog>
#include <QMessageBox>
#include <QDateTime>
#include <QBarSet>
#include <QBarSeries>
#include <QBarCategoryAxis>
#include <QValueAxis>
#include <QRegularExpression>
LogWidget::LogWidget(const QString &fileName, QWidget *parent)
: QWidget(parent), m_currentFile(fileName)
{
m_logModel = new QStandardItemModel(this);
m_logModel->setHorizontalHeaderLabels({
tr("时间戳"), tr("级别"), tr("来源"), tr("消息"), tr("行号")
});
m_proxyModel = new LogFilterProxyModel(this);
m_proxyModel->setSourceModel(m_logModel);
setupUi();
loadFile();
setWindowTitle(QFileInfo(fileName).fileName());
}
void LogWidget::setupUi()
{
QSplitter *splitter = new QSplitter(Qt::Vertical, this);
// 日志表格视图
m_logView = new QTableView(this);
m_logView->setModel(m_proxyModel);
m_logView->setSortingEnabled(true);
m_logView->setContextMenuPolicy(Qt::CustomContextMenu);
m_logView->setSelectionBehavior(QAbstractItemView::SelectRows);
m_logView->verticalHeader()->setDefaultSectionSize(20);
m_logView->setAlternatingRowColors(true);
// 图表视图
m_chartView = new QChartView(this);
m_chartView->setRenderHint(QPainter::Antialiasing);
splitter->addWidget(m_logView);
splitter->addWidget(m_chartView);
splitter->setStretchFactor(0, 3);
splitter->setStretchFactor(1, 1);
// 过滤控制面板
QGroupBox *filterGroup = new QGroupBox(tr("过滤条件"), this);
m_searchEdit = new QLineEdit(this);
m_levelCombo = new QComboBox(this);
m_levelCombo->addItems({"ALL", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"});
m_regexCheck = new QCheckBox(tr("正则表达式"), this);
m_caseSensitiveCheck = new QCheckBox(tr("区分大小写"), this);
m_autoRefreshCheck = new QCheckBox(tr("自动刷新"), this);
m_autoRefreshCheck->setChecked(true);
m_fromDateTime = new QDateTimeEdit(QDateTime::currentDateTime().addDays(-1), this);
m_toDateTime = new QDateTimeEdit(QDateTime::currentDateTime(), this);
m_fromDateTime->setDisplayFormat("yyyy-MM-dd HH:mm:ss");
m_toDateTime->setDisplayFormat("yyyy-MM-dd HH:mm:ss");
QPushButton *filterBtn = new QPushButton(tr("应用过滤"), this);
QHBoxLayout *filterLayout = new QHBoxLayout;
filterLayout->addWidget(new QLabel(tr("搜索:")));
filterLayout->addWidget(m_searchEdit);
filterLayout->addWidget(new QLabel(tr("级别:")));
filterLayout->addWidget(m_levelCombo);
filterLayout->addWidget(m_regexCheck);
filterLayout->addWidget(m_caseSensitiveCheck);
filterLayout->addWidget(new QLabel(tr("从:")));
filterLayout->addWidget(m_fromDateTime);
filterLayout->addWidget(new QLabel(tr("到:")));
filterLayout->addWidget(m_toDateTime);
filterLayout->addWidget(m_autoRefreshCheck);
filterLayout->addWidget(filterBtn);
filterLayout->addStretch();
filterGroup->setLayout(filterLayout);
// 主布局
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(filterGroup);
mainLayout->addWidget(splitter);
// 连接信号槽
connect(filterBtn, &QPushButton::clicked, this, &LogWidget::updateFilter);
connect(m_logView, &QTableView::customContextMenuRequested,
this, &LogWidget::showContextMenu);
// 设置列宽
m_logView->setColumnWidth(0, 150);
m_logView->setColumnWidth(1, 60);
m_logView->setColumnWidth(2, 120);
m_logView->setColumnWidth(4, 60);
// 初始化图表
setupChart();
}
void LogWidget::loadFile()
{
QFile file(m_currentFile);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QMessageBox::warning(this, tr("错误"), tr("无法打开文件: %1").arg(m_currentFile));
return;
}
m_logModel->removeRows(0, m_logModel->rowCount());
QTextStream in(&file);
int lineNumber = 0;
while (!in.atEnd()) {
parseLogLine(in.readLine(), ++lineNumber);
}
file.close();
updateStats();
applyHighlighting();
}
void LogWidget::parseLogLine(const QString &line, int lineNumber)
{
// 示例解析逻辑 - 根据实际日志格式调整
static QRegularExpression logRegex(
"(?<timestamp>\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2},\\d{3}) "
"\\[(?<thread>.*?)\\] "
"(?<level>\\w+) "
"(?<logger>\\S+) "
"- "
"(?<message>.*)"
);
QRegularExpressionMatch match = logRegex.match(line);
if (match.hasMatch()) {
QList<QStandardItem*> rowItems;
rowItems << new QStandardItem(match.captured("timestamp"));
rowItems << new QStandardItem(match.captured("level"));
rowItems << new QStandardItem(match.captured("logger"));
rowItems << new QStandardItem(match.captured("message"));
rowItems << new QStandardItem(QString::number(lineNumber));
// 设置数据角色用于高亮
QString level = match.captured("level");
QColor color = Qt::black;
if (level == "ERROR" || level == "FATAL") color = Qt::red;
else if (level == "WARN") color = QColor(255, 165, 0); // Orange
else if (level == "INFO") color = Qt::blue;
else if (level == "DEBUG") color = Qt::darkGray;
for (QStandardItem *item : rowItems) {
item->setData(color, Qt::ForegroundRole);
}
m_logModel->appendRow(rowItems);
} else {
// 无法解析的行作为纯文本
QList<QStandardItem*> rowItems;
rowItems << new QStandardItem("");
rowItems << new QStandardItem("UNKNOWN");
rowItems << new QStandardItem("");
rowItems << new QStandardItem(line);
rowItems << new QStandardItem(QString::number(lineNumber));
m_logModel->appendRow(rowItems);
}
}
void LogWidget::setupChart()
{
m_chart = new QChart();
m_chart->setTitle(tr("日志级别统计"));
m_chart->setAnimationOptions(QChart::SeriesAnimations);
m_chartView->setChart(m_chart);
}
void LogWidget::updateStats()
{
// 清除旧图表
m_chart->removeAllSeries();
// 统计各级别数量
QMap<QString, int> levelCounts;
for (int i = 0; i < m_logModel->rowCount(); ++i) {
QString level = m_logModel->index(i, 1).data().toString();
levelCounts[level]++;
}
// 创建柱状图
QBarSet *set = new QBarSet(tr("日志数量"));
QStringList categories;
QStringList levels = {"DEBUG", "INFO", "WARN", "ERROR", "FATAL"};
for (const QString &level : levels) {
*set << levelCounts.value(level, 0);
categories << level;
}
QBarSeries *series = new QBarSeries();
series->append(set);
m_chart->addSeries(series);
// 设置X轴
QBarCategoryAxis *axisX = new QBarCategoryAxis();
axisX->append(categories);
m_chart->addAxis(axisX, Qt::AlignBottom);
series->attachAxis(axisX);
// 设置Y轴
QValueAxis *axisY = new QValueAxis();
m_chart->addAxis(axisY, Qt::AlignLeft);
series->attachAxis(axisY);
// 设置颜色
QColor colors[] = {Qt::darkGray, Qt::blue, QColor(255, 165, 0), Qt::red, Qt::darkRed};
for (int i = 0; i < set->count(); ++i) {
set->setColor(colors[i], i);
}
}
void LogWidget::updateFilter()
{
QString pattern = m_searchEdit->text();
bool caseSensitive = m_caseSensitiveCheck->isChecked();
bool useRegex = m_regexCheck->isChecked();
QRegularExpression regex;
if (useRegex) {
regex.setPattern(pattern);
regex.setPatternOptions(caseSensitive ? QRegularExpression::NoPatternOption
: QRegularExpression::CaseInsensitiveOption);
} else {
// 普通文本搜索转换为正则表达式
regex.setPattern(QRegularExpression::escape(pattern));
regex.setPatternOptions(caseSensitive ? QRegularExpression::NoPatternOption
: QRegularExpression::CaseInsensitiveOption);
}
m_proxyModel->setFilterRegex(regex);
m_proxyModel->setFilterLevel(m_levelCombo->currentText());
m_proxyModel->setFilterCaseSensitive(caseSensitive);
qint64 from = m_fromDateTime->dateTime().toMSecsSinceEpoch();
qint64 to = m_toDateTime->dateTime().toMSecsSinceEpoch();
m_proxyModel->setFilterTimeRange(from, to);
}
void LogWidget::applyHighlighting()
{
for (int i = 0; i < m_logModel->rowCount(); ++i) {
QString level = m_logModel->index(i, 1).data().toString();
QColor color = Qt::black;
if (level == "ERROR" || level == "FATAL") color = Qt::red;
else if (level == "WARN") color = QColor(255, 165, 0);
else if (level == "INFO") color = Qt::blue;
else if (level == "DEBUG") color = Qt::darkGray;
for (int col = 0; col < m_logModel->columnCount(); ++col) {
QModelIndex index = m_logModel->index(i, col);
m_logModel->setData(index, color, Qt::ForegroundRole);
}
}
}
void LogWidget::showContextMenu(const QPoint &pos)
{
QMenu menu(this);
QAction *copyAction = new QAction(tr("复制选中内容"), this);
connect(copyAction, &QAction::triggered, this, &LogWidget::copySelected);
QAction *exportAction = new QAction(tr("导出为CSV"), this);
connect(exportAction, &QAction::triggered, this, &LogWidget::exportToCsv);
menu.addAction(copyAction);
menu.addAction(exportAction);
menu.exec(m_logView->viewport()->mapToGlobal(pos));
}
void LogWidget::copySelected()
{
QModelIndexList selected = m_logView->selectionModel()->selectedRows();
if (selected.isEmpty()) return;
QStringList lines;
for (const QModelIndex &index : selected) {
QStringList parts;
for (int col = 0; col < m_proxyModel->columnCount(); ++col) {
parts << m_proxyModel->index(index.row(), col).data().toString();
}
lines << parts.join("\t");
}
QApplication::clipboard()->setText(lines.join("\n"));
}
void LogWidget::exportToCsv()
{
QString fileName = QFileDialog::getSaveFileName(this, tr("导出为CSV"),
QFileInfo(m_currentFile).baseName() + ".csv",
tr("CSV文件 (*.csv)"));
if (fileName.isEmpty()) return;
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QMessageBox::warning(this, tr("错误"), tr("无法创建文件"));
return;
}
QTextStream out(&file);
// 写入表头
for (int col = 0; col < m_proxyModel->columnCount(); ++col) {
out << "\"" << m_proxyModel->headerData(col, Qt::Horizontal).toString() << "\"";
if (col < m_proxyModel->columnCount() - 1) out << ",";
}
out << "\n";
// 写入数据
for (int row = 0; row < m_proxyModel->rowCount(); ++row) {
for (int col = 0; col < m_proxyModel->columnCount(); ++col) {
QString text = m_proxyModel->index(row, col).data().toString();
text.replace("\"", "\"\""); // 转义引号
out << "\"" << text << "\"";
if (col < m_proxyModel->columnCount() - 1) out << ",";
}
out << "\n";
}
file.close();
QMessageBox::information(this, tr("成功"), tr("导出完成"));
}
void LogWidget::reloadFile()
{
if (m_autoRefreshCheck->isChecked()) {
loadFile();
}
}
4. 代理模型实现
// logfilterproxymodel.cpp
#include "logwidget.h"
LogFilterProxyModel::LogFilterProxyModel(QObject *parent)
: QSortFilterProxyModel(parent)
{
}
void LogFilterProxyModel::setFilterRegex(const QRegularExpression ®ex)
{
m_regex = regex;
invalidateFilter();
}
void LogFilterProxyModel::setFilterLevel(const QString &level)
{
m_level = level;
invalidateFilter();
}
void LogFilterProxyModel::setFilterTimeRange(qint64 from, qint64 to)
{
m_fromTime = from;
m_toTime = to;
invalidateFilter();
}
void LogFilterProxyModel::setFilterCaseSensitive(bool caseSensitive)
{
m_caseSensitive = caseSensitive;
invalidateFilter();
}
bool LogFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
QModelIndex timestampIndex = sourceModel()->index(sourceRow, 0, sourceParent);
QModelIndex levelIndex = sourceModel()->index(sourceRow, 1, sourceParent);
QModelIndex messageIndex = sourceModel()->index(sourceRow, 3, sourceParent);
// 级别过滤
if (!m_level.isEmpty() && m_level != "ALL") {
QString level = sourceModel()->data(levelIndex).toString();
if (!level.contains(m_level, m_caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive)) {
return false;
}
}
// 时间范围过滤
if (m_fromTime > 0 || m_toTime > 0) {
QString timestampStr = sourceModel()->data(timestampIndex).toString();
QDateTime timestamp = QDateTime::fromString(timestampStr, "yyyy-MM-dd HH:mm:ss,zzz");
if (!timestamp.isValid()) {
timestamp = QDateTime::fromString(timestampStr, "yyyy-MM-dd HH:mm:ss");
}
if (timestamp.isValid()) {
qint64 msecs = timestamp.toMSecsSinceEpoch();
if ((m_fromTime > 0 && msecs < m_fromTime) ||
(m_toTime > 0 && msecs > m_toTime)) {
return false;
}
}
}
// 正则表达式过滤
if (!m_regex.pattern().isEmpty()) {
QString message = sourceModel()->data(messageIndex).toString();
if (!message.contains(m_regex)) {
return false;
}
}
return true;
}
5. 主程序入口
// main.cpp
#include "mainwindow.h"
#include <QApplication>
#include <QStyleFactory>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// 设置应用程序信息
QApplication::setApplicationName("Qt日志分析工具");
QApplication::setApplicationVersion("1.0");
QApplication::setOrganizationName("MyCompany");
// 设置样式
QApplication::setStyle(QStyleFactory::create("Fusion"));
MainWindow w;
w.showMaximized();
// 处理命令行参数
if (argc > 1) {
w.openFile(QString::fromLocal8Bit(argv[1]));
}
return a.exec();
}
示例日志文件内容 (test.log)
text
2023-11-15 08:30:45,123 [main] INFO com.example.App - Application starting up
2023-11-15 08:30:45,456 [main] DEBUG com.example.Config - Loading configuration from /etc/app/config.yaml
2023-11-15 08:30:46,789 [pool-1-thread-2] WARN com.example.DB - Connection pool is 80% full
2023-11-15 08:30:47,012 [pool-1-thread-5] ERROR com.example.DB - Failed to connect to database: Connection refused
2023-11-15 08:30:47,345 [main] INFO com.example.App - Initialization completed in 2345 ms
2023-11-15 08:31:00,000 [scheduler-1] INFO com.example.Jobs - Starting scheduled job 'data-sync'
2023-11-15 08:31:02,334 [scheduler-1] ERROR com.example.Jobs - Job 'data-sync' failed: java.io.FileNotFoundException: /data/input.json (No such file or directory)
2023-11-15 08:31:05,678 [pool-2-thread-1] DEBUG com.example.Network - GET https://api.example.com/v1/status => 200 (356ms)
2023-11-15 08:31:10,123 [pool-2-thread-3] WARN com.example.Network - Retrying request (attempt 2/3)
2023-11-15 08:31:15,456 [main] FATAL com.example.App - Critical system failure: Disk full
2023-11-15 08:31:15,457 [main] INFO com.example.App - Initiating emergency shutdown
2023-11-15 08:31:15,789 [shutdown-hook] DEBUG com.example.App - Releasing resources
2023-11-15 08:31:16,000 [shutdown-hook] INFO com.example.App - Shutdown completed
[15/Nov/2023:09:45:12 +0800] "GET /api/users HTTP/1.1" 200 3452 12.345
[15/Nov/2023:09:45:13 +0800] "POST /api/login HTTP/1.1" 401 123 1.234
[15/Nov/2023:09:45:15 +0800] "GET /static/css/main.css HTTP/1.1" 304 - 0.456
ERROR: 2023-11-15T10:00:00Z | server-01 | DiskUtilization | /var is 95% full
WARNING: 2023-11-15T10:00:05Z | server-01 | Memory | Available memory below threshold: 512MB
15/11/2023 11:11:11 AM [SYSTEM] Backup started
15/11/2023 11:11:35 AM [SYSTEM] WARNING: Could not lock database during backup
15/11/2023 11:12:00 AM [SYSTEM] Backup completed with warnings
2023-11-15 12:00:00 [PID:1234] [TRACE] Entering function calculateRiskScore
2023-11-15 12:00:01 [PID:1234] [DEBUG] Risk parameters: {threshold=0.75, weight=1.2}
2023-11-15 12:00:03 [PID:1234] [INFO] Calculated risk score: 0.82
功能特点
-
多标签支持:使用QTabWidget同时打开多个日志文件,每个标签页独立显示和过滤
-
图表分析:使用Qt Charts模块显示日志级别统计柱状图
-
高级搜索:
-
支持正则表达式搜索
-
区分大小写选项
-
按日志级别过滤
-
按时间范围过滤
-
-
日志高亮:不同级别日志显示不同颜色
-
ERROR/FATAL: 红色
-
WARN: 橙色
-
INFO: 蓝色
-
DEBUG: 灰色
-
-
自动刷新:监控文件变化并自动重新加载
-
数据导出:支持导出为CSV格式
-
上下文菜单:右键菜单提供复制和导出功能
扩展建议
-
日志解析器插件:支持不同格式的日志文件
-
书签功能:标记重要日志行
-
自定义高亮规则:允许用户配置高亮颜色和条件
-
远程日志支持:通过网络获取日志文件
-
日志合并:合并多个日志文件并按时间排序
这个实现提供了完整的日志分析工具功能,后续可根据实际需求进一步定制和扩展。