You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
840 lines
32 KiB
840 lines
32 KiB
|
2 weeks ago
|
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(
|
||
|
|
@"^(?<datetime>\d{4}/\d{1,2}/\d{1,2}\s+\d{1,2}:\d{1,2}:\d{1,2})\s*(?<message>.*)$",
|
||
|
|
RegexOptions.Compiled
|
||
|
|
);
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 解析日志文件,以行首时间戳作为分隔符合并多行日志
|
||
|
|
/// </summary>
|
||
|
|
public DataTable ParseLogFile(string filePath)
|
||
|
|
{
|
||
|
|
return ParseLogFile(filePath, Encoding.Default);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 解析日志文件(支持指定编码)
|
||
|
|
/// </summary>
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 添加日志条目到 DataTable
|
||
|
|
/// </summary>
|
||
|
|
private void AddLogEntry(DataTable table, string timestamp, string message)
|
||
|
|
{
|
||
|
|
// 去除多余的空行
|
||
|
|
string cleanMessage = message.Trim();
|
||
|
|
if (!string.IsNullOrEmpty(cleanMessage))
|
||
|
|
{
|
||
|
|
table.Rows.Add(timestamp, cleanMessage);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 创建日志 DataTable 结构
|
||
|
|
/// </summary>
|
||
|
|
private DataTable CreateLogTable()
|
||
|
|
{
|
||
|
|
var table = new DataTable("LogData");
|
||
|
|
table.Columns.Add("时间戳", typeof(string));
|
||
|
|
table.Columns.Add("日志内容", typeof(string));
|
||
|
|
return table;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 解析日志并显示详细信息(用于调试)
|
||
|
|
/// </summary>
|
||
|
|
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++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 检查特定日志文件,显示每行的解析情况(调试用)
|
||
|
|
/// </summary>
|
||
|
|
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();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 从日志中提取包含数据库指令关键词的条目及其前后N条日志
|
||
|
|
/// </summary>
|
||
|
|
/// <param name="logTable">原始日志DataTable</param>
|
||
|
|
/// <param name="contextLines">前后文行数,默认为3</param>
|
||
|
|
/// <returns>包含数据库指令及其上下文的DataTable</returns>
|
||
|
|
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<int>();
|
||
|
|
var keywordMatches = new Dictionary<int, string>(); // 记录每个条目匹配的关键词
|
||
|
|
|
||
|
|
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<int>();
|
||
|
|
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<DataRow>();
|
||
|
|
|
||
|
|
// 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<DataRow>();
|
||
|
|
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<DataRow>();
|
||
|
|
|
||
|
|
// 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<DataRow>();
|
||
|
|
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<DataRow>();
|
||
|
|
|
||
|
|
// 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<DataRow>();
|
||
|
|
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<DataRow>();
|
||
|
|
|
||
|
|
// 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<DataRow>();
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
/// <summary>
|
||
|
|
/// 检查日志内容是否包含数据库指令关键词,返回匹配的关键词
|
||
|
|
/// </summary>
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 提取数据库指令及其上下文(分组版本)
|
||
|
|
/// </summary>
|
||
|
|
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<int>();
|
||
|
|
var keywordMap = new Dictionary<int, string>();
|
||
|
|
|
||
|
|
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<int>();
|
||
|
|
|
||
|
|
foreach (int dbIndex in dbIndices)
|
||
|
|
{
|
||
|
|
if (processedIndices.Contains(dbIndex))
|
||
|
|
continue;
|
||
|
|
|
||
|
|
groupId++;
|
||
|
|
var indicesInGroup = new List<int>();
|
||
|
|
|
||
|
|
// 添加数据库指令本身
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 获取数据库指令统计信息
|
||
|
|
/// </summary>
|
||
|
|
public Dictionary<string, int> GetDbCommandStatistics(DataTable logTable)
|
||
|
|
{
|
||
|
|
var stats = new Dictionary<string, int>();
|
||
|
|
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 导出数据库指令及其上下文为格式化字符串
|
||
|
|
/// </summary>
|
||
|
|
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();
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 只提取包含数据库指令的条目(不包含上下文)
|
||
|
|
/// </summary>
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|