Skip to content
Open
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
60 changes: 60 additions & 0 deletions docs/01-basic/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
## (Q1.1)

### 1

在LogParser\Parser\LogFileParser.cs中

```
using var csv = new CsvReader(logFile, config);
csv.Context.RegisterClassMap<LogRecordMap>();

foreach (var logRecord in csv.GetRecords<LogRecord>())

```

csv.GetRecords按照CSV规则解析各行。而每一列对应什么含义,也是在 LogFileParser 中通过 Index 指定的:

```
Map(m => m.LineNo).Index(0);
Map(m => m.Timestamp).Index(1);
Map(m => m.PodName).Index(2);
Map(m => m.Message).Index(3);

```

### 2

```

using (var doc = JsonDocument.Parse(logRecord.Message))
{
var root = doc.RootElement;
if (root.TryGetProperty("event", out var eventElement))

```

根据 eventElement 来判断类型

### 3

使用JsonSerializer.Deserialize。

同时设置

private static JsonSerializerOptions options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.KebabCaseLower,
};

调用时将options传入,完成命名法转换

## (Q1.2)

+ Dictionary<string, string> KeyValueVisitor.Dump(LogEntry entry)
+ Dictionary<string, string> CallLogEntry.Accept<Dictionary<string, string>>(visitor)
+ Dictionary<string, string> KeyValueVisitor.Visit(CallLogEntry entry)

## (Q1.3)

没有使用AI,时间花了大概三小时。比程设作业难。我认为我完成作业只是模仿示例完成了代码,还没有完全看懂整个架构。

Binary file added docs/02-multithreading/assets/localcli-normal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
55 changes: 55 additions & 0 deletions docs/02-multithreading/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# T2.3 LocalCli 实现报告

## 实现功能

- 输入日志目录并创建分析器,也可以在运行期间切换目录。
- 查看当前目录中的全部日志文件。
- 设置并行度,分析指定的一个或多个日志文件。
- 设置并行度,分析当前目录中的全部日志文件。
- 查询日志文件的分析状态,并区分未分析、分析成功、分析失败和文件不存在四种情况。
- 使用 `KeyValueVisitor.Dump` 输出成功解析的完整日志内容,并显示解析失败时的错误信息。
- 校验目录、并行度和文件名等输入,捕获分析过程中产生的异常,避免程序因非法输入退出。

![LocalCli 完整功能截图](./assets/localcli-normal.png)

## 鲁棒性测试截图

![LocalCli 鲁棒性测试截图](./assets/localcli-robustness.png)

## Q2.1

1. 共享变量是队列 `_items` 和完成标志 `_isCompleted`;统一用 `lock (_items)` 保护,配合 `Wait/Pulse/PulseAll` 协调生产和消费。

2. `LogFileAnalyzer` 的目录、分析状态及两个字典由 `_syncRoot` 加锁保护;工作线程在锁外解析文件,完成后再加锁写入 `_analysisResults`。

3. 使用 `if` 遇到虚假唤醒会在空队列取值,导致异常或消费者提前退出;使用 `while` 可在每次唤醒后重新检查条件,确保队列非空或生产已结束。

## Q2.2

扫描代码是 `Directory.EnumerateFiles(directoryPath, "*.log", SearchOption.TopDirectoryOnly)`;递归时改用 `AllDirectories`,并以相对路径或完整路径作键,避免子目录同名文件冲突。

## Q2.3

使用AI,给AI的提示词之一为:

```

public bool TryDequeue([NotNullWhen(true)] out T? item){
lock (_items){
while (_items.Count == 0 && !_isCompleted){
Monitor.Wait(_items);
}
if (_items.Count > 0){
item = _items.Dequeue();
return true;
}
}

item = default;
return false;
}
我现在这样写可能有什么问题?

```

我询问AI一些接口的用法,帮忙排查错误。目前未发现AI的解答有错误。我认为本节难度偏高
Binary file added docs/03-async-grpc/assets/remotecli-normal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions docs/03-async-grpc/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# T3.2 RemoteCli 实现报告

## 实现功能

- 连接并检测 LogAnalyzer Agent,支持通过命令行参数或环境变量指定服务地址。
- 异步切换日志目录、查看日志文件、分析指定文件和分析全部文件。
- 异步读取 `GetAnalysisResult` 响应流,区分文件头与日志条目,并将日志转换后完整输出。
- 检查每次 RPC 返回的操作状态,分别提示非法参数、目录或文件不存在等业务错误。
- 处理非法菜单、空输入、非法并行度、空文件列表和 gRPC 连接异常,避免程序意外退出。

![RemoteCli 完整功能截图](./assets/remotecli-normal.png)

## 鲁棒性测试截图

![RemoteCli 鲁棒性测试截图](./assets/remotecli-robustness.png)

## Q3.1

与本地程序相比,网络应用多了客户端与服务端之间的通信边界。一次调用可能因断网、服务不可用或业务状态失败,不能只考虑本地异常,还需要处理异步调用、序列化转换、状态码和流式响应。调试时也要同时启动两端,并区分传输错误与业务错误,因此状态同步、错误处理和联调过程更加复杂。

## Q3.2

我使用了AI.提示词为“请帮我解决目前T3.2”中的bug。
135 changes: 131 additions & 4 deletions src/LocalCli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ public static void Main(string[] args)
{
return null;
}
directory = directory.Trim();
if (directory.Length == 0)
{
Console.WriteLine("Directory cannot be empty, please try again:");
continue;
}
try
{
if (!analyzer.ChangeDirectory(directory))
Expand All @@ -41,6 +47,11 @@ public static void Main(string[] args)
Console.WriteLine("Directory illegal, please try again:");
continue;
}
catch (Exception ex)
{
Console.WriteLine($"Failed to open directory: {ex.Message}");
continue;
}
}
return analyzer;
}
Expand Down Expand Up @@ -112,22 +123,138 @@ 6. Exit.

private static void ShowLogFiles(LogFileAnalyzer analyzer)
{
throw new NotImplementedException("T2.3");
var files = analyzer.GetLogFiles();
Console.WriteLine($"[{string.Join(", ", files)}]");
}

private static void AnalyzeFiles(LogFileAnalyzer analyzer)
{
throw new NotImplementedException("T2.3");
try
{
var degreeOfParallelism = ReadDegreeOfParallelism();
var fileNames = ReadFileNames();

analyzer.AnalyzeFiles(degreeOfParallelism, fileNames);
Console.WriteLine($"Analysis completed: [{string.Join(", ", fileNames)}]");
}
catch (Exception ex)
{
Console.WriteLine($"Analysis failed: {ex.Message}");
}
}

private static void AnalyzeAll(LogFileAnalyzer analyzer)
{
throw new NotImplementedException("T2.3");
try
{
var degreeOfParallelism = ReadDegreeOfParallelism();
var fileNames = analyzer.GetLogFiles();

analyzer.AnalyzeAll(degreeOfParallelism);
Console.WriteLine($"Analysis completed: [{string.Join(", ", fileNames)}]");
}
catch (Exception ex)
{
Console.WriteLine($"Analysis failed: {ex.Message}");
}
}

private static void GetAnalysisResult(LogFileAnalyzer analyzer)
{
throw new NotImplementedException("T2.3");
Console.WriteLine("Please input log file name:");
var input = Console.ReadLine();
if (input is null)
{
return;
}

var fileName = input.Trim();
if (fileName.Length == 0)
{
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.");
return;
}

switch (result.State)
{
case AnalysisState.NotAnalyzed:
Console.WriteLine($"File {fileName} has not been analyzed yet.");
break;

case AnalysisState.Failed:
Console.WriteLine(
$"Analysis failed for {fileName}: {result.ErrorMessage ?? "Unknown error"}");
break;

case AnalysisState.Succeeded:
Console.WriteLine($"Analysis result for {fileName}:");
var visitor = new KeyValueVisitor();
foreach (var entry in result.Entries)
{
var values = visitor.Dump(entry);
Console.WriteLine(string.Join(", ",
values.Select(pair => $"{pair.Key}: {pair.Value}")));
}
break;

default:
Console.WriteLine($"Unknown analysis state for {fileName}: {result.State}");
break;
}
}

private static int ReadDegreeOfParallelism()
{
while (true)
{
Console.WriteLine("Please input degree of parallelism:");
var input = Console.ReadLine();
if (input is null)
{
throw new EndOfStreamException("Input ended.");
}

if (int.TryParse(input.Trim(), out var degreeOfParallelism)
&& degreeOfParallelism >= 0)
{
return degreeOfParallelism;
}

Console.WriteLine("Invalid degree of parallelism, please try again:");
}
}

private static List<string> ReadFileNames()
{
while (true)
{
Console.WriteLine("Please input log file names (comma separated):");
var input = Console.ReadLine();
if (input is null)
{
throw new EndOfStreamException("Input ended.");
}

var fileNames = input
.Split(',')
.Select(fileName => fileName.Trim())
.Where(fileName => fileName.Length > 0)
.Distinct(StringComparer.Ordinal)
.ToList();

if (fileNames.Count > 0)
{
return fileNames;
}

Console.WriteLine("No log file names provided, please try again:");
}
}
}
}
Loading
Loading