解析日志文件,模棱两可的定界符

时间:2019-05-22 14:32:51

标签: c# parsing

我必须解析一个日志文件,并且不确定如何最好地利用每一行的不同部分。我面临的问题是原始开发人员使用':'来分隔令牌,这有点白痴,因为该行包含本身包含':'的时间戳!

示例行如下所示:

transaction_date_time:[systemid]:sending_system:receiving_system:data_length:data:[ws_name]

2019-05-08 15:03:13:494|2019-05-08 15:03:13:398:[192.168.1.2]:ABC:DEF:67:cd71f7d9a546ec2b32b,AACN90012001000012,OPNG:[WebService.SomeName.WebServiceModule::WebServiceName]

我没有问题,可以读取日志文件并访问每一行,但是不确定如何解析?

2 个答案:

答案 0 :(得分:0)

使用正则表达式,我能够解析所有内容。数据似乎来自excel,因为秒的派系使用冒号而不是句点。 C#不喜欢冒号,所以我不得不用句号代替冒号。我还从右到左进行了解析,以解决结肠问题。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;


namespace ConsoleApplication3
{
    class Program1
    {
        const string FILENAME = @"c:\temp\test.txt";
        static void Main(string[] args)
        {
            string line = "";
            int rowCount = 0;
            StreamReader reader = new StreamReader(FILENAME);
            string pattern = @"^(?'time'.*):\[(?'systemid'[^\]]+)\]:(?'sending'[^:]+):(?'receiving'[^:]+):(?'length'[^:]+):(?'data'[^:]+):\[(?'ws_name'[^\]]+)\]";

            while ((line = reader.ReadLine()) != null)
            {
                line = line.Trim();
                if (line.Length > 0)
                {
                    if (++rowCount != 1) //skip header row
                    {
                        Log_Data newRow = new Log_Data();
                        Log_Data.logData.Add(newRow);
                        Match match = Regex.Match(line, pattern, RegexOptions.RightToLeft);

                        newRow.ws_name = match.Groups["ws_name"].Value;
                        newRow.data = match.Groups["data"].Value;
                        newRow.length = int.Parse(match.Groups["length"].Value);
                        newRow.receiving_system = match.Groups["receiving"].Value;
                        newRow.sending_system = match.Groups["sending"].Value;
                        newRow.systemid  = match.Groups["systemid"].Value;
                        //end data is first then start date is second
                        string[] date = match.Groups["time"].Value.Split(new char[] {'|'}).ToArray();
                        string replacePattern = @"(?'leader'.+):(?'trailer'\d+)";
                        string stringDate = Regex.Replace(date[1], replacePattern, "${leader}.${trailer}", RegexOptions.RightToLeft);
                        newRow.startDate = DateTime.Parse(stringDate);
                        stringDate = Regex.Replace(date[0], replacePattern, "${leader}.${trailer}", RegexOptions.RightToLeft);
                        newRow.endDate = DateTime.Parse(stringDate );
                    }
                }
            }


        }
    }
    public class Log_Data
    {
        public static List<Log_Data> logData = new List<Log_Data>();

        public DateTime startDate { get; set; } //transaction_date_time:[systemid]:sending_system:receiving_system:data_length:data:[ws_name]
        public DateTime endDate { get; set; }
        public string systemid { get; set; }
        public string sending_system { get; set; }
        public string receiving_system { get; set; }
        public int length { get; set; }
        public string data { get; set; }
        public string ws_name { get; set; }
    }
}

答案 1 :(得分:0)

由于输入字符串不是完全可分割的 ,由于定界符char也是内容的一部分,因此可以使用简单的regex表达式代替。
简单但可能足够快,即使使用默认设置也是如此。

输入字符串的不同部分可以通过以下捕获组分开:

string pattern = @"^(.*?)\|(.*?):\[(.*?)\]:(.*?):(.*?):(\d+):(.*?):\[(.*)\]$";

这将为您提供8组+ 1(Group[0]),其中包含整个字符串。

使用Regex类,只需传递一个字符串即可解析(名称为line,在此)并将正则表达式(名称为pattern)传递给Match()方法,使用默认值设置:

var result = Regex.Match(line, pattern);

Groups.Value属性返回每个捕获组的结果。例如,两个日期:

var dateEnd = DateTime.ParseExact(result.Groups[1].Value, "yyyy-MM-dd hh:mm:ss:ttt", CultureInfo.InvariantCulture),
var dateStart = DateTime.ParseExact(result.Groups[2].Value, "yyyy-MM-dd hh:mm:ss:ttt", CultureInfo.InvariantCulture),

IpAddress提取为:\[(.*?)\]
您可以为此分组命名,这样可以更清楚地知道该值是指什么。只需添加前缀为?并括在<>或单引号'中的字符串即可命名分组:

...\[(?<IpAddress>.*?)\]...

但是请注意,命名组会修改Regex.Groups索引:未命名的组将首先插入,命名后的组将插入后面。因此,仅命名IpAddress组将使其成为最后一项Groups[8]。当然,您可以命名所有组,并且将保留索引。

var hostAddress = IPAddress.Parse(result.Groups["IpAddress"].Value);

此模式应允许 medium 机器每秒解析130,000~150,000个字符串。
您必须对其进行测试才能找到 perfect 模式。例如,第一个匹配项(对应于第一个日期):(.*?)\|,如果不贪心则更快(使用*?惰性量词)。最后一场比赛的对立面:\[(.*)\]jdweng使用的模式比这里使用的模式还要快。

有关每个令牌的用法和含义的详细说明,请参见Regex101