feat: 动态筛选列系统 - 支持自定义字段#16
Conversation
- LogEntry 添加 extraFields 存储动态字段 - LogFormatTemplate 移除 KNOWN_FIELDS 限制,允许任意占位符 - LogTableModel 支持动态列 (setExtraColumns, ExtraFieldsRole) - LogFilterProxyModel 支持额外字段筛选 (setExtraFieldFilter) - LogLoader 解析时填充 extraFields,扩展 summaryReady 信号 - LogViewer 动态创建额外字段筛选 UI (rebuildExtraFieldUI) - LogExporter CSV/JSON/TXT 导出包含额外字段
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9d71f898cd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (!accepted.isEmpty() && accepted.size() < it.value().size()) { | ||
| proxyModel->setExtraFieldFilter(it.key(), accepted); |
There was a problem hiding this comment.
Fixed. Changed the condition from !accepted.isEmpty() && accepted.size() < it.value().size() to accepted.size() < it.value().size(), and updated setExtraFieldFilter to keep empty sets as valid filters (reject all rows) instead of removing the filter.
| if (!logs.isEmpty()) { | ||
| for (auto it = logs.first().extraFields.constBegin(); | ||
| it != logs.first().extraFields.constEnd(); ++it) { |
There was a problem hiding this comment.
Fixed. Now iterating all entries to collect unique extra field names using QSet, then sorting for consistent column order.
- analyzeLineStructure 现在会收集时间戳后所有方括号字段
- 区分日志等级字段和普通字段
- 普通方括号字段生成为 {field1}, {field2} 等占位符
- 保持日志等级和模块的检测逻辑
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 01dfd2dc53
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| : TS_BARE.match(logLines[0]); | ||
| if (firstTsMatch.hasMatch()) { | ||
| QString afterTs = logLines[0].mid(firstTsMatch.capturedEnd()).trimmed(); | ||
| QRegularExpression bracketRe(R"(^\s*\[([^\]]+)\])"); |
There was a problem hiding this comment.
This was already fixed in the current codebase. The bracketRe regex is R"(\[([^\]]+)\])" without the ^ anchor.
| sourceModel->setExtraColumns(extraFieldNames); | ||
| rebuildExtraFieldUI(extraFieldNames, extraFieldValues); |
There was a problem hiding this comment.
Fixed. Added sourceModel->setExtraColumns(QStringList()) and cleared extraFieldCheckBoxes at the start of loadLogFile(), before the background loader starts.
- 移除 bracketRe 中的 ^ 锚点,允许匹配后续方括号字段 - 编译时检测占位符是否在方括号内,使用 [^]]+ 替代 \S+ - 智能分析优先于预设匹配,检测到额外字段时直接使用
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9a41b6a4c8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // If smart analysis found extra fields, use it directly | ||
| if (!fmt.extraFieldNames().isEmpty()) { | ||
| return fmt; |
There was a problem hiding this comment.
Fixed. Removed the early return when smart analysis finds extra fields. Now presets are always compared first, and smart template is only used when it has extra fields AND matches equally well as the best preset.
- Fix empty extra-field filter: unchecking all checkboxes now correctly produces zero matches instead of being treated as unfiltered - Fix CSV headers: collect extra field names from all entries, not just the first one, to handle heterogeneous data correctly - Fix reset: clear dynamic columns and extra field UI at load start to prevent stale columns from previous file - Fix smart template: remove early return when extra fields found; presets are always compared first and preferred when matching equally well, so standard fields like module get proper names - bracketRe ^ anchor was already fixed in prior commit Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3d31fbc21f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for (auto it = entry.extraFields.constBegin(); | ||
| it != entry.extraFields.constEnd(); ++it) { | ||
| QString value = it.value(); | ||
| if (format == ExportConfig::CSV) { | ||
| value = escapeForCsv(value); | ||
| } | ||
| fields << value; | ||
| } |
There was a problem hiding this comment.
Emit CSV extra fields in fixed header order
The CSV header is built from the union of all extra-field names, but each row appends only the current entry’s extraFields map values. When some rows are missing one or more extra fields, those rows produce fewer columns and subsequent values shift under the wrong headers, which corrupts exported data alignment for downstream parsing.
Useful? React with 👍 / 👎.
| if (smartHasExtraFields && smartMatchCount >= bestPresetCount) { | ||
| return LogFormatTemplate(smartTemplate); |
There was a problem hiding this comment.
Keep preset template when smart match is only tied
This tie-break now selects the smart template whenever it has extra fields and matches as well as the best preset (>=). In standard formats that presets already model correctly, smart detection can still produce generic placeholders (e.g. field1, field2) and lose semantic fields like module, which then breaks module-based filtering and summaries even though a correct preset parse was available.
Useful? React with 👍 / 👎.
| if (parent.isValid()) | ||
| return 0; | ||
| return ColumnCount; | ||
| return ColumnCount + m_extraColumns.size(); |
There was a problem hiding this comment.
Preserve content-column highlighting after adding extra columns
Increasing table column count dynamically makes ColumnMessage no longer the last column, but HighlightDelegate::paint only highlights when index.column() == index.model()->columnCount() - 1. As a result, search highlighting stops working on message cells whenever extra fields are present, which is a user-visible regression in the main search workflow.
Useful? React with 👍 / 👎.
…ignment
The analyzeLineStructure function was too aggressive in classifying bare
words as separate fields (level, module). For logs with multi-space
alignment like "START M21 test_1/path", the generated template
"[{timestamp}] [{field1}] {level} {module} {message}" failed because
\S+ placeholders can't match through multiple spaces.
Now the function validates the detailed template against sample lines.
If the simpler base template "[{timestamp}] [{field1}] {message}"
matches significantly better, it falls back to that.
Also made analyzeLineStructure public for testability and added
test_format_detect.cpp to verify detection for predict, default, and
custom field log formats.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- 每个筛选GroupBox添加"全选"/"取消全选"按钮
- 移除自动检测中过于激进的module fallback,避免错误添加{module}
- 加载器跳过空的level/module值,防止空白筛选项
- 将level/module从额外列中分离,避免表格列和筛选框重复
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- CSV导出: formatLogEntry 接受 extraFieldNames 参数,缺失字段输出空列 - 模板检测: preset 匹配良好时优先使用,保留语义字段名(如 module) - 搜索高亮: HighlightDelegate 改用固定 ColumnMessage 列索引,不再依赖最后一列 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- logentry.h: 添加 rawLine、matched 字段 - logformattemplate.h: 添加 DetectInfo 结构和 detectWithInfo 声明 - 其他文件: 同步未提交的 feature 改动 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5a93d79274
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| LogFormatTemplate::DetectInfo detectInfo = | ||
| LogFormatTemplate::detectWithInfo(sampleLines); |
There was a problem hiding this comment.
Declare detect-with-info API before using it
LogLoader::process now relies on LogFormatTemplate::DetectInfo and LogFormatTemplate::detectWithInfo(...), but these symbols are not declared in src/core/logformattemplate.h in this commit. Any build that compiles logloader.cpp against that header fails immediately with unknown type/member errors, so the feature cannot ship in its current state.
Useful? React with 👍 / 👎.
| LogFormatTemplate::DetectInfo detectInfo = | ||
| LogFormatTemplate::detectWithInfo(sampleLines); | ||
| fmt = LogFormatTemplate(detectInfo.templateStr); | ||
| emit detectInfoReady(detectInfo.templateStr, detectInfo.reason); |
There was a problem hiding this comment.
Add emitted signal to LogLoader class interface
This code emits detectInfoReady(...), but LogLoader’s signal list in src/core/logloader.h does not define that signal in this commit. Qt’s meta-object compilation then cannot resolve the emit/connect usage, causing a compile-time break for the loader/viewer integration path.
Useful? React with 👍 / 👎.
| void LogViewer::loadLogFile(const QString& filePath, | ||
| const QString& formatTemplate) |
There was a problem hiding this comment.
Sync LogViewer declarations with new implementation
The implementation now uses a two-argument loadLogFile(...) overload and several new members/slots (formatModeCombo, onFormatModeChanged, updateTemplateInfo, fieldFiltersLayout, etc.), but src/ui/logviewer.h in this commit still declares the old interface. As written, the GUI target will not compile due to unmatched method definitions and missing class members.
Useful? React with 👍 / 👎.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: afc9366b0b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (tryMsFormat) { | ||
| entry.timestamp = QDateTime::fromString(tsStr, "yyyy-MM-dd HH:mm:ss.zzz"); | ||
| if (!entry.timestamp.isValid()) { | ||
| tryMsFormat = false; | ||
| entry.timestamp = QDateTime::fromString(tsStr, "yyyy-MM-dd HH:mm:ss"); | ||
| } | ||
| } else { | ||
| entry.timestamp = QDateTime::fromString(tsStr, "yyyy-MM-dd HH:mm:ss"); |
There was a problem hiding this comment.
Parse each timestamp with both supported formats
Switching tryMsFormat to false after the first non-millisecond timestamp makes all later yyyy-MM-dd HH:mm:ss.zzz lines parse as invalid in mixed-format logs. This silently drops valid timestamps from min/max computation and time filtering (because invalid timestamps bypass the range check), so files that contain both second-precision and millisecond-precision records are filtered/summarized incorrectly.
Useful? React with 👍 / 👎.
| int mode = AppSettings::instance().getFormatMode(); | ||
| if (mode > 0) { | ||
| // Preset mode: get preset template by index (1-based) | ||
| const auto presets = LogFormatTemplate::presets(); | ||
| int presetIdx = mode - 1; | ||
| if (presetIdx >= 0 && presetIdx < presets.size()) { | ||
| effectiveTemplate = presets[presetIdx].templateStr; | ||
| } | ||
| } | ||
| // mode == 0: auto-detect (pass empty) |
There was a problem hiding this comment.
Honor saved custom format mode when loading files
When formatMode is -1 (custom), this branch leaves effectiveTemplate empty and falls back to auto-detection, so opening a new file ignores the user’s saved custom template. The custom mode can only work on the immediate reload path that passes an explicit template, which breaks the expected behavior for subsequent loads (including after restart).
Useful? React with 👍 / 👎.
| if (!entry.matched) { | ||
| // Unmatched line: put rawLine in first column, rest empty | ||
| out << escapeForCsv(entry.rawLine) << "\n"; |
There was a problem hiding this comment.
Emit full-width CSV rows for unmatched entries
For unmatched lines, the exporter writes only rawLine and a newline even when the header includes multiple selected fields and extra columns. That produces variable-width CSV rows, which breaks downstream CSV parsing and column alignment whenever unmatched rows are present in the export.
Useful? React with 👍 / 👎.
- 时间戳解析: 每行都尝试两种格式,不再用 tryMsFormat 标志跳过 - 自定义格式: mode==-1 时从 AppSettings 读取自定义模板 - CSV导出: unmatched 行填充空列保持宽度一致 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ed0ce237c9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| fmt = LogFormatTemplate::detect(sampleLines); | ||
| LogFormatTemplate::DetectInfo detectInfo = | ||
| LogFormatTemplate::detectWithInfo(sampleLines); | ||
| fmt = LogFormatTemplate(detectInfo.templateStr); |
There was a problem hiding this comment.
Fall back to a valid template when auto-detect returns none
In auto-detect mode, detectWithInfo(...) can return an empty templateStr (for example on empty files or unsupported formats), but this code immediately constructs LogFormatTemplate(detectInfo.templateStr) and then aborts on invalid template. That turns previously loadable files into hard failures and prevents the unmatched-line path from ever running, so users cannot open those logs at all unless they manually choose a template.
Useful? React with 👍 / 👎.
| proxyModel->setLevels(selectedLevels); | ||
| proxyModel->setModules(selectedModules); |
There was a problem hiding this comment.
Keep empty level/module selections from disabling filtering
After adding per-field “deselect all”, an empty level or module selection is now passed through directly; because the proxy treats an empty set as “no constraint”, clicking deselect-all on level/module still shows all matched rows instead of none. This makes those controls ineffective and inconsistent with extra-field filters, where an empty selection does exclude all rows.
Useful? React with 👍 / 👎.
- 自动检测返回空模板时回退到默认模板,不再直接报错 - level/module 全部取消选中时正确排除所有行(用 active 标志区分"未筛选"和"全不选") Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 477aaa9045
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (savedMode >= 0 && savedMode < formatModeCombo->count() - 1) { | ||
| formatModeCombo->setCurrentIndex(savedMode); |
There was a problem hiding this comment.
Restore custom mode in combo when formatMode is -1
When a user has previously saved custom mode (formatMode == -1), this restore block skips setting the combo index, so the UI stays on “自动识别” even though loadLogFile() still applies the saved custom template (mode < 0 branch at src/ui/logviewer.cpp:492-495). After restart, users see Auto selected but files are parsed with the custom template, which is a misleading state that can silently produce unexpected parsing/filter results.
Useful? React with 👍 / 👎.
| void LogFilterProxyModel::setLevels(const QStringList& levels) | ||
| { | ||
| m_levelSet = QSet<QString>(levels.cbegin(), levels.cend()); | ||
| m_levelFilterActive = true; |
There was a problem hiding this comment.
Avoid activating level filter when no level options exist
This unconditionally enables the level filter even when levels is empty; combined with filterAcceptsRow (m_levelFilterActive && !m_levelSet.contains(level)), clicking Filter on logs that do not have a parsed level field filters out every matched row. The same pattern exists for modules, so custom formats without level/module become impossible to time-filter without collapsing to zero rows.
Useful? React with 👍 / 👎.
- formatMode==-1 时combo选中"自定义..."项 - 只在level/module checkbox存在时才激活对应筛选,避免无该字段的日志被清空 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Summary
{cycle},{state},{task})作为表格新列显示变更
LogEntry添加extraFields、rawLine、matched字段LogFormatTemplate移除KNOWN_FIELDS限制,允许任意占位符;新增DetectInfo和detectWithInfoAPILogTableModel支持动态列 (setExtraColumns,ExtraFieldsRole)LogFilterProxyModel支持额外字段筛选 (setExtraFieldFilter) 和隐藏未匹配行LogLoader解析时填充extraFields,扩展summaryReady信号,新增detectInfoReady信号LogViewer动态创建额外字段筛选 UI (rebuildFieldFilterUI)LogExporterCSV/JSON/TXT 导出包含额外字段HighlightDelegate搜索高亮支持动态列最新改进
{module}ColumnMessage列索引,不再依赖最后一列测试
🤖 Generated with Claude Code