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
45 changes: 45 additions & 0 deletions docs/01-basic/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# 问答题报告

### (Q1.1)

在给出的代码框架 `Parser` 中:

* 哪条语句或哪几条语句将日志按逗号进行分割?代码中,我们是如何指定每一行的第几个字段代表何种意义的?
> using var csv = new CsvReader(logFile, config);配合循环使用的csv.GetRecords<LogRecord>();
>在LogRecordMap中一一指定对应

* 在对日志中 JSON 格式的 `message` 字段进行读取时,我们是在哪个方法内用哪几条语句判断这一行日志的种类(Call / Request / Internal)的?
> LineParser 类的 ParseLine 方法,if(root.TryGetProperty("event", out var eventElement))语句,再用swith判断

* 在确定了日志种类后,我们是调用了哪个库方法对 JSON 进行解析的?
> System.Text.Json 库中的 JsonSerializer.Deserialize<T>(...) 方法

* 进一步,我们的框架代码是如何防止日志中有字段缺失的?(例如所给的 Call 日志的 `message` 中缺失 `request_id` 字段)
> 在每个属性前面都强制加上了 [property: JsonRequired] 特性标签

* 更进一步,日志中的 JSON 的键是 `abc-def` 命名法(称为烤串命名法),而我们的解析结果却是放在 `AbcDef` 命名法(称为大驼峰命名法)的属性里,我们的框架代码中是如何告诉 JSON 解析器完成这一命名法转换的?
> 框架代码事先创建了一个名为 options 的配置变量,并在其中设置了 JsonNamingPolicy.KebabCaseLower 这一规则。在调用 JsonSerializer.Deserialize 提取数据时,代码将这个 options 作为参数交给了解析工具。

---

### (Q1.2)

以一个 Call 事件的解析结果为例,当调用 `KeyValueVisitor` 的 `Dump` 方法后,都有哪些方法被调用?请补充完整如下的方法调用链(.NET 内置库无需写出):

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


---

### (Q1.3)

本次作业中,你是否使用了 AI?根据你的使用情况,在以下 (Q1.3.a) (Q1.3.b) 两个问题中选择一题作答:


#### (Q1.3.b)
如果使用了 AI,你给予 AI 的提示词是什么?你认为 AI 给出的解答、你完全凭借传统搜索引擎以及自己的能力能够写出的解答之间,AI 的解答比你好在哪?AI 又有哪些解答是存在问题的,或者至少是不如你自己的解答的?给出你的理由。
+ > 提示词上,在完成代码部分,我只是把ai当做搜索引擎使用,去解释一些我看不懂的C#内置函数,然后再用自己能力补充代码,不过在q1.1中第1,4,5个问题,我确实完全不了解c#内置的库是什么,让ai先给出了解答再自己借助ai去了解;
+ > 代码中,ai的解答和我几乎相同;在问答题中,ai的解答比我更具有专业性,对整个项目的把握比我更透彻
+ > 我让只让ai给出了q1.1中1,4,5题超出我能力范围之外的解答,这些解答核实没发现什么大问题,其他的ai解答和我大致相同,我暂时未发现ai明显不如自己解答的情况。
Binary file added docs/02-multithreading/image-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/02-multithreading/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
57 changes: 57 additions & 0 deletions docs/02-multithreading/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
## 任务 2.3:实现前端交互逻辑 (Console UI)

### 1. 功能介绍
本任务在 `Program.cs` 中实现了控制台交互界面,主要完成了以下三个功能:

* **指定文件分析 (AnalyzeFiles)**:
接收用户输入的以逗号分隔的文件名,自动去除多余空格和空项。通过安全的方式读取用户输入的线程数,如果遇到输入格式错误或者后台任务冲突,会通过循环把用户留在当前步骤要求重新输入,防止程序崩溃。
* **全目录分析 (AnalyzeAll)**:
只需读取用户输入的线程数,在校验输入合法后,直接交给后台并发分析整个文件夹里的所有日志。
* **查询结果 (GetAnalysisResult)**:
获取用户输入的文件名,调用后台查询。如果文件还没有分析,提示未分析;如果分析失败,打印具体的报错信息;如果分析成功,则调用 `KeyValueVisitor.Dump` 方法,把底层的日志数据转换成键值对字典,并逐行清晰地打印到屏幕上。

### 2. 运行效果截图

![alt text](image.png)


![alt text](image-1.png)

### 3. 问答题

#### (Q2.1) 临界区与数据竞争理解

* **`WorkQueue<T>` 类中的共享变量有哪些?是通过什么保护其免于数据竞争(data race)呢?**
> 答:_items和_isCompleted;运用lock()形成互斥锁
>
>

* **`LogFileAnalyzer` 类中的共享变量有哪些?是通过什么保护其免于数据竞争呢?**
> 答:_currentDirectory,_isAnalyzing,_logFiles,_analysisResults;定义 _syncRoot作为互斥锁对象来保护
>
>
>

* **如果条件变量的判断条件使用了 `if` 判断而非 `while` 判断,当出现了虚假唤醒现象时(在类 UNIX 系统中,由于 UNIX 信号等机制,即使没有人调用过 `signal` 或 `broadcast`,处于 `wait` 当中的条件变量也可能被唤醒),会出现什么后果?结合无限仓库容量的生产者消费者问题简单叙述一下。**
> 答:这样的话会导致消费者在被异常唤醒时跳过if检查直接强行尝试从空仓库中取商品,,进而导致程序发生崩溃。
>
>
>

#### (Q2.2) 目录扫描逻辑

* **在给出的代码框架 `LogFileAnalyzer` 中,那一段代码扫描了给定的目录中的全部 `.log` 后缀的日志文件?假使给定的需求是不但要扫描给定目录中的日志文件,还要递归地获取给定的目录的全部子目录、子子目录……内的日志文件,应当如何做(简要回答即可)?**[cite: 6]
> 答:ChangeDirectory 方法中Directory.EnumerateFiles(directoryPath, "*.log", SearchOption.TopDirectoryOnly);
> SearchOption.TopDirectoryOnly 修改为 SearchOption.AllDirectories
>
>

#### (Q2.3) AI 使用情况调查

*本次作业中,你是否使用了 AI?根据你的使用情况,在以下 (Q2.3.a) (Q2.3.b) 两个问题中选择一题作答即可:*

* **(Q2.3.b) 如果使用了 AI,你给予 AI 的提示词是什么?你对 AI 的使用是询问 AI 一些接口的用法或是在某处的写法,还是让 AI 帮你写一部分作业代码,又或是让 AI 给你讲解代码框架?AI 的解答是否出现过错误(如果有,是哪些)?你认为本节的难度是偏低、适中,还是偏高?**
> 答:我给予ai提示词更多是搜索性质与提示讲解性质,并没有让ai直接给出过代码。因为本节难度我认为对于之前从未接触过c#的人来说难度比较高,c#的一堆内置函数单凭自己很难看懂,更何况有时候会一下子牵扯到多个代码文件,没有ai的辅助梳理很容易忘记要干什么。
>
>
>
Binary file added docs/03-async-grpc/image-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/03-async-grpc/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 38 additions & 0 deletions docs/03-async-grpc/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# 异步 gRPC 日志分析系统实现报告

## 一、 功能介绍

本项目在之前本地日志分析器的基础上,成功升级为了基于 gRPC 框架的“客户端-服务端”网络架构。主要实现了以下核心功能:

1. **服务端日志处理代理 (AgentSession)**:
- 实现了响应各种网络请求的方法。
- 能够将底层系统抛出的异常(如路径非法、并发度为负数等)拦截,并转化为标准的网络状态码(如 `INVALID_ARGUMENT`, `DIRECTORY_NOT_FOUND` 等)返回给客户端。
- 针对结果获取,实现了服务端的流式(Streaming)返回逻辑,先发送全局状态(Header),再循环发送具体的日志内容(LogEntry)。

2. **远程异步客户端 (RemoteCli)**:
- 全面采用了 `async/await` 异步编程模型,保证在等待网络响应时控制台不会卡死。
- **切换目录 (Change Directory)**:向服务器发送路径,并接收服务器返回的当前绝对路径和文件列表。
- **显示文件 (Show Log Files)**:通过网络请求获取当前远端工作目录下的所有日志文件。
- **执行分析 (Analyze)**:支持全局分析和部分文件分析,将用户输入的并发度等参数打包发送,并根据服务器返回的状态码判断任务是否成功启动。
- **流式接收结果 (Get Analysis Result)**:建立流式通道,根据服务器发回的数据包类型(Header 或是具体的日志行),分别进行状态打印和逐行解析展示。

## 二、图片展示
![alt text](image.png)
![alt text](image-1.png)
(注:中间有个todo1.2是因为切换分支后没有及时同步第一讲的功能,不过不影响本讲内容实现)

## 三、问题回答
**问题 3.1:你认为,你在开发网络应用程序,与你在以往开发非网络应用程序的区别在哪里?网络应用程序的开发存在哪些额外的难点?存在哪些额外的复杂之处?**

**答:**
> 核心区别在于数据通信: 本地程序直接在内存中传递对象,而网络程序必须将数据打包
> 主要难点在于异步逻辑: 网络通信存在延迟,为了不让程序等待卡死,必须使用 async/await 等异步编程模型
> 额外复杂之处在于极高的容错要求: 服务端作为常驻程序,绝不能因为用户的错误输入或内部小错误而崩溃。



**问题 3.2:本次作业中,你是否使用了 AI?根据你的使用情况,在以下 (Q3.2.a) (Q3.2.b) 两个问题中选择一题作答:**

**(Q3.2.b) 如果使用了 AI,你给予 AI 的提示词是什么?你对 AI 的使用是询问 AI 一些接口的用法、gRPC 的使用,或是在某处的写法,还是让 AI 帮你写一部分作业代码,又或是让 AI 给你讲解代码框架?AI 的解答是否出现过错误(如果有,是哪些)?你从 AI 那里是否得知了一些关于异步,或是 gRPC 等原本你不知道或是难以理解的知识?**

>我对ai的使用主要来自询问我看不懂的语句语法,让ai给我讲解作业中的代码框架。ai的解答出现过错误,比如一开始都没有使用异步编程模型等低级错误,但是人工一眼就看得出来。我使用ai主要就是补充知识,所以我的确获得了原本不知道的知识。
Binary file added docs/04-avalonia/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 30 additions & 0 deletions docs/04-avalonia/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# 04-avalonia 实验报告

## 1. 功能介绍

本项目基于 Avalonia UI 框架实现了一个图形界面的日志分析客户端,完整复刻并扩展了控制台版本的功能。具体实现的功能如下:

* **刷新日志文件列表**:用户可以点击界面的 Refresh 按钮,通过发起 RPC 请求实时获取服务端指定目录下的最新日志文件,并更新显示在 Log Files 列表中。
* **分析选中的多个文件**:支持通过按住 Ctrl 键在列表中多选文件。点击 Selected 按钮后,客户端将所选文件名和输入的并行度(DoP)发送给服务端进行批量分析。
* **分析全部文件**:在界面上新增了 All 按钮,允许用户一键提交当前目录下的所有文件进行分析,无需逐一选中。
* **右键菜单分析指定文件**:为文件列表添加了右键上下文菜单,用户可以直接右键点击某个特定的日志文件并选择“Analyze File(A)”进行针对性分析。
* **查看并显示分析结果**:通过右键菜单的“View Analysis Results(V)”选项,可以通过服务端流(Server Stream)持续获取特定文件的日志分析结果。结果列表(Analysis Result)能够根据返回的日志状态及内部字段进行动态格式化并直观展示。
* **异常拦截与鲁棒性保障**:在发起所有请求前均加入了对并行度(如负数或非数字)及选中状态的合法性校验。对于底层 gRPC 通信异常或服务端的报错,统一通过图形化的消息对话框(MessageDialog)向用户提示,确保程序在遇到非法输入或内部错误时绝对不会崩溃。

## 2. 完整功能截图
![alt text](image.png)

## 3. 问答题

### (Q4.1) 你认为,你在开发 GUI 应用程序,与你在以往开控制台应用程序的区别在哪里?GUI 应用程序的开发存在哪些额外的难点?存在哪些额外的复杂之处?你是否有通过编写 GUI 应用程序对异步 `async` 和 `await` 有了更进一步的理解?异步编程是否又给你带来的额外的困扰?说说你的看法。
> 控制台只能让用户线按顺序线性地操作,而gui可以让用户多方面打乱顺序操作
> 难点在于对异步通信的要求更高,主线程ui渲染绝对不能被卡死
> 有,这个异步操作保证了程序在等待gpc响应是仍然能与用户进行交互
> 代码实现与逻辑上更为复杂,并且异步编程一些语法容易在编写时疏漏
### (Q4.2) 本次作业中,你是否使用了 AI?根据你的使用情况,在以下 (Q4.2.a) (Q4.2.b) 两个问题中选择一题作答:

#### (Q4.2.b) 如果使用了 AI,你给予 AI 的提示词是什么?你对 AI 的使用是询问 AI 一些接口的用法、gRPC 的使用,或是在某处的写法,还是让 AI 帮你写一部分作业代码,又或是让 AI 给你讲解代码框架?AI 的解答是否出现过错误(如果有,是哪些)?你从 AI 那里是否得知了一些关于异步,或是 gRPC 等原本你不知道或是难以理解的知识?
> 项目到了这个整个项目结构已经比较复杂,故本节使用ai,问题中三种情况均存在。
> 本节中没有出现错误,我从ai获得过未知以及之前遗忘的知识。


9 changes: 9 additions & 0 deletions src/LocalCli/LocalCli.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,13 @@
</None>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.35.1" />
<PackageReference Include="Grpc.Net.Client" Version="2.83.0" />
<PackageReference Include="Grpc.Tools" Version="2.83.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

</Project>
140 changes: 134 additions & 6 deletions src/LocalCli/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using LogAnalyzer;
using System.Net;
using LogAnalyzer;
using LogParser.Visitors;

namespace LocalCli
Expand Down Expand Up @@ -112,22 +113,149 @@ 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;
}
int index=1;
foreach (var file in files)
{
Console.WriteLine($"{index}.{file}");
index++;
}
}

private static void AnalyzeFiles(LogFileAnalyzer analyzer)
{
throw new NotImplementedException("T2.3");
{ while(true){
Console.WriteLine("Please input log files to analyze (separated by comma):");
var str=Console.ReadLine();
if (str is null)
{
return;
}
var selectedFiles = str.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(f => f.Trim()).ToArray();
if (selectedFiles.Length == 0)
{
Console.WriteLine("No files specified, please try again.");
continue;
}
int degree = 0;
while (true)
{
Console.WriteLine("Please input degree of parallelism:");
var degreeStr = Console.ReadLine();
if (degreeStr is null) return;
if (!int.TryParse(degreeStr, out degree))
{
Console.WriteLine("Invalid number, please try again.");
continue;
}
break;
}
try
{
analyzer.AnalyzeFiles(degree,selectedFiles);
break;
}
catch (InvalidOperationException)
{
Console.WriteLine("Analysis is already running, please wait.");
break;
}
catch(ArgumentException)
{
Console.WriteLine("Invalid files, please try again.");
continue;
}
}

}


private static void AnalyzeAll(LogFileAnalyzer analyzer)
{
throw new NotImplementedException("T2.3");
while (true)
{
Console.WriteLine("Please input degree of parallelism:");
var degreeStr = Console.ReadLine();
if (degreeStr is null)
{
return;
}
if (!int.TryParse(degreeStr, out int degree))
{
Console.WriteLine("Invalid number, please try again.");
continue;
}
try
{
analyzer.AnalyzeAll(degree);
break;
}
catch (InvalidOperationException)
{
Console.WriteLine("Analysis is already running, please wait.");
break;
}
catch (ArgumentException)
{
Console.WriteLine("Invalid degree of parallelism, please try again.");
continue;
}
}
}

private static void GetAnalysisResult(LogFileAnalyzer analyzer)
{
throw new NotImplementedException("T2.3");
while (true)
{
Console.WriteLine("Please input log file name:");
var fileName = Console.ReadLine();
if (fileName is null)
{
return;
}
fileName = fileName.Trim();
if (fileName.Length == 0)
{
Console.WriteLine("Invalid file name, please try again.");
continue;
}
if (!analyzer.TryGetAnalysisResult(fileName, out var result))
{
Console.WriteLine("File not found, please try again.");
continue;
}
if (result is null)
{
Console.WriteLine("File not found, please try again.");
continue;
}
switch (result.State)
{
case AnalysisState.NotAnalyzed:
Console.WriteLine("Not analyzed.");
break;
case AnalysisState.Failed:
Console.WriteLine($"Failed: {result.ErrorMessage}");
break;
case AnalysisState.Succeeded:
var visitor = new KeyValueVisitor();
foreach (var entry in result.Entries)
{
var logDict = visitor.Dump(entry);
foreach (var kvp in logDict)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}
}
break;

}
break;
}
}
}
}
Loading