252RB日志查询工具
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

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