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
2 changes: 1 addition & 1 deletion docs/00-prepare/guidance.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@

#### 复刻仓库(Fork this Repository)

本仓库隶属于 EESAST 组织,并关闭了直接提交的权限。要对代码进行修改,需要在你自己的个人账户中复刻这个仓库(本质是两个仓库,不同姓也可以不同名,但有天然的关联):
本仓库隶属于 EESAST 组织,并关闭了直接提交的权限。要对代码进行修改,需要在你自己的个人账户中复刻 [这个仓库](https://github.com/eesast/dotnet-workshop) (原仓库与复刻的仓库本质上是两个仓库,不同姓也可以不同名,但在 GitHub 上存在关联):

![press-fork](./assets/press-fork.png)

Expand Down
23 changes: 23 additions & 0 deletions docs/01-basic/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
01-basic 问答题
Q1.1
1. 分割与映射:使用 `CsvHelper` 库的 `LogRecordMap` 类,通过 `Map(m => m.LineNo).Index(0)` 指定第 0 列为行号,第 1 列时间戳,第 2 列 Pod 名,第 3 列为 Message。

2. 类型判断:在 `LineParser.ParseLine` 方法中,通过 `JsonDocument.Parse` 解析 `Message`,再用 `TryGetProperty("event", out var eventElement)` 获取事件字段,最后用 `switch` 匹配 `"call"`、`"request"`、`"internal"`。

3. JSON 解析方法:调用 `System.Text.Json.JsonSerializer.Deserialize<T>()` 将 JSON 转为强类型对象。

4. 防止字段缺失:在 `CallMessage`、`RequestMessage` 等 record 的属性上标注 `[JsonRequired]`,确保字段存在。

5. 命名转换:通过 `JsonSerializerOptions` 设置 `PropertyNamingPolicy = JsonNamingPolicy.KebabCaseLower`,自动映射 `RequestId` ↔ `request-id`。

Q1.2

调用链:
1. `KeyValueVisitor.Dump(LogEntry entry)`
2. `CallLogEntry.Accept<TResult>(ILogEntryVisitor<TResult> visitor)`(多态匹配实际类型)
3. `KeyValueVisitor.Visit(CallLogEntry entry)`

Q1.3.b
交互过程:提供了题目和代码文件,要求进行逐行讲解和代码实现指导,并在环境配置(WSL 安装 .NET 10 SDK)上获得了操作命令帮助。
AI 优点:AI 相比我自行查阅文档,AI 能结合具体任务提供更加定制化方案,节省了寻找合适 API 的时间。同时对代码可以有更详细地讲解,降低了我的理解门槛。
AI不足:存在幻觉没看到的文档/内容也会自己生成。
4 changes: 2 additions & 2 deletions src/LogParser/Models/LogEntries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public sealed record RequestLogEntry(
{
public override TResult Accept<TResult>(ILogEntryVisitor<TResult> visitor)
{
throw new NotImplementedException("TODO: T1.2");
return visitor.Visit(this);
}
}

Expand All @@ -69,7 +69,7 @@ public sealed record InternalLogEntry(
{
public override TResult Accept<TResult>(ILogEntryVisitor<TResult> visitor)
{
throw new NotImplementedException("TODO: T1.2");
return visitor.Visit(this);
}
}

Expand Down
47 changes: 42 additions & 5 deletions src/LogParser/Parser/LineParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,11 @@
var root = doc.RootElement;
if (root.TryGetProperty("event", out var eventElement))
{
return eventElement.GetString() switch

Check warning on line 16 in src/LogParser/Parser/LineParser.cs

View workflow job for this annotation

GitHub Actions / test-01-basic

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '""' is not covered.

Check warning on line 16 in src/LogParser/Parser/LineParser.cs

View workflow job for this annotation

GitHub Actions / test-01-basic

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '""' is not covered.
{
"call" => LineParser.CreateCall(logRecord),
"request" => throw new NotImplementedException("TODO: T1.2"),
"internal" => throw new NotImplementedException("TODO: T1.2"),
_ => throw new FormatException($"Unknown event type: {eventElement.GetString()} in log message: {logRecord.Message}")
"request" => LineParser.CreateRequest(logRecord),
"internal" => LineParser.CreateInternal(logRecord),
};
}
else
Expand Down Expand Up @@ -48,14 +47,45 @@
);
}

//新添加
private static LogEntry CreateRequest(LogRecord logRecord)
{
throw new NotImplementedException("TODO: T1.2");
var requestMessage = JsonSerializer.Deserialize<RequestMessage>(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<InternalMessage>(logRecord.Message, options)
?? throw new FormatException($"Failed to deserialize internal message: {logRecord.Message}");

var exceptionFull = internalMessage.Exception;
var colonIndex = exceptionFull.IndexOf(':');
if (colonIndex == -1)
throw new FormatException($"Exception format invalid, missing ':' in: {exceptionFull}");

var exceptionName = exceptionFull.Substring(0, colonIndex).Trim();
var exceptionMessage = exceptionFull.Substring(colonIndex + 1).Trim();

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)
Expand All @@ -78,10 +108,17 @@

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
);
}
}
27 changes: 25 additions & 2 deletions src/LogParser/Visitors/KeyValueVisitor.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using LogParser.Models;
using System.Collections.Generic;

namespace LogParser.Visitors
{
Expand All @@ -24,14 +25,36 @@ public Dictionary<string, string> Visit(CallLogEntry entry)
};
}

// 新增:处理Request类型日志
public Dictionary<string, string> Visit(RequestLogEntry entry)
{
throw new NotImplementedException("TODO: T1.3");
return new Dictionary<string, string>
{
["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()
};
}

// 新增:处理 Internal 类型日志
public Dictionary<string, string> Visit(InternalLogEntry entry)
{
throw new NotImplementedException("TODO: T1.3");
return new Dictionary<string, string>
{
["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
};
}
}
}
Loading