From c4657b9c1d4cd9f431ba6c905f6aae284826afc8 Mon Sep 17 00:00:00 2001 From: pintu545 <282360370@qq.com> Date: Sat, 1 Aug 2026 15:44:31 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=9001-basic=E6=89=80?= =?UTF-8?q?=E6=9C=89=E4=BB=A3=E7=A0=81=E5=92=8C=E9=97=AE=E7=AD=94=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/01-basic/report.md | 32 ++++++++++++++++ src/LogParser/Models/LogEntries.cs | 4 +- src/LogParser/Parser/LineParser.cs | 46 ++++++++++++++++++++--- src/LogParser/Visitors/KeyValueVisitor.cs | 24 +++++++++++- 4 files changed, 96 insertions(+), 10 deletions(-) create mode 100644 docs/01-basic/report.md diff --git a/docs/01-basic/report.md b/docs/01-basic/report.md new file mode 100644 index 0000000..c0a28f5 --- /dev/null +++ b/docs/01-basic/report.md @@ -0,0 +1,32 @@ +Q1.1: + +1.using var csv = new CsvReader(logFile, config); + +​ csv.GetRecords() + +通过在LogFileParser.cs中定义一个继承自ClassMap的映射类LogRecordMap实现的。通过Map(m => m.LineNo).Index(0);之类,将第0列设置为行号,将第3列设置为Message。 + +2.var root = JsonDocument.Parse(logRecord.Message).RootElement; + +root.TryGetProperty("event", out var eventElement) + +ventElement.GetString() switch { "call" => ..., "request" => ..., "internal" => ... } + +3.调用了 System.Text.Json 库中的 JsonSerializer.Deserialize(string, JsonSerializerOptions) + +给每个属性加上了property: JsonRequired特性,如果缺失特性,会抛出异常 + +定义了JsonSerializerOptions的options静态对象,设置了PropertyNamingPolicy = JsonNamingPolicy.KebabCaseLower + +Q1.2: + +Dictionary KeyValueVisitor.Dump(LogEntry entry) + +TResult CallLogEntry.Accept(ILogEntryVisitor visitor) + +Dictionary KeyValueVisitor.Visit(CallLogEntry entry) + +Q1.3: + +提示词:给我整个代码的类的结构图,与程序执行的流程图。AI的解答比我更加迅速,并且能够给我提供相对应的知识点,便于更好更快的理解代码。 + diff --git a/src/LogParser/Models/LogEntries.cs b/src/LogParser/Models/LogEntries.cs index 69edbc0..e4e9bbc 100644 --- a/src/LogParser/Models/LogEntries.cs +++ b/src/LogParser/Models/LogEntries.cs @@ -54,7 +54,7 @@ public sealed record RequestLogEntry( { public override TResult Accept(ILogEntryVisitor visitor) { - throw new NotImplementedException("TODO: T1.2"); + return visitor.Visit(this); } } @@ -69,7 +69,7 @@ public sealed record InternalLogEntry( { public override TResult Accept(ILogEntryVisitor visitor) { - throw new NotImplementedException("TODO: T1.2"); + return visitor.Visit(this); } } diff --git a/src/LogParser/Parser/LineParser.cs b/src/LogParser/Parser/LineParser.cs index 0475f6b..1b57491 100644 --- a/src/LogParser/Parser/LineParser.cs +++ b/src/LogParser/Parser/LineParser.cs @@ -16,8 +16,8 @@ public static LogEntry ParseLine(LogRecord logRecord) return eventElement.GetString() switch { "call" => LineParser.CreateCall(logRecord), - "request" => throw new NotImplementedException("TODO: T1.2"), - "internal" => throw new NotImplementedException("TODO: T1.2"), + "request" => LineParser.CreateRequest(logRecord), + "internal" => LineParser.CreateInternal(logRecord), _ => throw new FormatException($"Unknown event type: {eventElement.GetString()} in log message: {logRecord.Message}") }; } @@ -50,12 +50,41 @@ private static LogEntry CreateCall(LogRecord logRecord) private static LogEntry CreateRequest(LogRecord logRecord) { - throw new NotImplementedException("TODO: T1.2"); + + var requestMessage = JsonSerializer.Deserialize(logRecord.Message, options) + ?? throw new FormatException($"Failed to deserialize request message: {logRecord.Message}"); + return new RequestLogEntry( + LineNo: logRecord.LineNo, + Timestamp: DateTimeOffset.Parse(logRecord.Timestamp), + PodName: logRecord.PodName, + Severity: ParseSeverity(requestMessage.Severity), + RequestId: requestMessage.RequestId, + Method: requestMessage.Method, + Path: requestMessage.Path, + StatusCode: requestMessage.StatusCode + ); } private static LogEntry CreateInternal(LogRecord logRecord) { - throw new NotImplementedException("TODO: T1.2"); + var internalMessage = JsonSerializer.Deserialize(logRecord.Message, options) + ?? throw new FormatException($"Failed to deserialize internal message: {logRecord.Message}"); + var exception = internalMessage.Exception; + var idx = exception.IndexOf(": "); + if (idx == -1) + { + throw new FormatException($"Invalid exception format: {exception}"); + } + var exceptionName = exception.Substring(0, idx); + var exceptionMessage = exception.Substring(idx + 2); + return new InternalLogEntry( + LineNo: logRecord.LineNo, + Timestamp: DateTimeOffset.Parse(logRecord.Timestamp), + PodName: logRecord.PodName, + Severity: ParseSeverity(internalMessage.Severity), + ExceptionName: exceptionName, + ExceptionMessage: exceptionMessage + ); } private static LogSeverity ParseSeverity(string severity) @@ -77,11 +106,16 @@ private record CallMessage( ); private record RequestMessage( - // TODO: T1.2 + [property: JsonRequired] string Severity, + [property: JsonRequired] string RequestId, + [property: JsonRequired] string Method, + [property: JsonRequired] string Path, + [property: JsonRequired] int StatusCode ); private record InternalMessage( - // TODO: T1.2 + [property: JsonRequired] string Severity, + [property: JsonRequired] string Exception ); } } diff --git a/src/LogParser/Visitors/KeyValueVisitor.cs b/src/LogParser/Visitors/KeyValueVisitor.cs index e5ceba2..c4ee145 100644 --- a/src/LogParser/Visitors/KeyValueVisitor.cs +++ b/src/LogParser/Visitors/KeyValueVisitor.cs @@ -26,12 +26,32 @@ public Dictionary Visit(CallLogEntry entry) public Dictionary Visit(RequestLogEntry entry) { - throw new NotImplementedException("TODO: T1.3"); + return new Dictionary + { + ["LineNo"] = entry.LineNo.ToString(), + ["Timestamp"] = entry.Timestamp.ToString("O"), + ["PodName"] = entry.PodName, + ["Severity"] = entry.Severity.ToString(), + ["EventType"] = entry.EventType.ToString(), + ["RequestId"] = entry.RequestId, + ["Method"] = entry.Method, + ["Path"] = entry.Path, + ["StatusCode"] = entry.StatusCode.ToString() + }; } public Dictionary Visit(InternalLogEntry entry) { - throw new NotImplementedException("TODO: T1.3"); + return new Dictionary + { + ["LineNo"] = entry.LineNo.ToString(), + ["Timestamp"] = entry.Timestamp.ToString("O"), + ["PodName"] = entry.PodName, + ["Severity"] = entry.Severity.ToString(), + ["EventType"] = entry.EventType.ToString(), + ["ExceptionName"] = entry.ExceptionName, + ["ExceptionMessage"] = entry.ExceptionMessage + }; } } } From fb8f35d2561553a957e70c883eb9deb05db06e31 Mon Sep 17 00:00:00 2001 From: pintu545 <282360370@qq.com> Date: Mon, 3 Aug 2026 20:25:06 +0800 Subject: [PATCH 2/5] homework2 --- docs/02-multithreading/report.md | 25 ++++++++ src/LocalCli/Program.cs | 92 ++++++++++++++++++++++++++++-- src/LogAnalyzer/LogFileAnalyzer.cs | 90 +++++++++++++++++------------ src/LogAnalyzer/WorkQueue.cs | 39 ++++++++++++- 4 files changed, 202 insertions(+), 44 deletions(-) create mode 100644 docs/02-multithreading/report.md diff --git a/docs/02-multithreading/report.md b/docs/02-multithreading/report.md new file mode 100644 index 0000000..cc2f585 --- /dev/null +++ b/docs/02-multithreading/report.md @@ -0,0 +1,25 @@ +Q2.1: + +*共享变量有_items,和 _isCompleted,保护机制,通过lock( _items)来保护临界区,同时使用Monitor.Wait和Monitor.PulseAll来实现条件等待和唤醒处理。 + +*共享变量有 _ isAnalyzing , _currentDirectory , _logFiles , _analysisResults。保护机制,通过lock( _syncRoot)进行保护。 + +*后果:if条件不会重新检查队列状态,线程会继续执行,如果队列继续为空,就会抛出队列空的报错,造成崩溃。 + +Q2.2: + +**var logFiles = Directory.EnumerateFiles(directoryPath, "*.log", SearchOption.TopDirectoryOnly) + .Select(filePath => Path.GetFileName(filePath)) + .OrderBy(fileName => fileName); + +*只需要将 Directory.EnumerateFiles 的第三个参数由SearchOption.TopDirectoryOnly 修改为 SearchOption.AllDirectories 即可 + +Q2.3: + +*提供给我类的关系和接口,提供给我所需函数的实现形式和出现位置,讲解pulseall和wait + +*帮我讲解代码框架 + +*在生成workmain中的 AnalysisResult构造时,最初直接将parser.Parse(reader) 的返回值(IEnumerable)传给了需要 IReadOnlyList的构造函数,导致类型不匹配报错。最终加了.ToList()解决 + +*偏高 \ No newline at end of file diff --git a/src/LocalCli/Program.cs b/src/LocalCli/Program.cs index 17b30db..191d5e4 100644 --- a/src/LocalCli/Program.cs +++ b/src/LocalCli/Program.cs @@ -112,22 +112,106 @@ 6. Exit. private static void ShowLogFiles(LogFileAnalyzer analyzer) { - throw new NotImplementedException("T2.3"); + var files = analyzer.GetLogFiles(); + if (files.Count == 0) + { + Console.WriteLine("No log files found in the current directory."); + return; + } + + Console.WriteLine("Log files in current directory:"); + foreach (var file in files) + { + Console.WriteLine($" - {file}"); + } } private static void AnalyzeFiles(LogFileAnalyzer analyzer) { - throw new NotImplementedException("T2.3"); + Console.WriteLine("Please input file names (separated by comma):"); + Console.Write(">>> "); + var input = Console.ReadLine(); + if (string.IsNullOrWhiteSpace(input)) + { + Console.WriteLine("Input cannot be empty."); + return; + } + var fileNames = input.Split(',') + .Select(f => f.Trim()) + .Where(f => !string.IsNullOrEmpty(f)) + .ToList(); + + if (fileNames.Count == 0) + { + Console.WriteLine("No valid file names provided."); + return; + } + + try + { + analyzer.AnalyzeFiles(0, fileNames); + Console.WriteLine("Analysis completed successfully."); + } + catch (ArgumentException ex) + { + Console.WriteLine($"Error: {ex.Message}"); + } + catch (InvalidOperationException ex) + { + Console.WriteLine($"Error: {ex.Message}"); + } + catch (Exception ex) + { + Console.WriteLine($"An error occurred during analysis: {ex.Message}"); + } } private static void AnalyzeAll(LogFileAnalyzer analyzer) { - throw new NotImplementedException("T2.3"); + try + { + analyzer.AnalyzeAll(0); + Console.WriteLine("All log files analyzed successfully."); + } + catch (Exception ex) + { + Console.WriteLine($"An error occurred during analysis: {ex.Message}"); + } } private static void GetAnalysisResult(LogFileAnalyzer analyzer) { - throw new NotImplementedException("T2.3"); + Console.WriteLine("Please input log file name:"); + Console.Write(">>> "); + var fileName = Console.ReadLine()?.Trim(); + if (string.IsNullOrEmpty(fileName)) + { + Console.WriteLine("File name cannot be empty."); + return; + } + + if (!analyzer.TryGetAnalysisResult(fileName, out var result) || result is null) + { + Console.WriteLine($"File '{fileName}' does not exist or is not in the directory."); + return; + } + + switch (result.State) + { + case AnalysisState.NotAnalyzed: + Console.WriteLine($"File '{fileName}' has NOT been analyzed yet. Please run analysis first."); + break; + + case AnalysisState.Succeeded: + Console.WriteLine($"Analysis result for '{fileName}' (Worker #{result.WorkerId}):"); + KeyValueVisitor.Dump(result.Entries); + break; + + case AnalysisState.Failed: + Console.WriteLine($"Analysis for '{fileName}' FAILED!"); + Console.WriteLine($"Error message: {result.ErrorMessage}"); + break; + } } } } diff --git a/src/LogAnalyzer/LogFileAnalyzer.cs b/src/LogAnalyzer/LogFileAnalyzer.cs index c3e7691..f6c8863 100644 --- a/src/LogAnalyzer/LogFileAnalyzer.cs +++ b/src/LogAnalyzer/LogFileAnalyzer.cs @@ -138,10 +138,7 @@ public void AnalyzeFiles(int degreeOfParallelism, IEnumerable fileNames) } fileList = fileNameList.Select(fileName => _logFiles[fileName]).ToList(); - /* - * Set _isAnalyzing - */ - // TODO: T2.2 + _isAnalyzing = true; } try @@ -150,11 +147,10 @@ public void AnalyzeFiles(int degreeOfParallelism, IEnumerable fileNames) } finally { - /* - * Unset _isAnalyzing - * Remember to lock _syncRoot to prevent data race - */ - // TODO: T2.2 + lock (_syncRoot) + { + _isAnalyzing = false; + } } } @@ -165,11 +161,14 @@ private void RunWorkers(int degreeOfParallelism, IReadOnlyList fileLis { foreach (var file in fileList) { - /* - * Filter unparsed files. - * If there is an unknown file, throw System.InvalidOperationException. - */ - throw new NotImplementedException("TODO: T2.2"); + if (!_analysisResults.TryGetValue(file.Name, out var result)) + { + throw new InvalidOperationException($"Unknown log file: {file.Name}"); + } + if (result.State == AnalysisState.NotAnalyzed) + { + logFilesToParse.Add(file); + } } } @@ -180,10 +179,11 @@ private void RunWorkers(int degreeOfParallelism, IReadOnlyList fileLis var queue = new WorkQueue(); - /* - * Enqueue log files - */ - // TODO: T2.2 + foreach (var file in logFilesToParse) + { + queue.Enqueue(file); + } + queue.CompleteAdding(); degreeOfParallelism = Math.Max(Math.Min(degreeOfParallelism, logFilesToParse.Count), 1); var workers = new Thread[degreeOfParallelism]; @@ -191,16 +191,16 @@ private void RunWorkers(int degreeOfParallelism, IReadOnlyList fileLis { int workerId = i; string threadName = $"log-analyzer-worker-{workerId}"; - /* - * Create and start threads to run `WorkerMain` - */ - // TODO: T2.2 + workers[i] = new Thread(() => WorkerMain(workerId, queue)) + { + Name = threadName + }; + workers[i].Start(); + } + foreach (var worker in workers) + { + worker.Join(); } - - /* - * Wait for (join) all threads to end - */ - // TODO: T2.2 } private void WorkerMain(int workerId, WorkQueue queue) @@ -212,21 +212,37 @@ private void WorkerMain(int workerId, WorkQueue queue) AnalysisResult result; try { - // Parse file - throw new NotImplementedException("TODO: T2.2"); + + using var streamReader = new StreamReader(file.FullName); + var entries = parser.Parse(streamReader); + + result = new AnalysisResult( + FileName: file.Name, + FullName: file.FullName, + State: AnalysisState.Succeeded, + Entries: entries.ToList(), + ErrorMessage: null, + WorkerId: workerId + ); } catch (Exception ex) { - // Save exception message to result - throw new NotImplementedException("TODO: T2.2"); + result = new AnalysisResult( + FileName: file.Name, + FullName: file.FullName, + State: AnalysisState.Failed, + Entries: Array.Empty(), + ErrorMessage: ex.Message, + WorkerId: workerId + ); } - /* - * Save parse result. - * [!Important] Remember to lock _syncRoot to prevent data race. - */ - throw new NotImplementedException("TODO: T2.2"); + + lock (_syncRoot) + { + _analysisResults[file.Name] = result; + } } } } -} +} \ No newline at end of file diff --git a/src/LogAnalyzer/WorkQueue.cs b/src/LogAnalyzer/WorkQueue.cs index 23055a5..22c2dfa 100644 --- a/src/LogAnalyzer/WorkQueue.cs +++ b/src/LogAnalyzer/WorkQueue.cs @@ -1,4 +1,8 @@ using System.Diagnostics.CodeAnalysis; +using System; +using System.Collections.Generic; +using System.Threading; + namespace LogAnalyzer { @@ -20,17 +24,46 @@ public bool IsCompleted public void Enqueue(T item) { - throw new NotImplementedException("TODO: T2.1"); + lock(_items) + { + if(_isCompleted) + { + throw new InvalidOperationException("Adding has been completed."); + } + _items.Enqueue(item); + Monitor.PulseAll(_items); + } } public bool TryDequeue([NotNullWhen(true)] out T? item) { - throw new NotImplementedException("TODO: T2.1"); + lock (_items) + { + while (_items.Count== 0 &&! _isCompleted) + { + Monitor.Wait(_items); + } + if( _items.Count >0) + { + item=_items.Dequeue(); + return true; + } + item = default; + return false; + } } public void CompleteAdding() { - throw new NotImplementedException("TODO: T2.1"); + lock (_items) + { + if (_isCompleted) + { + return; + } + _isCompleted = true; + Monitor.PulseAll(_items); + } } } } From b04a82b8074bb499395aa34ca78e8986089558e2 Mon Sep 17 00:00:00 2001 From: pintu545 <282360370@qq.com> Date: Tue, 4 Aug 2026 11:22:46 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=E4=BD=9C=E4=B8=9A2=E6=9B=B4=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/LogAnalyzer/LogFileAnalyzer.cs | 2 +- src/LogAnalyzer/WorkQueue.cs | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/LogAnalyzer/LogFileAnalyzer.cs b/src/LogAnalyzer/LogFileAnalyzer.cs index f6c8863..2a346f6 100644 --- a/src/LogAnalyzer/LogFileAnalyzer.cs +++ b/src/LogAnalyzer/LogFileAnalyzer.cs @@ -179,12 +179,12 @@ private void RunWorkers(int degreeOfParallelism, IReadOnlyList fileLis var queue = new WorkQueue(); + // 1. 先将任务入队 foreach (var file in logFilesToParse) { queue.Enqueue(file); } queue.CompleteAdding(); - degreeOfParallelism = Math.Max(Math.Min(degreeOfParallelism, logFilesToParse.Count), 1); var workers = new Thread[degreeOfParallelism]; for (int i = 0; i < degreeOfParallelism; i++) diff --git a/src/LogAnalyzer/WorkQueue.cs b/src/LogAnalyzer/WorkQueue.cs index 22c2dfa..8b334f2 100644 --- a/src/LogAnalyzer/WorkQueue.cs +++ b/src/LogAnalyzer/WorkQueue.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Threading; - namespace LogAnalyzer { public class WorkQueue @@ -24,9 +23,9 @@ public bool IsCompleted public void Enqueue(T item) { - lock(_items) + lock (_items) { - if(_isCompleted) + if (_isCompleted) { throw new InvalidOperationException("Adding has been completed."); } @@ -37,17 +36,19 @@ public void Enqueue(T item) public bool TryDequeue([NotNullWhen(true)] out T? item) { - lock (_items) + lock (_items) { - while (_items.Count== 0 &&! _isCompleted) - { + while (_items.Count == 0 && !_isCompleted) + { Monitor.Wait(_items); } - if( _items.Count >0) + + if (_items.Count > 0) { - item=_items.Dequeue(); + item = _items.Dequeue()!; return true; } + item = default; return false; } @@ -66,4 +67,4 @@ public void CompleteAdding() } } } -} +} \ No newline at end of file From 9a4fac2cc500e246bd94b69b604189f27b387c56 Mon Sep 17 00:00:00 2001 From: pintu545 <282360370@qq.com> Date: Tue, 4 Aug 2026 11:54:01 +0800 Subject: [PATCH 4/5] homework/02-multithreading --- src/LogAnalyzer/LogFileAnalyzer.cs | 19 +++++++++++-------- src/LogAnalyzer/WorkQueue.cs | 4 ++-- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/LogAnalyzer/LogFileAnalyzer.cs b/src/LogAnalyzer/LogFileAnalyzer.cs index 2a346f6..56fab08 100644 --- a/src/LogAnalyzer/LogFileAnalyzer.cs +++ b/src/LogAnalyzer/LogFileAnalyzer.cs @@ -1,7 +1,10 @@ -using LogParser.Models; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using LogParser.Models; using LogParser.Parser; -using System.Diagnostics.CodeAnalysis; -using System.Security.Cryptography.X509Certificates; namespace LogAnalyzer { @@ -15,6 +18,7 @@ public class LogFileAnalyzer public string? CurrentDirectory => _currentDirectory; public bool HasDirectory => _currentDirectory is not null; + public bool IsAnalyzing { get @@ -67,7 +71,7 @@ public bool ChangeDirectory(string? directoryPath) .OrderBy(fileName => fileName); foreach (var fileName in logFiles) { - _logFiles.Add(fileName, new FileInfo(Path.Join(_currentDirectory, fileName))); + _logFiles.Add(fileName, new FileInfo(Path.Combine(directoryPath, fileName))); _analysisResults.Add(fileName, new AnalysisResult( FileName: fileName, FullName: _logFiles[fileName].FullName, @@ -179,12 +183,12 @@ private void RunWorkers(int degreeOfParallelism, IReadOnlyList fileLis var queue = new WorkQueue(); - // 1. 先将任务入队 foreach (var file in logFilesToParse) { queue.Enqueue(file); } queue.CompleteAdding(); + degreeOfParallelism = Math.Max(Math.Min(degreeOfParallelism, logFilesToParse.Count), 1); var workers = new Thread[degreeOfParallelism]; for (int i = 0; i < degreeOfParallelism; i++) @@ -193,7 +197,8 @@ private void RunWorkers(int degreeOfParallelism, IReadOnlyList fileLis string threadName = $"log-analyzer-worker-{workerId}"; workers[i] = new Thread(() => WorkerMain(workerId, queue)) { - Name = threadName + Name = threadName, + IsBackground = true }; workers[i].Start(); } @@ -212,7 +217,6 @@ private void WorkerMain(int workerId, WorkQueue queue) AnalysisResult result; try { - using var streamReader = new StreamReader(file.FullName); var entries = parser.Parse(streamReader); @@ -237,7 +241,6 @@ private void WorkerMain(int workerId, WorkQueue queue) ); } - lock (_syncRoot) { _analysisResults[file.Name] = result; diff --git a/src/LogAnalyzer/WorkQueue.cs b/src/LogAnalyzer/WorkQueue.cs index 8b334f2..1164d1a 100644 --- a/src/LogAnalyzer/WorkQueue.cs +++ b/src/LogAnalyzer/WorkQueue.cs @@ -1,6 +1,6 @@ -using System.Diagnostics.CodeAnalysis; -using System; +using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Threading; namespace LogAnalyzer From 754dd2d3148b72957e33f755cfeb0173f4749174 Mon Sep 17 00:00:00 2001 From: pintu545 <282360370@qq.com> Date: Thu, 6 Aug 2026 18:48:44 +0800 Subject: [PATCH 5/5] homework/02-multithreading --- src/LocalCli/Program.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/LocalCli/Program.cs b/src/LocalCli/Program.cs index 191d5e4..403fde5 100644 --- a/src/LocalCli/Program.cs +++ b/src/LocalCli/Program.cs @@ -204,7 +204,11 @@ private static void GetAnalysisResult(LogFileAnalyzer analyzer) case AnalysisState.Succeeded: Console.WriteLine($"Analysis result for '{fileName}' (Worker #{result.WorkerId}):"); - KeyValueVisitor.Dump(result.Entries); + // Fix: Iterate over the entries collection + foreach (var entry in result.Entries) + { + KeyValueVisitor.Dump(entry); + } break; case AnalysisState.Failed: