Skip to content

Commit e976b13

Browse files
Fase 2 voltooid: Configureerbare parser, async indexering en UI verbeteringen
- Geïmplementeerd: PatternLogParser met regex-based custom log formats - Toegevoegd: Async indexering voor grote bestanden (10GB+ support) - Verbeterd: Memory allocatie strategie (reserve based on fileSize/120) - Gerefactored: LogParser -> StandardLogParser + ILogParser interface - Toegevoegd: About dialog en parser configuratie UI - Verbeterd: Resource bundling voor macOS (achtergrond, fonts, icoon) - Vergroot: Standaard venstergrootte naar 1600x900 - Vernieuwd: App icoon met transparante achtergrond - Toegevoegd: vcpkg.json voor dependency management - Opgeschoond: Repository structuur en .gitignore
1 parent 93b933e commit e976b13

28 files changed

+827
-73
lines changed

.gitignore

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,35 @@
1-
# Build artifacts
1+
# Build Artifacts
2+
build/
23
*.o
4+
*.app
5+
*.dSYM
6+
7+
# Executables (if not in build/)
38
log_analyzer
49
log_analyzer_gui
10+
log_analyzer_gui_*
511
test_parser
612
test_analyzers
13+
test_catch2
14+
unit_tests
715

8-
# Output files
9-
out_report.txt
10-
test_output.txt
16+
# Runtime & Config
1117
*.log
18+
resources/MyIcon.iconset
19+
log_analyzer_config.ini
20+
imgui.ini
21+
22+
# Test Output
23+
test_output.txt
24+
tests/test_output.txt
25+
out_report.txt
26+
large_test.log
27+
large_report.txt
1228

13-
# IDE
29+
# OS & IDE
30+
.DS_Store
1431
.vscode/
1532
.idea/
1633
*.swp
1734
*.swo
1835
*~
19-
20-
# macOS
21-
.DS_Store
22-
23-
# External libraries (downloaded)
24-
external/imgui/
25-
26-
# Test artifacts
27-
large_test.log
28-
large_report.txt

CMakeLists.txt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ target_link_libraries(imgui PUBLIC glfw)
4343

4444
# --- Core Sources ---
4545
set(CORE_SOURCES
46-
core/LogParser.cpp
46+
core/StandardLogParser.cpp
47+
core/PatternLogParser.cpp
4748
core/Timestamp.cpp
4849
core/ConfigManager.cpp
4950
io/MemoryMappedFile.cpp
@@ -65,11 +66,19 @@ add_executable(log_analyzer
6566
)
6667

6768
# --- GUI Executable ---
68-
add_executable(log_analyzer_gui
69+
add_executable(log_analyzer_gui MACOSX_BUNDLE
6970
${CORE_SOURCES}
7071
gui/GuiController.cpp
7172
gui/LogViewer.cpp
7273
gui/main_gui.cpp
74+
resources/AppIcon.icns
75+
resources/background_calm.png
76+
resources/fa-solid-900.ttf
77+
)
78+
79+
set_target_properties(log_analyzer_gui PROPERTIES
80+
MACOSX_BUNDLE_ICON_FILE "AppIcon.icns"
81+
RESOURCE "resources/AppIcon.icns;resources/background_calm.png;resources/fa-solid-900.ttf"
7382
)
7483
# Link imgui (which links glfw), and macOS frameworks
7584
target_link_libraries(log_analyzer_gui PRIVATE imgui ${COCOA_LIB} ${IOKIT_LIB} ${COREVIDEO_LIB} OpenGL::GL)
@@ -81,6 +90,7 @@ add_executable(unit_tests
8190
${CORE_SOURCES}
8291
tests/test_parser_catch2.cpp
8392
tests/test_analyzers_catch2.cpp
93+
tests/test_pattern_parser.cpp
8494
tests/test_main_catch2.cpp
8595
external/catch2/catch_amalgamated.cpp
8696
)

analysis/AnalysisContext.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ struct AnalysisContext {
1010
std::optional<Timestamp> fromTs;
1111
std::optional<Timestamp> toTs;
1212
std::optional<std::string> keyword;
13+
std::string customPattern; // If non-empty, use PatternLogParser
1314
};
1415

1516
} // namespace loganalyzer

analysis/AnalysisResult.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,19 @@ void AnalysisResult::merge(const AnalysisResult &other) {
2020
levelCounts[level] += count;
2121
}
2222

23+
// Merge Heatmap
24+
for (size_t d = 0; d < 7; ++d) {
25+
for (size_t h = 0; h < 24; ++h) {
26+
heatmap[d][h] += other.heatmap[d][h];
27+
}
28+
}
29+
30+
// Merge Timeline
31+
// NOTE: Simple concatenation. Sorting should happen at the end of pipeline.
32+
// Ideally, specialized merging that buckets by minute would be better,
33+
// but for now, append and let the GUI or finalizer sort it.
34+
timeline.insert(timeline.end(), other.timeline.begin(), other.timeline.end());
35+
2336
// Merge topErrors
2437
// Strategy: Combine both vectors into a map to sum counts, then recreate
2538
// vector

analysis/AnalysisResult.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "../core/LogLevel.h"
44
#include "../core/ParseError.h"
5+
#include <array>
56
#include <cstdint>
67
#include <map>
78
#include <string>
@@ -24,6 +25,22 @@ struct AnalysisResult {
2425
// Top 10 ERROR messages: (message, count), deterministically sorted
2526
std::vector<std::pair<std::string, uint64_t>> topErrors;
2627

28+
// Timeline Data: Minute-by-minute error/warning counts
29+
// Storing simple counts per minute bucket (relative to start time or
30+
// absolute?) Let's store absolute timestamps (rounded to minute) -> count
31+
// Using vector of pairs for determinism and easy plotting
32+
struct TimelineBucket {
33+
uint64_t timestamp; // Unix timestamp for minute
34+
uint32_t errorCount;
35+
uint32_t warningCount;
36+
};
37+
std::vector<TimelineBucket> timeline;
38+
39+
// Heatmap Data: 7 Days x 24 Hours
40+
// heatmap[day_of_week][hour_of_day] = count
41+
// day 0 = Sunday, 1 = Monday ... 6 = Saturday
42+
std::array<std::array<uint32_t, 24>, 7> heatmap = {};
43+
2744
void merge(const AnalysisResult &other);
2845
};
2946

analysis/Pipeline.cpp

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "Pipeline.h"
2-
#include "../core/LogParser.h"
2+
#include "../core/PatternLogParser.h"
3+
#include "../core/StandardLogParser.h"
34
#include "../io/MemoryMappedFile.h"
45
#include "KeywordHitAnalyzer.h"
56
#include "LevelCountAnalyzer.h"
@@ -141,6 +142,18 @@ AnalysisResult Pipeline::run(const std::string &inputPath,
141142
const uint64_t progressReportInterval = 1024 * 1024; // 1MB
142143
uint64_t bytesSinceLastReport = 0;
143144

145+
// Thread-local timeline aggregator
146+
std::map<uint64_t, std::pair<uint32_t, uint32_t>>
147+
localTimeline; // Key -> {Error, Warning}
148+
149+
// Setup parser
150+
std::unique_ptr<ILogParser> parser;
151+
if (!context.customPattern.empty()) {
152+
parser = std::make_unique<PatternLogParser>(context.customPattern);
153+
} else {
154+
parser = std::make_unique<StandardLogParser>();
155+
}
156+
144157
while (currentPos < endOffset) {
145158
if (wasCancelled && *wasCancelled)
146159
return localResult;
@@ -161,7 +174,7 @@ AnalysisResult Pipeline::run(const std::string &inputPath,
161174
lineNumber++;
162175

163176
// Parse
164-
ParseResult parseResult = LogParser::parse(line, lineNumber);
177+
ParseResult parseResult = parser->parse(line, lineNumber);
165178

166179
if (std::holds_alternative<LogEntry>(parseResult)) {
167180
localResult.parsedLines++; // thread-local count
@@ -173,6 +186,52 @@ AnalysisResult Pipeline::run(const std::string &inputPath,
173186
for (auto &analyzer : analyzers) {
174187
analyzer->process(entry);
175188
}
189+
190+
// --- Populate Heatmap & Timeline ---
191+
if (entry.ts.month > 0) { // Valid check heuristic
192+
// Calculate day of week (0=Sunday)
193+
// Zeller's congruence or just std::tm if we reused it?
194+
// Optimization: We manually parsed ts, so we don't have tm
195+
// directly. Let's rely on a helper or basic calculation. For speed,
196+
// C++20 chrono is best but we are C++17 here (mostly). Let's do a
197+
// simple Zeller for Day of Week. Zeller algorithm (0=Saturday,
198+
// 1=Sunday.. for the math, adjusted to 0=Sun):
199+
int y = entry.ts.year;
200+
int m = entry.ts.month;
201+
int q = entry.ts.day;
202+
if (m < 3) {
203+
m += 12;
204+
y -= 1;
205+
}
206+
int K = y % 100;
207+
int J = y / 100;
208+
int h = (q + 13 * (m + 1) / 5 + K + K / 4 + J / 4 + 5 * J) % 7;
209+
// h is 0=Saturday, 1=Sunday...6=Friday
210+
// Map to 0=Sunday...6=Saturday
211+
int dayIdx = (h + 1) % 7; // Now 0=Sun, 1=Mon...6=Sat
212+
213+
int hourIdx = entry.ts.hour;
214+
if (dayIdx >= 0 && dayIdx < 7 && hourIdx >= 0 && hourIdx < 24) {
215+
localResult.heatmap[dayIdx][hourIdx]++;
216+
}
217+
218+
// Timeline: Bucket by minute
219+
// We need a monotonic timestamp.
220+
// Let's assume entry.ts can convert to unix time approx or we just
221+
// use the raw components. Let's use a simplified 64-bit sort key:
222+
// YYYYMMDDHHMM This is sufficient for sorting and buckets.
223+
uint64_t timeKey = (uint64_t)entry.ts.year * 100000000 +
224+
(uint64_t)entry.ts.month * 1000000 +
225+
(uint64_t)entry.ts.day * 10000 +
226+
(uint64_t)entry.ts.hour * 100 +
227+
(uint64_t)entry.ts.minute;
228+
229+
if (entry.level == LogLevel::ERROR) {
230+
localTimeline[timeKey].first++;
231+
} else if (entry.level == LogLevel::WARNING) {
232+
localTimeline[timeKey].second++;
233+
}
234+
}
176235
}
177236
} else {
178237
localResult.invalidLines++;
@@ -208,6 +267,13 @@ AnalysisResult Pipeline::run(const std::string &inputPath,
208267
for (auto &analyzer : analyzers) {
209268
analyzer->finalize(localResult);
210269
}
270+
271+
// Flatten timeline map to vector
272+
localResult.timeline.reserve(localTimeline.size());
273+
for (const auto &[timeKey, counts] : localTimeline) {
274+
localResult.timeline.push_back({timeKey, counts.first, counts.second});
275+
}
276+
211277
return localResult;
212278
};
213279

app/AppRequest.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ struct AppRequest {
1111
std::optional<Timestamp> fromTimestamp;
1212
std::optional<Timestamp> toTimestamp;
1313
std::optional<std::string> keyword;
14+
std::string customPattern;
1415
};
1516

1617
} // namespace loganalyzer

app/Application.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ void Application::runHeadless(const AppRequest &request, AppResult &result,
3838
context.fromTs = request.fromTimestamp;
3939
context.toTs = request.toTimestamp;
4040
context.keyword = request.keyword;
41+
context.customPattern = request.customPattern;
4142

4243
// Run pipeline with progress callback
4344
try {

core/ILogParser.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#pragma once
2+
3+
#include "ParseResult.h"
4+
#include <string_view>
5+
6+
namespace loganalyzer {
7+
8+
class ILogParser {
9+
public:
10+
virtual ~ILogParser() = default;
11+
12+
// Thread-safe parse method
13+
virtual ParseResult parse(std::string_view line, size_t lineNumber) const = 0;
14+
};
15+
16+
} // namespace loganalyzer

0 commit comments

Comments
 (0)