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
32 changes: 32 additions & 0 deletions docs/01-basic/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
Q1.1:

1.using var csv = new CsvReader(logFile, config);

​ csv.GetRecords<LogRecord>()

通过在LogFileParser.cs中定义一个继承自ClassMap<LogRecord>的映射类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<T>(string, JsonSerializerOptions)

给每个属性加上了property: JsonRequired特性,如果缺失特性,会抛出异常

定义了JsonSerializerOptions的options静态对象,设置了PropertyNamingPolicy = JsonNamingPolicy.KebabCaseLower

Q1.2:

Dictionary<string, string> KeyValueVisitor.Dump(LogEntry entry)

TResult CallLogEntry.Accept<TResult>(ILogEntryVisitor<TResult> visitor)

Dictionary<string, string> KeyValueVisitor.Visit(CallLogEntry entry)

Q1.3:

提示词:给我整个代码的类的结构图,与程序执行的流程图。AI的解答比我更加迅速,并且能够给我提供相对应的知识点,便于更好更快的理解代码。

25 changes: 25 additions & 0 deletions docs/02-multithreading/report.md
Original file line number Diff line number Diff line change
@@ -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<LogEntry>)传给了需要 IReadOnlyList<LogEntry>的构造函数,导致类型不匹配报错。最终加了.ToList()解决

*偏高
104 changes: 100 additions & 4 deletions src/LocalCli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,22 +112,118 @@ 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}):");

// 整合判空逻辑
if (result.Entries != null)
{
// 修复 CS0120:实例化 KeyValueVisitor
var visitor = new KeyValueVisitor();

foreach (var entry in result.Entries)
{
// 使用实例调用 Dump 方法
visitor.Dump(entry);
}
}
break;

case AnalysisState.Failed:
Console.WriteLine($"Analysis for '{fileName}' FAILED!");
Console.WriteLine($"Error message: {result.ErrorMessage}");
break;
}
}
}
}
101 changes: 60 additions & 41 deletions src/LogAnalyzer/LogFileAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -15,6 +18,7 @@ public class LogFileAnalyzer

public string? CurrentDirectory => _currentDirectory;
public bool HasDirectory => _currentDirectory is not null;

public bool IsAnalyzing
{
get
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -138,10 +142,7 @@ public void AnalyzeFiles(int degreeOfParallelism, IEnumerable<string> fileNames)
}
fileList = fileNameList.Select(fileName => _logFiles[fileName]).ToList();

/*
* Set _isAnalyzing
*/
// TODO: T2.2
_isAnalyzing = true;
}

try
Expand All @@ -150,11 +151,10 @@ public void AnalyzeFiles(int degreeOfParallelism, IEnumerable<string> fileNames)
}
finally
{
/*
* Unset _isAnalyzing
* Remember to lock _syncRoot to prevent data race
*/
// TODO: T2.2
lock (_syncRoot)
{
_isAnalyzing = false;
}
}
}

Expand All @@ -165,11 +165,14 @@ private void RunWorkers(int degreeOfParallelism, IReadOnlyList<FileInfo> 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);
}
}
}

Expand All @@ -180,27 +183,29 @@ private void RunWorkers(int degreeOfParallelism, IReadOnlyList<FileInfo> fileLis

var queue = new WorkQueue<FileInfo>();

/*
* 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];
for (int i = 0; i < degreeOfParallelism; i++)
{
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,
IsBackground = true
};
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<FileInfo> queue)
Expand All @@ -212,21 +217,35 @@ private void WorkerMain(int workerId, WorkQueue<FileInfo> 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<LogEntry>(),
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;
}
}
}
}
}
}
Loading
Loading