diff --git a/App.config b/App.config new file mode 100644 index 0000000..56efbc7 --- /dev/null +++ b/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/App.xaml b/App.xaml new file mode 100644 index 0000000..b79b2e7 --- /dev/null +++ b/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/App.xaml.cs b/App.xaml.cs new file mode 100644 index 0000000..352b433 --- /dev/null +++ b/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace LogAnalyzer +{ + /// + /// App.xaml 的交互逻辑 + /// + public partial class App : Application + { + } +} diff --git a/LogAnalyzer.csproj b/LogAnalyzer.csproj new file mode 100644 index 0000000..5fdeba4 --- /dev/null +++ b/LogAnalyzer.csproj @@ -0,0 +1,99 @@ + + + + + Debug + AnyCPU + {0B886B0C-D80A-4AF0-A670-4FFD5FAE70FD} + WinExe + LogAnalyzer + LogAnalyzer + v4.7.2 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + + MainWindow.xaml + Code + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + \ No newline at end of file diff --git a/LogAnalyzer.slnx b/LogAnalyzer.slnx new file mode 100644 index 0000000..737f5fe --- /dev/null +++ b/LogAnalyzer.slnx @@ -0,0 +1,3 @@ + + + diff --git a/LogParserService.cs b/LogParserService.cs new file mode 100644 index 0000000..5a13428 --- /dev/null +++ b/LogParserService.cs @@ -0,0 +1,840 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace LogAnalyzer +{ + public class LogParserService + { + // 匹配行首的时间戳格式: 2026/7/17 2:46:38 + // 使用 ^ 确保匹配行首 + private readonly Regex _timestampRegex = new Regex( + @"^(?\d{4}/\d{1,2}/\d{1,2}\s+\d{1,2}:\d{1,2}:\d{1,2})\s*(?.*)$", + RegexOptions.Compiled + ); + + /// + /// 解析日志文件,以行首时间戳作为分隔符合并多行日志 + /// + public DataTable ParseLogFile(string filePath) + { + return ParseLogFile(filePath, Encoding.Default); + } + + /// + /// 解析日志文件(支持指定编码) + /// + public DataTable ParseLogFile(string filePath, Encoding encoding) + { + if (!File.Exists(filePath)) + throw new FileNotFoundException($"日志文件未找到: {filePath}", filePath); + + var logTable = CreateLogTable(); + + try + { + using (var reader = new StreamReader(filePath, encoding)) + { + string line; + string currentTimestamp = null; + StringBuilder currentMessage = new StringBuilder(); + bool hasContent = false; + + while ((line = reader.ReadLine()) != null) + { + // 检查行首是否匹配时间戳格式 + var match = _timestampRegex.Match(line); + + if (match.Success) + { + // 遇到新的时间戳,保存之前的条目 + if (hasContent && currentTimestamp != null) + { + AddLogEntry(logTable, currentTimestamp, currentMessage.ToString()); + } + + // 开始新的条目 + currentTimestamp = match.Groups["datetime"].Value; + string messagePart = match.Groups["message"].Value.Trim(); + currentMessage.Clear(); + + if (!string.IsNullOrEmpty(messagePart)) + { + currentMessage.Append(messagePart); + } + + hasContent = true; + } + else + { + // 不是时间戳行,追加到当前消息 + if (hasContent) + { + if (currentMessage.Length > 0) + { + currentMessage.Append(Environment.NewLine); + } + currentMessage.Append(line.TrimEnd()); + } + else + { + // 如果文件开头没有时间戳,将整行作为特殊处理 + // 这里可以忽略或作为独立条目 + if (!string.IsNullOrWhiteSpace(line)) + { + logTable.Rows.Add("未知时间", line.Trim()); + } + } + } + } + + // 保存最后一个条目 + if (hasContent && currentTimestamp != null) + { + AddLogEntry(logTable, currentTimestamp, currentMessage.ToString()); + } + } + } + catch (IOException ex) + { + throw new InvalidOperationException($"读取日志文件失败: {ex.Message}", ex); + } + + return logTable; + } + + /// + /// 添加日志条目到 DataTable + /// + private void AddLogEntry(DataTable table, string timestamp, string message) + { + // 去除多余的空行 + string cleanMessage = message.Trim(); + if (!string.IsNullOrEmpty(cleanMessage)) + { + table.Rows.Add(timestamp, cleanMessage); + } + } + + /// + /// 创建日志 DataTable 结构 + /// + private DataTable CreateLogTable() + { + var table = new DataTable("LogData"); + table.Columns.Add("时间戳", typeof(string)); + table.Columns.Add("日志内容", typeof(string)); + return table; + } + + /// + /// 解析日志并显示详细信息(用于调试) + /// + public void ParseAndDisplay(string filePath) + { + var logData = ParseLogFile(filePath); + + Console.WriteLine($"总共解析了 {logData.Rows.Count} 条日志条目"); + Console.WriteLine(new string('=', 80)); + + int index = 1; + foreach (DataRow row in logData.Rows) + { + string timestamp = row["时间戳"]?.ToString() ?? ""; + string message = row["日志内容"]?.ToString() ?? ""; + + Console.WriteLine($"条目 {index}:"); + Console.WriteLine($"时间: {timestamp}"); + Console.WriteLine($"内容:"); + Console.WriteLine(message); + Console.WriteLine(new string('-', 80)); + Console.WriteLine(); + + index++; + } + } + + /// + /// 检查特定日志文件,显示每行的解析情况(调试用) + /// + public void DebugParse(string filePath) + { + if (!File.Exists(filePath)) + { + Console.WriteLine($"文件不存在: {filePath}"); + return; + } + + Console.WriteLine("逐行解析调试:"); + Console.WriteLine(new string('=', 80)); + + using (var reader = new StreamReader(filePath, Encoding.Default)) + { + string line; + int lineNumber = 0; + + while ((line = reader.ReadLine()) != null) + { + lineNumber++; + var match = _timestampRegex.Match(line); + + Console.WriteLine($"行 {lineNumber}:"); + Console.WriteLine($" 原始: {line}"); + + if (match.Success) + { + Console.WriteLine($" 识别为时间戳: {match.Groups["datetime"].Value}"); + Console.WriteLine($" 消息部分: {match.Groups["message"].Value}"); + } + else + { + Console.WriteLine($" 不是时间戳行(作为多行内容的一部分)"); + } + Console.WriteLine(); + } + } + } + + /// + /// 从日志中提取包含数据库指令关键词的条目及其前后N条日志 + /// + /// 原始日志DataTable + /// 前后文行数,默认为3 + /// 包含数据库指令及其上下文的DataTable + public DataTable ExtractDatabaseCommandsWithContext(DataTable logTable, int contextLines = 3) + { + if (logTable == null || logTable.Rows.Count == 0) + throw new ArgumentException("日志数据为空", nameof(logTable)); + + // 创建结果表 + var resultTable = new DataTable("DbCommandContext"); + resultTable.Columns.Add("时间戳", typeof(string)); + resultTable.Columns.Add("日志内容", typeof(string)); + resultTable.Columns.Add("是否数据库指令", typeof(bool)); + resultTable.Columns.Add("条目索引", typeof(int)); + resultTable.Columns.Add("上下文类型", typeof(string)); + resultTable.Columns.Add("匹配关键词", typeof(string)); // 记录匹配到的关键词 + + // 1. 查找所有包含数据库指令关键词的条目索引 + var dbCommandIndices = new List(); + var keywordMatches = new Dictionary(); // 记录每个条目匹配的关键词 + + for (int i = 0; i < logTable.Rows.Count; i++) + { + string message = logTable.Rows[i]["日志内容"]?.ToString() ?? ""; + string matchedKeyword = ContainsDbKeyword(message); + + if (!string.IsNullOrEmpty(matchedKeyword)) + { + dbCommandIndices.Add(i); + keywordMatches[i] = matchedKeyword; + } + } + + if (dbCommandIndices.Count == 0) + { + // 如果没有找到数据库指令,返回空表 + return resultTable; + } + + // 2. 收集需要提取的索引范围 + var indicesToExtract = new HashSet(); + foreach (int dbIndex in dbCommandIndices) + { + // 添加数据库指令本身 + indicesToExtract.Add(dbIndex); + + // 添加前N条 + for (int i = 1; i <= contextLines; i++) + { + int prevIndex = dbIndex - i; + if (prevIndex >= 0) + indicesToExtract.Add(prevIndex); + } + + // 添加后N条 + for (int i = 1; i <= contextLines; i++) + { + int nextIndex = dbIndex + i; + if (nextIndex < logTable.Rows.Count) + indicesToExtract.Add(nextIndex); + } + } + + // 3. 按索引排序并添加到结果表 + var sortedIndices = indicesToExtract.OrderBy(idx => idx).ToList(); + + foreach (int idx in sortedIndices) + { + DataRow originalRow = logTable.Rows[idx]; + string message = originalRow["日志内容"]?.ToString() ?? ""; + bool isDbCommand = dbCommandIndices.Contains(idx); + string matchedKeyword = isDbCommand ? keywordMatches[idx] : ""; + + // 确定上下文类型 + string contextType; + if (isDbCommand) + contextType = "数据库指令"; + else + { + // 判断是前文还是后文 + bool isBeforeDb = dbCommandIndices.Any(dbIdx => dbIdx > idx); + bool isAfterDb = dbCommandIndices.Any(dbIdx => dbIdx < idx); + + if (isBeforeDb && !isAfterDb) + contextType = "指令前文"; + else if (isAfterDb && !isBeforeDb) + contextType = "指令后文"; + else if (isBeforeDb && isAfterDb) + contextType = "上下文(指令之间)"; + else + contextType = "上下文"; + } + + resultTable.Rows.Add( + originalRow["时间戳"]?.ToString() ?? "", + message, + isDbCommand, + idx, + contextType, + matchedKeyword + ); + } + + return resultTable; + } + public DataTable ExtractTank1CommandsWithContext(DataTable logTable) + { + // 1. 参数校验 + if (logTable == null || logTable.Rows.Count == 0) + throw new ArgumentException("日志数据为空", nameof(logTable)); + + // 2. 创建结果表,保持与输入表相同的结构(或按需自定义) + // var resultTable = logTable.Clone(); // Clone() 会复制表结构,但不复制数据 + + var matchedRows = new List(); + + // 3. 遍历原始表,匹配关键字 + foreach (DataRow row in logTable.Rows) + { + string message = row["日志内容"]?.ToString() ?? ""; + + // 同时包含 "M1T1" 和 "T1执行" 才匹配(如需匹配其一,请将 && 改为 ||) + if (message.Contains("M1T1") || message.Contains("T1执行") || + message.Contains(" T1") || message.Contains(",T1") || message.Contains(",401)")) + { + matchedRows.Add(row); + } + } + + var finalRows = new List(); + if (matchedRows.Count > 0) + { + int startIdx = 0; + string currentContent = matchedRows[0]["日志内容"]?.ToString() ?? ""; + + for (int i = 1; i < matchedRows.Count; i++) + { + string nextContent = matchedRows[i]["日志内容"]?.ToString() ?? ""; + + // 如果下一行的内容与当前块的内容不同 + if (nextContent != currentContent) + { + // 将当前块的【开始行】加入结果 + finalRows.Add(matchedRows[startIdx]); + + // 如果当前块有连续重复(即 i - startIdx > 1),将【结束行】加入结果 + if (i - startIdx > 1) + { + finalRows.Add(matchedRows[i - 1]); + } + + // 重置状态,开始新的块 + startIdx = i; + currentContent = nextContent; + } + } + + // 处理最后一个块(循环结束后别忘了处理尾部) + finalRows.Add(matchedRows[startIdx]); + if (matchedRows.Count - startIdx > 1) + { + finalRows.Add(matchedRows[matchedRows.Count - 1]); + } + } + + // 3. 构建并返回结果表 + var resultTable = logTable.Clone(); + foreach (var row in finalRows) + { + resultTable.ImportRow(row); + } + + return resultTable; + } + public DataTable ExtractTank2CommandsWithContext(DataTable logTable) + { + // 1. 参数校验 + if (logTable == null || logTable.Rows.Count == 0) + throw new ArgumentException("日志数据为空", nameof(logTable)); + + var matchedRows = new List(); + + // 3. 遍历原始表,匹配关键字 + foreach (DataRow row in logTable.Rows) + { + string message = row["日志内容"]?.ToString() ?? ""; + + // 同时包含 "M1T1" 和 "T1执行" 才匹配(如需匹配其一,请将 && 改为 ||) + if (message.Contains("M1T2") || message.Contains("T2执行") || + message.Contains(" T2") || message.Contains(",T2") || message.Contains(",402)")) + { + // 将匹配到的行导入结果表 + matchedRows.Add(row); + } + } + + var finalRows = new List(); + if (matchedRows.Count > 0) + { + int startIdx = 0; + string currentContent = matchedRows[0]["日志内容"]?.ToString() ?? ""; + + for (int i = 1; i < matchedRows.Count; i++) + { + string nextContent = matchedRows[i]["日志内容"]?.ToString() ?? ""; + + // 如果下一行的内容与当前块的内容不同 + if (nextContent != currentContent) + { + // 将当前块的【开始行】加入结果 + finalRows.Add(matchedRows[startIdx]); + + // 如果当前块有连续重复(即 i - startIdx > 1),将【结束行】加入结果 + if (i - startIdx > 1) + { + finalRows.Add(matchedRows[i - 1]); + } + + // 重置状态,开始新的块 + startIdx = i; + currentContent = nextContent; + } + } + + // 处理最后一个块(循环结束后别忘了处理尾部) + finalRows.Add(matchedRows[startIdx]); + if (matchedRows.Count - startIdx > 1) + { + finalRows.Add(matchedRows[matchedRows.Count - 1]); + } + } + + // 3. 构建并返回结果表 + var resultTable = logTable.Clone(); + foreach (var row in finalRows) + { + resultTable.ImportRow(row); + } + + return resultTable; + } + public DataTable ExtractTank3CommandsWithContext(DataTable logTable) + { + // 1. 参数校验 + if (logTable == null || logTable.Rows.Count == 0) + throw new ArgumentException("日志数据为空", nameof(logTable)); + + var matchedRows = new List(); + + // 3. 遍历原始表,匹配关键字 + foreach (DataRow row in logTable.Rows) + { + string message = row["日志内容"]?.ToString() ?? ""; + + // 同时包含 "M1T1" 和 "T1执行" 才匹配(如需匹配其一,请将 && 改为 ||) + if (message.Contains("M2T1") || message.Contains("T3执行") || + message.Contains(" T3") || message.Contains(",T3") || message.Contains(",403)")) + { + // 将匹配到的行导入结果表 + matchedRows.Add(row); + } + } + + var finalRows = new List(); + if (matchedRows.Count > 0) + { + int startIdx = 0; + string currentContent = matchedRows[0]["日志内容"]?.ToString() ?? ""; + + for (int i = 1; i < matchedRows.Count; i++) + { + string nextContent = matchedRows[i]["日志内容"]?.ToString() ?? ""; + + // 如果下一行的内容与当前块的内容不同 + if (nextContent != currentContent) + { + // 将当前块的【开始行】加入结果 + finalRows.Add(matchedRows[startIdx]); + + // 如果当前块有连续重复(即 i - startIdx > 1),将【结束行】加入结果 + if (i - startIdx > 1) + { + finalRows.Add(matchedRows[i - 1]); + } + + // 重置状态,开始新的块 + startIdx = i; + currentContent = nextContent; + } + } + + // 处理最后一个块(循环结束后别忘了处理尾部) + finalRows.Add(matchedRows[startIdx]); + if (matchedRows.Count - startIdx > 1) + { + finalRows.Add(matchedRows[matchedRows.Count - 1]); + } + } + + // 3. 构建并返回结果表 + var resultTable = logTable.Clone(); + foreach (var row in finalRows) + { + resultTable.ImportRow(row); + } + + return resultTable; + } + public DataTable ExtractTank4CommandsWithContext(DataTable logTable) + { + // 1. 参数校验 + if (logTable == null || logTable.Rows.Count == 0) + throw new ArgumentException("日志数据为空", nameof(logTable)); + + var matchedRows = new List(); + + // 3. 遍历原始表,匹配关键字 + foreach (DataRow row in logTable.Rows) + { + string message = row["日志内容"]?.ToString() ?? ""; + + // 同时包含 "M1T1" 和 "T1执行" 才匹配(如需匹配其一,请将 && 改为 ||) + if (message.Contains("M2T2") || message.Contains("T4执行") || + message.Contains(" T4") || message.Contains(",T4") || message.Contains(",404)")) + { + // 将匹配到的行导入结果表 + matchedRows.Add(row); + } + } + + var finalRows = new List(); + if (matchedRows.Count > 0) + { + int startIdx = 0; + string currentContent = matchedRows[0]["日志内容"]?.ToString() ?? ""; + + for (int i = 1; i < matchedRows.Count; i++) + { + string nextContent = matchedRows[i]["日志内容"]?.ToString() ?? ""; + + // 如果下一行的内容与当前块的内容不同 + if (nextContent != currentContent) + { + // 将当前块的【开始行】加入结果 + finalRows.Add(matchedRows[startIdx]); + + // 如果当前块有连续重复(即 i - startIdx > 1),将【结束行】加入结果 + if (i - startIdx > 1) + { + finalRows.Add(matchedRows[i - 1]); + } + + // 重置状态,开始新的块 + startIdx = i; + currentContent = nextContent; + } + } + + // 处理最后一个块(循环结束后别忘了处理尾部) + finalRows.Add(matchedRows[startIdx]); + if (matchedRows.Count - startIdx > 1) + { + finalRows.Add(matchedRows[matchedRows.Count - 1]); + } + } + + // 3. 构建并返回结果表 + var resultTable = logTable.Clone(); + foreach (var row in finalRows) + { + resultTable.ImportRow(row); + } + + return resultTable; + } + /// + /// 检查日志内容是否包含数据库指令关键词,返回匹配的关键词 + /// + private string ContainsDbKeyword(string message) + { + if (string.IsNullOrWhiteSpace(message)) + return null; + + // 数据库指令关键词列表(不区分大小写) + string[] dbKeywords = { + "SELECT", "INSERT", "UPDATE", "DELETE", "CREATE", "ALTER", + "DROP", "TRUNCATE", "MERGE", "EXEC", "EXECUTE", + "FROM", "WHERE", "JOIN", "INNER", "LEFT", "RIGHT", "FULL", "OUTER", + "UNION", "INTERSECT", "EXCEPT", "VALUES", "SET", "WITH", + "AS", "ON", "AND", "OR", "NOT", "IN", "LIKE", "BETWEEN", + "BEGIN", "COMMIT", "ROLLBACK", "GRANT", "REVOKE", + "PROCEDURE", "FUNCTION", "TRIGGER", "INDEX", "VIEW" + }; + + string upperMessage = message.ToUpper(); + + // 检查是否包含任何关键词 + foreach (string keyword in dbKeywords) + { + // 使用正则表达式确保关键词是独立的单词 + string pattern = $@"\b{keyword}\b"; + if (Regex.IsMatch(upperMessage, pattern, RegexOptions.IgnoreCase)) + { + return keyword; + } + } + + return null; + } + + /// + /// 提取数据库指令及其上下文(分组版本) + /// + public DataTable ExtractDbCommandsGrouped(DataTable logTable, int contextLines = 3) + { + if (logTable == null || logTable.Rows.Count == 0) + throw new ArgumentException("日志数据为空", nameof(logTable)); + + var resultTable = new DataTable("DbCommandGrouped"); + resultTable.Columns.Add("组ID", typeof(int)); + resultTable.Columns.Add("时间戳", typeof(string)); + resultTable.Columns.Add("日志内容", typeof(string)); + resultTable.Columns.Add("类型", typeof(string)); + resultTable.Columns.Add("行号", typeof(int)); + resultTable.Columns.Add("匹配关键词", typeof(string)); + + // 查找所有包含数据库指令关键词的条目索引 + var dbIndices = new List(); + var keywordMap = new Dictionary(); + + for (int i = 0; i < logTable.Rows.Count; i++) + { + string message = logTable.Rows[i]["日志内容"]?.ToString() ?? ""; + string matchedKeyword = ContainsDbKeyword(message); + + if (!string.IsNullOrEmpty(matchedKeyword)) + { + dbIndices.Add(i); + keywordMap[i] = matchedKeyword; + } + } + + if (dbIndices.Count == 0) + return resultTable; + + int groupId = 0; + var processedIndices = new HashSet(); + + foreach (int dbIndex in dbIndices) + { + if (processedIndices.Contains(dbIndex)) + continue; + + groupId++; + var indicesInGroup = new List(); + + // 添加数据库指令本身 + indicesInGroup.Add(dbIndex); + processedIndices.Add(dbIndex); + + // 添加前N条 + for (int i = 1; i <= contextLines; i++) + { + int prevIndex = dbIndex - i; + if (prevIndex >= 0 && !processedIndices.Contains(prevIndex)) + { + indicesInGroup.Add(prevIndex); + processedIndices.Add(prevIndex); + } + } + + // 添加后N条 + for (int i = 1; i <= contextLines; i++) + { + int nextIndex = dbIndex + i; + if (nextIndex < logTable.Rows.Count && !processedIndices.Contains(nextIndex)) + { + indicesInGroup.Add(nextIndex); + processedIndices.Add(nextIndex); + } + } + + // 按索引排序 + indicesInGroup.Sort(); + + // 添加到结果表 + foreach (int idx in indicesInGroup) + { + DataRow originalRow = logTable.Rows[idx]; + string message = originalRow["日志内容"]?.ToString() ?? ""; + bool isDb = idx == dbIndex || dbIndices.Contains(idx); + string matchedKeyword = isDb ? keywordMap[idx] : ""; + + string type; + if (isDb) + type = $"数据库指令 [{matchedKeyword}]"; + else if (idx < dbIndex) + type = "前文"; + else + type = "后文"; + + resultTable.Rows.Add( + groupId, + originalRow["时间戳"]?.ToString() ?? "", + message, + type, + idx + 1, + matchedKeyword + ); + } + } + + return resultTable; + } + + /// + /// 获取数据库指令统计信息 + /// + public Dictionary GetDbCommandStatistics(DataTable logTable) + { + var stats = new Dictionary(); + + for (int i = 0; i < logTable.Rows.Count; i++) + { + string message = logTable.Rows[i]["日志内容"]?.ToString() ?? ""; + string matchedKeyword = ContainsDbKeyword(message); + + if (!string.IsNullOrEmpty(matchedKeyword)) + { + if (!stats.ContainsKey(matchedKeyword)) + stats[matchedKeyword] = 0; + stats[matchedKeyword]++; + } + } + + return stats; + } + + /// + /// 导出数据库指令及其上下文为格式化字符串 + /// + public string ExportDbCommandsToString(DataTable logTable, int contextLines = 3) + { + var dbContext = ExtractDbCommandsGrouped(logTable, contextLines); + + if (dbContext.Rows.Count == 0) + return "未找到数据库指令"; + + StringBuilder sb = new StringBuilder(); + sb.AppendLine("=".PadRight(100, '=')); + sb.AppendLine($"数据库指令及其上下文(前后各{contextLines}条)"); + sb.AppendLine("=".PadRight(100, '=')); + sb.AppendLine(); + + // 统计信息 + var stats = GetDbCommandStatistics(logTable); + sb.AppendLine("【统计信息】"); + foreach (var kvp in stats.OrderByDescending(k => k.Value)) + { + sb.AppendLine($" {kvp.Key}: {kvp.Value} 次"); + } + sb.AppendLine(); + sb.AppendLine(new string('-', 100)); + sb.AppendLine(); + + int currentGroupId = -1; + + foreach (DataRow row in dbContext.Rows) + { + int groupId = Convert.ToInt32(row["组ID"]); + string timestamp = row["时间戳"]?.ToString() ?? ""; + string message = row["日志内容"]?.ToString() ?? ""; + string type = row["类型"]?.ToString() ?? ""; + int lineNumber = Convert.ToInt32(row["行号"]); + + if (groupId != currentGroupId) + { + if (currentGroupId != -1) + { + sb.AppendLine(new string('-', 100)); + sb.AppendLine(); + } + currentGroupId = groupId; + sb.AppendLine($"--- 数据库指令组 {groupId} ---"); + } + + string prefix = type.StartsWith("数据库指令") ? "▶▶▶ " : " "; + sb.AppendLine($"{prefix}[{timestamp}] 行{lineNumber} [{type}]"); + + // 显示消息内容 + string[] lines = message.Split(new[] { Environment.NewLine }, StringSplitOptions.None); + foreach (string line in lines) + { + if (type.StartsWith("数据库指令")) + sb.AppendLine($" {line}"); + else + sb.AppendLine($" {line}"); + } + sb.AppendLine(); + } + + return sb.ToString(); + } + + /// + /// 只提取包含数据库指令的条目(不包含上下文) + /// + public DataTable ExtractDbCommandsOnly(DataTable logTable) + { + var resultTable = new DataTable("DbCommandsOnly"); + resultTable.Columns.Add("时间戳", typeof(string)); + resultTable.Columns.Add("日志内容", typeof(string)); + resultTable.Columns.Add("匹配关键词", typeof(string)); + resultTable.Columns.Add("条目索引", typeof(int)); + + for (int i = 0; i < logTable.Rows.Count; i++) + { + string message = logTable.Rows[i]["日志内容"]?.ToString() ?? ""; + string matchedKeyword = ContainsDbKeyword(message); + + if (!string.IsNullOrEmpty(matchedKeyword)) + { + resultTable.Rows.Add( + logTable.Rows[i]["时间戳"]?.ToString() ?? "", + message, + matchedKeyword, + i + ); + } + } + + return resultTable; + } + } + +} \ No newline at end of file diff --git a/MainWindow.xaml b/MainWindow.xaml new file mode 100644 index 0000000..751c2e0 --- /dev/null +++ b/MainWindow.xaml @@ -0,0 +1,69 @@ + + + + + + + + + +