Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/example_custom_fields.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[2025-01-15 08:00:01.123] [INFO] [C1 +0001] START TaskA modA : 初始化系统
[2025-01-15 08:00:02.456] [DEBUG] [C1 +0002] WAIT TaskB modB : 等待资源分配
[2025-01-15 08:00:03.789] [INFO] [C2 +0001] START TaskA modA : 开始处理请求
[2025-01-15 08:00:04.012] [WARN] [C1 +0003] EMIT TaskC modC : 发送警告信号
[2025-01-15 08:00:05.345] [ERROR] [C2 +0002] FAIL TaskB modB : 连接超时
[2025-01-15 08:00:06.678] [INFO] [C1 +0004] PASS TaskA modA : 测试通过
[2025-01-15 08:00:07.901] [DEBUG] [C3 +0001] READY TaskD modD : 就绪状态
[2025-01-15 08:00:08.234] [INFO] [C2 +0003] END TaskC modC : 任务完成
[2025-01-15 08:00:09.567] [ERROR] [C1 +0005] FAIL TaskB modB : 数据校验失败
[2025-01-15 08:00:10.890] [INFO] [C3 +0002] START TaskA modA : 重新初始化
Binary file added resources/icons/template.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions resources/resources.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<file>icons/filter.png</file>
<file>icons/toggle.png</file>
<file>icons/export.png</file>
<file>icons/template.png</file>
</qresource>

</RCC>
10 changes: 9 additions & 1 deletion src/core/logentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#define LOGENTRY_H

#include <QDateTime>
#include <QMap>
#include <QMetaType>
#include <QString>

Expand Down Expand Up @@ -75,10 +76,17 @@ struct LogEntry
*/
QString message;

QMap<QString, QString> extraFields;

QString rawLine; // 原始行文本(未匹配时用于显示)
bool matched = true; // 是否匹配模板

bool operator==(const LogEntry& other) const
{
return timestamp == other.timestamp && level == other.level &&
module == other.module && message == other.message;
module == other.module && message == other.message &&
extraFields == other.extraFields && rawLine == other.rawLine &&
matched == other.matched;
}
};

Expand Down
99 changes: 74 additions & 25 deletions src/core/logexporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QSet>
#include <QTextStream>
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#include <QTextCodec>
Expand Down Expand Up @@ -218,8 +219,12 @@ bool LogExporter::exportToTxt(const QList<LogEntry>& logs,
// Write each log entry as a formatted line
for (int i = 0; i < logs.size(); ++i) {
const LogEntry& entry = logs[i];
QString line = formatLogEntry(entry, config, ExportConfig::TXT);
out << line << "\n";
if (!entry.matched) {
out << entry.rawLine << "\n";
} else {
QString line = formatLogEntry(entry, config, ExportConfig::TXT);
out << line << "\n";
}

if (i % 1000 == 0 || i == logs.size() - 1)
emitProgress(i + 1, logs.size());
Expand Down Expand Up @@ -272,13 +277,34 @@ bool LogExporter::exportToCsv(const QList<LogEntry>& logs,
if (config.includeContent)
headers << QObject::tr("内容");

// Collect extra field names from all entries
QSet<QString> extraFieldNameSet;
for (const LogEntry& entry : logs) {
for (auto it = entry.extraFields.constBegin();
it != entry.extraFields.constEnd(); ++it) {
extraFieldNameSet.insert(it.key());
}
}
QStringList extraFieldNames(extraFieldNameSet.constBegin(), extraFieldNameSet.constEnd());
extraFieldNames.sort();
headers << extraFieldNames;

out << headers.join(",") << "\n";

// Write each log entry as a CSV row
int totalColumns = headers.size();
for (int i = 0; i < logs.size(); ++i) {
const LogEntry& entry = logs[i];
QString line = formatLogEntry(entry, config, ExportConfig::CSV);
out << line << "\n";
if (!entry.matched) {
// Unmatched line: rawLine in first column, rest empty
out << escapeForCsv(entry.rawLine);
for (int c = 1; c < totalColumns; ++c)
out << ",";
out << "\n";
} else {
QString line = formatLogEntry(entry, config, ExportConfig::CSV, extraFieldNames);
out << line << "\n";
}

if (i % 1000 == 0 || i == logs.size() - 1)
emitProgress(i + 1, logs.size());
Expand Down Expand Up @@ -337,26 +363,36 @@ bool LogExporter::exportToJson(const QList<LogEntry>& logs,
return json.mid(1, json.size() - 2); // strip [ and ]
};

bool first = true;
auto writeField = [&](const QString& key, const QString& value) {
if (!first)
out << ",\n";
first = false;
out << " " << jsonEscape(key) << ": " << jsonEscape(value);
};

if (config.includeTimestamp) {
writeField("timestamp",
entry.timestamp.toString("yyyy-MM-dd HH:mm:ss.zzz"));
}
if (config.includeLevel) {
writeField("level", entry.level);
}
if (config.includeModule) {
writeField("module", entry.module);
}
if (config.includeContent) {
writeField("content", entry.message);
if (!entry.matched) {
out << " " << jsonEscape("raw") << ": " << jsonEscape(entry.rawLine);
} else {
bool first = true;
auto writeField = [&](const QString& key, const QString& value) {
if (!first)
out << ",\n";
first = false;
out << " " << jsonEscape(key) << ": " << jsonEscape(value);
};

if (config.includeTimestamp) {
writeField("timestamp",
entry.timestamp.toString("yyyy-MM-dd HH:mm:ss.zzz"));
}
if (config.includeLevel) {
writeField("level", entry.level);
}
if (config.includeModule) {
writeField("module", entry.module);
}
if (config.includeContent) {
writeField("content", entry.message);
}

// Add extra fields
for (auto it = entry.extraFields.constBegin();
it != entry.extraFields.constEnd(); ++it) {
writeField(it.key(), it.value());
}
}

out << "\n }";
Expand Down Expand Up @@ -386,7 +422,8 @@ bool LogExporter::exportToJson(const QList<LogEntry>& logs,
*/
QString LogExporter::formatLogEntry(const LogEntry& entry,
const ExportConfig& config,
ExportConfig::Format format)
ExportConfig::Format format,
const QStringList& extraFieldNames)
{
QStringList fields;

Expand Down Expand Up @@ -422,6 +459,18 @@ QString LogExporter::formatLogEntry(const LogEntry& entry,
fields << content;
}

// Add extra fields (use known field names for consistent column count)
const QStringList& names = extraFieldNames.isEmpty()
? QStringList(entry.extraFields.keys())
: extraFieldNames;
for (const QString& name : names) {
QString value = entry.extraFields.value(name);
if (format == ExportConfig::CSV) {
value = escapeForCsv(value);
}
fields << value;
}

// Join fields with appropriate separator
if (format == ExportConfig::CSV) {
return fields.join(",");
Expand Down
3 changes: 2 additions & 1 deletion src/core/logexporter.h
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,8 @@ class LogExporter : public QObject
* settings.
*/
QString formatLogEntry(const LogEntry& entry, const ExportConfig& config,
ExportConfig::Format format);
ExportConfig::Format format,
const QStringList& extraFieldNames = QStringList());

/**
* @brief Escape special characters for CSV format
Expand Down
Loading
Loading