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; } } }