要进一步增强MES日志分析工具,使其支持复杂的依赖规则,包括时间窗口、前置接口的最小调用次数,以及多个关键字段的联合匹配

要进一步增强MES日志分析工具,使其支持复杂的依赖规则,包括时间窗口、前置接口的最小调用次数,以及多个关键字段的联合匹配,我们需要修改数据结构、分析逻辑和配置文件格式。以下是实现思路和更新后的C#代码,基于之前的`EnhancedMESLogAnalyzer.cs`,并提供中文解释。

 实现思路
1. 支持复杂规则:
   - 时间窗口:允许指定前置接口调用必须在目标接口调用前的特定时间范围内(如前1小时内)。
   - 最小调用次数:要求前置接口针对相同关键字段值至少成功调用指定次数(如至少2次)。
   - 多关键字段联合匹配:支持多个关键字段(如`TraySn`和`LotSn`)的联合匹配,确保前置接口和目标接口的这些字段值一致。

2. 修改数据结构:
   - 更新`DependencyRule`类,添加:
     - `TimeWindowSeconds`:时间窗口(秒),限制前置接口调用时间范围。
     - `MinCallCount`:前置接口的最小成功调用次数。
     - `KeyFields`:支持多个关键字段(列表),替代单一`KeyField`。
   - 保持`PrerequisiteStatus`和`AnalysisResult`结构,适应多字段匹配。

3. 更新配置文件:
   - 修改`config.json`格式,支持新的字段:
     - `TimeWindowSeconds`(可选,默认为无限)。
     - `MinCallCount`(可选,默认为1)。
     - `KeyFields`(字符串列表,替代`KeyField`)。
   - 示例配置文件:
     ```json
     [
       {
         "TargetApi": "/api/eapmanagement/ProductOut/CheckProductOut",
         "PrerequisiteApis": ["/api/eapmanagement/TraySN/TraySNBind"],
         "KeyFields": ["TraySn", "LotSn"],
         "ErrorCode": "EAPManagement:04448",
         "TimeWindowSeconds": 3600,
         "MinCallCount": 1
       }
     ]
     ```

4. 分析逻辑调整:
   - 在`Analyze`方法中:
     - 检查前置接口调用是否在指定时间窗口内。
     - 验证成功调用次数是否达到`MinCallCount`。
     - 确保所有`KeyFields`的值在前置接口和目标接口中匹配。
   - 记录不符合规则的前置接口状态(未调用、调用失败或不足次数)。

5. 报告增强:
   - 在报告中显示时间窗口和最小调用次数要求。
   - 注明多字段匹配的实际值(如`TraySn`和`LotSn`)。
   - 详细说明失败原因(如“调用次数不足”或“时间窗口外”)。

 更新后的C#代码
以下是支持复杂规则的代码,基于之前的实现,添加了时间窗口、最小调用次数和多关键字段支持。

```x-csharp
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace ComplexMESLogAnalyzer
{
    public class LogEntry
    {
        public DateTime Timestamp { get; set; }
        public string LogLevel { get; set; }
        public string Uri { get; set; }
        public JObject Request { get; set; }
        public JObject Response { get; set; }
        public int Time { get; set; }
        public string Status { get; set; }
        public string RawMessage { get; set; }
    }

    public class DependencyRule
    {
        public string TargetApi { get; set; }
        public List<string> PrerequisiteApis { get; set; }
        public List<string> KeyFields { get; set; } // 支持多个关键字段
        public string ErrorCode { get; set; }
        public int? TimeWindowSeconds { get; set; } // 时间窗口(秒),可选
        public int MinCallCount { get; set; } = 1; // 最小调用次数,默认为1
    }

    public class PrerequisiteStatus
    {
        public string PrerequisiteApi { get; set; }
        public string Status { get; set; } // "NotCalled", "Failed", "Success", "InsufficientCalls", "OutOfTimeWindow"
        public List<LogEntry> FailedCalls { get; set; }
        public int SuccessfulCallCount { get; set; }
    }

    public class AnalysisResult
    {
        public LogEntry ErrorEntry { get; set; }
        public List<PrerequisiteStatus> PrerequisiteStatuses { get; set; }
        public Dictionary<string, string> KeyValues { get; set; } // 存储多个关键字段值
        public string Details { get; set; }
    }

    public class MESLogAnalyzer
    {
        private readonly List<LogEntry> logEntries = new List<LogEntry>();
        private List<DependencyRule> rules = new List<DependencyRule>();

        public void LoadConfig(string configPath)
        {
            try
            {
                string configJson = File.ReadAllText(configPath);
                rules = JsonConvert.DeserializeObject<List<DependencyRule>>(configJson);
            }
            catch (Exception ex)
            {
                throw new Exception($"Failed to load config file: {ex.Message}");
            }
        }

        public void LoadLogFile(string filePath)
        {
            string[] lines = File.ReadAllLines(filePath);
            Regex logPattern = new Regex(@"\[(.*?)\]\s*(\w+)\s*-\s*PostSync,uri:(.*?),request:(.*?),response:(.*?),time:(\d+),(\w+\s+\w+\s+\w+)");

            foreach (string line in lines)
            {
                Match match = logPattern.Match(line);
                if (match.Success)
                {
                    try
                    {
                        var entry = new LogEntry
                        {
                            Timestamp = DateTime.Parse(match.Groups[1].Value),
                            LogLevel = match.Groups[2].Value,
                            Uri = match.Groups[3].Value,
                            Request = JsonConvert.DeserializeObject<JObject>(match.Groups[4].Value),
                            Response = JsonConvert.DeserializeObject<JObject>(match.Groups[5].Value),
                            Time = int.Parse(match.Groups[6].Value),
                            Status = match.Groups[7].Value,
                            RawMessage = line
                        };
                        logEntries.Add(entry);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($"Failed to parse line: {line}\nError: {ex.Message}");
                    }
                }
            }
        }

        public List<AnalysisResult> Analyze()
        {
            var results = new List<AnalysisResult>();

            foreach (var entry in logEntries.Where(e => e.Response["isError"]?.Value<bool>() == true))
            {
                foreach (var rule in rules)
                {
                    if (entry.Uri.EndsWith(rule.TargetApi) && entry.Response["code"]?.Value<string>() == rule.ErrorCode)
                    {
                        // 提取所有关键字段值
                        var keyValues = new Dictionary<string, string>();
                        bool allKeyFieldsValid = true;

                        foreach (var keyField in rule.KeyFields)
                        {
                            string keyValue = entry.Request[keyField]?.Value<string>();
                            if (string.IsNullOrEmpty(keyValue))
                            {
                                allKeyFieldsValid = false;
                                break;
                            }
                            keyValues[keyField] = keyValue;
                        }

                        if (!allKeyFieldsValid)
                            continue;

                        var prereqStatuses = new List<PrerequisiteStatus>();
                        bool hasIssue = false;

                        foreach (var prereqApi in rule.PrerequisiteApis)
                        {
                            // 查找前置接口调用,匹配所有关键字段
                            var prereqCalls = logEntries
                                .Where(e => e.Timestamp < entry.Timestamp &&
                                            e.Uri.EndsWith(prereqApi) &&
                                            rule.KeyFields.All(kf => e.Request[kf]?.Value<string>() == keyValues[kf]))
                                .ToList();

                            var status = new PrerequisiteStatus
                            {
                                PrerequisiteApi = prereqApi,
                                FailedCalls = new List<LogEntry>(),
                                SuccessfulCallCount = 0
                            };

                            if (!prereqCalls.Any())
                            {
                                status.Status = "NotCalled";
                                hasIssue = true;
                            }
                            else
                            {
                                // 应用时间窗口过滤
                                var validCalls = prereqCalls;
                                if (rule.TimeWindowSeconds.HasValue)
                                {
                                    var timeWindow = TimeSpan.FromSeconds(rule.TimeWindowSeconds.Value);
                                    validCalls = prereqCalls
                                        .Where(c => entry.Timestamp - c.Timestamp <= timeWindow)
                                        .ToList();
                                }

                                var successfulCalls = validCalls
                                    .Where(c => c.Response["isError"]?.Value<bool>() == false)
                                    .ToList();
                                var failedCalls = validCalls
                                    .Where(c => c.Response["isError"]?.Value<bool>() == true)
                                    .ToList();

                                status.SuccessfulCallCount = successfulCalls.Count;
                                status.FailedCalls = failedCalls;

                                if (successfulCalls.Count >= rule.MinCallCount)
                                {
                                    status.Status = "Success";
                                }
                                else if (validCalls.Any())
                                {
                                    status.Status = failedCalls.Any() ? "Failed" : "InsufficientCalls";
                                    hasIssue = true;
                                }
                                else
                                {
                                    status.Status = "OutOfTimeWindow";
                                    hasIssue = true;
                                }
                            }

                            prereqStatuses.Add(status);
                        }

                        if (hasIssue)
                        {
                            var details = new List<string>
                            {
                                $"Error in {rule.TargetApi} for {string.Join(", ", keyValues.Select(kv => $"{kv.Key}: '{kv.Value}'"))} likely caused by issues with prerequisite APIs:"
                            };
                            foreach (var status in prereqStatuses)
                            {
                                details.Add($"- {status.PrerequisiteApi}: {status.Status} (Successful calls: {status.SuccessfulCallCount}, Required: {rule.MinCallCount})");
                                if (status.Status == "Failed" && status.FailedCalls.Any())
                                {
                                    details.Add("  Failed calls:");
                                    foreach (var failedCall in status.FailedCalls)
                                    {
                                        details.Add($"    Timestamp: {failedCall.Timestamp}, Error: {failedCall.Response["message"]?.Value<string>()}");
                                    }
                                }
                                if (rule.TimeWindowSeconds.HasValue)
                                {
                                    details.Add($"  Time Window: Within {rule.TimeWindowSeconds.Value} seconds");
                                }
                            }

                            results.Add(new AnalysisResult
                            {
                                ErrorEntry = entry,
                                PrerequisiteStatuses = prereqStatuses,
                                KeyValues = keyValues,
                                Details = string.Join("\n", details)
                            });
                        }
                    }
                }
            }

            return results;
        }

        public void GenerateReport(List<AnalysisResult> results, string outputPath)
        {
            using (StreamWriter writer = new StreamWriter(outputPath))
            {
                writer.WriteLine("MES Log Analysis Report");
                writer.WriteLine("=====================");
                writer.WriteLine($"Generated on: {DateTime.Now}");
                writer.WriteLine($"Total Errors Analyzed: {results.Count}");
                writer.WriteLine();

                foreach (var result in results)
                {
                    writer.WriteLine($"Error Timestamp: {result.ErrorEntry.Timestamp}");
                    writer.WriteLine($"Error API: {result.ErrorEntry.Uri}");
                    writer.WriteLine($"Error Message: {result.ErrorEntry.Response["message"]?.Value<string>()}");
                    writer.WriteLine($"Key Field Values: {string.Join(", ", result.KeyValues.Select(kv => $"{kv.Key}: {kv.Value}"))}");
                    writer.WriteLine($"Details:\n{result.Details}");
                    writer.WriteLine($"Raw Log: {result.ErrorEntry.RawMessage}");
                    writer.WriteLine();
                }

                if (results.Count == 0)
                    writer.WriteLine("No errors matched the defined dependency rules.");
            }
        }

        public static void Main(string[] args)
        {
            if (args.Length < 3)
            {
                Console.WriteLine("Usage: ComplexMESLogAnalyzer <logFilePath> <configFilePath> <outputReportPath>");
                return;
            }

            string logFilePath = args[0];
            string configFilePath = args[1];
            string outputPath = args[2];

            try
            {
                var analyzer = new MESLogAnalyzer();
                analyzer.LoadConfig(configFilePath);
                analyzer.LoadLogFile(logFilePath);
                var results = analyzer.Analyze();
                analyzer.GenerateReport(results, outputPath);

                Console.WriteLine($"Analysis complete. Report generated at {outputPath}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex.Message}");
            }
        }
    }
}
```

 配置文件示例
创建一个`config.json`文件,包含时间窗口、最小调用次数和多关键字段:

```json
[
    {
        "TargetApi": "/api/eapmanagement/ProductOut/CheckProductOut",
        "PrerequisiteApis": [
            "/api/eapmanagement/TraySN/TraySNBind",
            "/api/eapmanagement/FixtureWithDut/FixtureBindDut"
        ],
        "KeyFields": ["TraySn", "LotSn"],
        "ErrorCode": "EAPManagement:04448",
        "TimeWindowSeconds": 3600,
        "MinCallCount": 1
    },
    {
        "TargetApi": "/api/eapmanagement/ProductIn/CheckProductIn",
        "PrerequisiteApis": [
            "/api/eapmanagement/FixtureWithDut/FixtureUnBindEquip"
        ],
        "KeyFields": ["TraySn"],
        "ErrorCode": "EAPManagement:04448",
        "TimeWindowSeconds": 7200,
        "MinCallCount": 2
    },
    {
        "TargetApi": "/api/eapmanagement/Parameter/UploadParameterDatas",
        "PrerequisiteApis": [
            "/api/eapmanagement/TraySN/TraySNBind"
        ],
        "KeyFields": ["LotSn"],
        "ErrorCode": "EAPManagement:04448",
        "MinCallCount": 1
    }
]
```

 使用方法
1. 环境准备:
   - 安装.NET SDK。
   - 添加`Newtonsoft.Json` NuGet包。

2. 准备配置文件:
   - 创建`config.json`,定义依赖规则,包括`KeyFields`、`TimeWindowSeconds`和`MinCallCount`。

3. 编译和运行:
   - 保存代码为`ComplexMESLogAnalyzer.cs`。
   - 编译并运行:
     ```bash
     dotnet run MES.log config.json output_report.txt
     ```
     - `MES.log`:日志文件路径
     - `config.json`:配置文件路径
     - `output_report.txt`:输出报告路径

4. 输出:
   - 报告列出错误详情,包括多关键字段值、时间窗口、最小调用次数要求,以及每个前置接口的状态(未调用、调用失败、调用次数不足、时间窗口外或成功)。

 示例输出
基于提供的MES日志和配置文件,报告可能如下:

```
MES Log Analysis Report
=====================
Generated on: 2025-05-17 22:41:00
Total Errors Analyzed: 2

Error Timestamp: 2025-05-16 21:55:05
Error API: http://172.16.6.101:44388/api/eapmanagement/ProductOut/CheckProductOut
Error Message: 该托盘G-1-V400L-W2-H-2024-2-008没有需要出站的器件!
Key Field Values: TraySn: G-1-V400L-W2-H-2024-2-008, LotSn: P25190002
Details:
Error in /api/eapmanagement/ProductOut/CheckProductOut for TraySn: 'G-1-V400L-W2-H-2024-2-008', LotSn: 'P25190002' likely caused by issues with prerequisite APIs:
- /api/eapmanagement/TraySN/TraySNBind: NotCalled (Successful calls: 0, Required: 1)
  Time Window: Within 3600 seconds
- /api/eapmanagement/FixtureWithDut/FixtureBindDut: NotCalled (Successful calls: 0, Required: 1)
  Time Window: Within 3600 seconds
Raw Log: [2025-05-16 21:55:05,908] INFO - PostSync,uri:http://172.16.6.101:44388/api/eapmanagement/ProductOut/CheckProductOut,...

Error Timestamp: 2025-05-16 10:37:09
Error API: http://172.16.6.101:44388/api/eapmanagement/Parameter/UploadParameterDatas
Error Message: 设备8H-ECHJZZ-01未在产品工艺工序中绑定!
Key Field Values: LotSn: P25190002
Details:
Error in /api/eapmanagement/Parameter/UploadParameterDatas for LotSn: 'P25190002' likely caused by issues with prerequisite APIs:
- /api/eapmanagement/TraySN/TraySNBind: Failed (Successful calls: 0, Required: 1)
  Failed calls:
    Timestamp: 2025-05-16 08:42:29, Error: 托盘G-1-1-H-2024-11-013未绑定器件!
Raw Log: [2025-05-16 10:37:09,009] INFO - PostSync,uri:http://172.16.6.101:44388/api/eapmanagement/Parameter/UploadParameterDatas,...
```

 代码说明
1. 数据结构更新:
   - DependencyRule:
     - `KeyFields`:替换`KeyField`,支持多字段列表。
     - `TimeWindowSeconds`:可选,定义时间窗口(秒)。
     - `MinCallCount`:最小成功调用次数,默认为1。
   - PrerequisiteStatus:
     - 添加`SuccessfulCallCount`,记录实际成功调用次数。
     - 新增状态`InsufficientCalls`(调用次数不足)和`OverTimeWindow`(时间窗口外)。
   - AnalysisResult:
     - `KeyValues`:存储多个关键字段的名称和值。

2. 分析逻辑:
   - 多关键字段匹配:
     - 提取目标接口请求中的所有`KeyFields`值,存储在`KeyValues`字典。
     - 前置接口调用需匹配所有`KeyFields`值。
   - 时间窗口:
     - 如果`TimeWindowSeconds`存在,过滤调用记录,确保时间差在指定范围内。
     - 如果无有效调用,标记为`OutOfTimeWindow`。
   - 最小调用次数:
     - 计算时间窗口内的成功调用次数,比较`MinCallCount`。
     - 如果成功次数不足,标记为`InsufficientCalls`(若有调用)或`Failed`(若全失败)。
   - 状态判定:
     - `NotCalled`:无调用记录。
     - `OutOfTimeWindow`:有调用但不在时间窗口内。
     - `Failed`:有调用但全失败。
     - `InsufficientCalls`:有调用但成功次数不足。
     - `Success`:成功次数满足要求。

3. 报告生成:
   - 显示所有`KeyFields`的值(如`TraySn: xxx, LotSn: yyy`)。
   - 每个前置接口显示状态、成功调用次数和要求次数。
   - 注明时间窗口(如果有)。
   - 失败调用列出时间戳和错误消息。

 中文解释
 功能改进
- 时间窗口:
  - 用户可指定前置接口调用必须在目标接口前的多少秒内(如3600秒=1小时)。
  - 例如,`TraySN/TraySNBind`必须在`ProductOut/CheckProductOut`前1小时内调用,否则标记为`OutOfTimeWindow`。
- 最小调用次数:
  - 要求前置接口成功调用至少N次(如2次),否则标记为`InsufficientCalls`。
  - 适用于需要多次初始化的场景,如多次绑定操作。
- 多关键字段联合匹配:
  - 支持匹配多个字段(如`TraySn`和`LotSn`),确保前置接口和目标接口的这些字段值一致。
  - 提高匹配精度,适应复杂依赖关系。

 代码变化
- DependencyRule:
  - `KeyFields`支持多字段,`TimeWindowSeconds`和`MinCallCount`增加规则灵活性。
- Analyze:
  - 验证所有`KeyFields`值,匹配时要求完全一致。
  - 应用时间窗口过滤,检查`entry.Timestamp - call.Timestamp <= TimeWindow`。
  - 计算成功调用次数,比较`MinCallCount`。
- GenerateReport:
  - 显示多字段值、时间窗口和调用次数要求。
  - 新增状态说明(如`InsufficientCalls`)和失败调用详情。

 使用场景
- 时间窗口:确保前置接口调用不过期,如绑定操作需在1小时内完成。
- 最小调用次数:适用于需要多次调用的场景,如多次设备初始化。
- 多关键字段:处理复杂流程,如同时依赖托盘号(`TraySn`)和批次号(`LotSn`)的接口。

 扩展功能
1. 更复杂条件:
   - 支持`KeyFields`的条件匹配(如正则表达式或部分匹配)。
   - 添加调用顺序要求(如接口A必须在接口B前调用)。
2. 动态规则:
   - 支持运行时修改`config.json`,无需重启。
   - 添加命令行选项指定临时规则。
3. 性能优化:
   - 对`KeyFields`值建立索引,加速多字段匹配。
   - 使用并行处理分析多个错误。
4. 增强报告:
   - 支持JSON或HTML报告格式。
   - 添加统计信息(如各接口的错误率)。

 注意事项
- 配置文件:
  - 确保`KeyFields`中的字段名与日志请求JSON一致。
  - `TimeWindowSeconds`和`MinCallCount`需合理设置,避免过于严格。
- 日志格式:
  - 假设日志格式与样例一致,若不同,需调整正则表达式。
- 错误覆盖:
  - 用户需根据MES系统补充规则,覆盖所有场景。
- 性能:
  - 多关键字段匹配可能增加计算量,建议对大日志文件优化查询。

这个工具通过支持时间窗口、最小调用次数和多关键字段联合匹配,大幅提升了规则的灵活性和诊断精度,适用于复杂的MES日志分析场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhxup606

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值