解析日志文件

时间:2014-04-09 21:25:12

标签: regex powershell select filter paragraph

我正在尝试编写一个脚本来简化在特定应用程序日志文件中搜索特定信息的过程。所以我想也许有一种方法可以将它们转换为XML树,而且我的开始还不错....但问题是,如果你问我,应用程序日志文件绝对是一团糟

有些条目很简单

2014/04/09 11:27:03 INFO  Some.code.function - Doing stuff

理想情况下,我想把上面的内容变成这样的东西

    <Message>
    <Date>2014/04/09</Date>
    <Time>11:48:38</Time>
    <Type>INFO</Type>
    <Source>Some.code.function</Source>
    <Sub>Doing stuff</Sub>
    </Message>

其他条目是这样的,其中有附加信息和换行符

2014/04/09 11:27:04 INFO  Some.code.function - Something happens

changes: 
this stuff happened

我想将最后一个块转换成上面的内容,但是将其他信息添加到一个部分

    <Message>
    <Date>2014/04/09</Date>
    <Time>11:48:38</Time>
    <Type>INFO</Type>
    <Source>Some.code.function</Source>
    <Sub>Doing stuff</Sub>
    <details>changes: 
this stuff happened</details>
    </Message>

然后是其他消息,错误将以

的形式出现
2014/04/09 11:27:03 ERROR  Some.code.function - Something didn't work right
Log Entry: LONGARSEDGUID
Error Code: E3145
Application: Name
Details:
message information etc etc and more line breaks, this part of the message may add up to an unknown number of lines before the next entry

这个最后一个块我想转换为上面的例子的最后一个,但是为日志条目,错误代码,应用程序添加XML节点,再次,像这样的细节

    <Message>
    <Date>2014/04/09</Date>
    <Time>11:48:38</Time>
    <Type>ERROR  </Type>
    <Source>Some.code.function</Source>
    <Sub>Something didn't work right</Sub>
    <Entry>LONGARSEDGUID</Entry>
    <Code>E3145</Code>
    <Application>Name</Application>
    <details>message information etc etc and more line breaks, this part of the message may add up to an unknown number of lines before the next entry</details>
    </Message>

现在我知道Select-String有一个上下文选项,可以让我在我过滤的行之后选择一些行,问题是,这不是一个常数。

我认为正则表达式也可以在日期字符串之前选择段落块,但正则表达式不是我的强点,我认为可能有更好的方法,因为一个常量是新的条目以日期字符串

开头

这个想法是要么将这些分解为xml或各种各样的表,然后从那里我希望它可能需要最后一次或过滤不相关或重复的消息更容易

出于隐私原因删除/替换一些信息之后,我有一个我刚扔在pastebin上的样本

http://pastebin.com/raw.php?i=M9iShyT2

2 个答案:

答案 0 :(得分:1)

处理此类文件的一种可能方法是逐行处理它们。每个日志条目都以时间戳开头,并在出现以时间戳开头的下一行时结束,因此您可以执行以下操作:

Get-Content 'C:\path\to\your.log' | % {
  if ($_ -match '^\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}') {
    if ($logRecord) {
      # If a current log record exists, it is complete now, so it can be added
      # to your XML or whatever, e.g.:

      $logRecord -match '^(\d{4}/\d{2}/\d{2}) (\d{2}:\d{2}:\d{2}) (\S+) ...'

      $message = $xml.CreateElement('Message')

      $date = $xml.CreateElement('Date')
      $date.InnerText = $matches[1]
      $message.AppendChild($date)

      $time = $xml.CreateElement('Time')
      $time.InnerText = $matches[2]
      $message.AppendChild($time)

      $type = $xml.CreateElement('Type')
      $type.InnerText = $matches[3]
      $message.AppendChild($type)

      ...

      $xml.SelectSingleNode('...').AppendChild($message)
    }
    $logRecord = $_          # start new record
  } else {
    $logRecord += "`r`n$_"   # append to current record
  }
}

答案 1 :(得分:1)

对不起,这有点晚了,我在那里工作了一段时间(努力工作期待我在他们的角钱里有所作为)。我最终得到了类似于Ansgar Wiechers解决方案的东西,但是将事物格式化为对象并将它们收集到一个数组中。它不管理您稍后添加的XML,但是这为您提供了一个很好的对象数组,可用于其他记录。我将在这里解释一下主要的RegEx系列,我将在线评论它的实用性。

'(^ \ d {4} / \ d {2} / \ d {2} \ d {2}:\ d {2}:\ d {2})[\ d +?](\ w +? ){1,2}(。+?) - (。+)$'是检测新记录开始的正则表达式。我开始解释它,但是你可能有更好的资源来学习RegEx而不是我向我解释它。有关完整细分和示例,请参阅this RegEx101.com link

$Records=@() #Create empty array that we will populate with custom objects later
$Event = $Null #make sure nothing in $Event to give script a clean start
Get-Content 'C:\temp\test1.txt' | #Load file, and start looping through it line-by-line.
?{![string]::IsNullOrEmpty($_)}|% { #Filter out blank lines, and then perform the following on each line
  if ($_ -match '(^\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}) \[\d+?] (\w+?) {1,2}(.+?) - (.+)$') { #New Record Detector line! If it finds this RegEx match, it means we're starting a new record.
    if ($Event) { #If there's already a record in progress, add it to the Array
      $Records+=$Event
    }
    $Event = New-Object PSObject -Property @{ #Create a custom PSObject object with these properties that we just got from that RegEx match
DateStamp = [datetime](get-date $Matches[1]) #We convert the date/time stamp into an actual DateTime object. That way sorting works better, and you can compare it to real dates if needed.
Type = $Matches[2]
Source = $Matches[3]
Message = $Matches[4]}

好的,这里的原因暂停一点。 $Matches不是我定义的,为什么我引用它? 。当PowerShell从RegEx表达式获取匹配时,它会自动将结果匹配存储在$ Matches中。因此,我们在括号中匹配的所有组都变为$ Matches [1],$ Matches [2],依此类推。是的,它是一个数组,并且有一个$ Matches [0],但这是匹配的整个字符串,而不仅仅是匹配的组。我们现在返回您定期安排的脚本......

  } else { #End of the 'New Record' section. If it's not a new record if does the following
    if($_ -match "^((?:[^ ^\[])(?:\w| |\.)+?):(.*)$"){ 

RegEx再次匹配。它首先说明这必须是带有克拉字符(^)的字符串的开头。然后它说(在(?:<stuff>)格式注明的非捕获组中,这对我的目的来说仅仅意味着它不会出现在$ Matches中)[^ \[];这意味着下一个字符不能是空格或开括号(用a转义),只是为了加快速度并跳过这些检查。如果你在括号[]中有东西并且第一个字符是克拉,则意味着“与这些括号中的任何内容都不匹配”。

我实际上只是将下一部分更改为包含句点,并使用\ w而不是[a-zA-Z0-9],因为它基本上是相同的但更短。 \ w是RegEx中的“单词字符”,包括字母,数字和下划线。我不确定为什么下划线被认为是一个单词的一部分,但我不制定规则我只是玩游戏。我使用的是[a-zA-Z0-9],它匹配'a'和'z'(小写)之间的任何东西,'A'和'Z'(大写)之间的任何东西,以及'0'和'9'之间的任何东西。存在包含下划线字符的风险\ w更短更简单。

然后是这个RegEx的实际捕获部分。这有两组,第一组是字母,数字,下划线,空格和句点(用\来转义,因为'。'它自己匹配任何字符)。然后冒号。然后是第二组,直到行结束时为其他所有内容。

        $Field = $Matches[1] #Everything before the colon is the name of the field
        $Value = $Matches[2].trim() #everything after the colon is the data in that field
        $Event | Add-Member $Field $Value #Add the Field to $Event as a NoteProperty, with a value of $Value. Those two are actually positional parameters for Add-Member, so we don't have to go and specify what kind of member, specify what the name is, and what the value is. Just Add-Member <[string]name> <value can be a string, array, yeti, whatever... it's not picky>
        } #End of New Field for current record
    else{$Value = $_} #If it didn't find the regex to determine if it is a new field then this is just more data from the last field, so don't change the field, just set it all as data.

    } else { #If it didn't find the regex then this is just more data from the last field, so don't change the field, just set it all as data.the field does not 'not exist') do this:
            $Event.$Field += if(![string]::isNullOrEmpty($Event.$Field)){"`r`n$_"}else{$_}} 

对于相当短的代码,这是一个很长的解释。它真的只是将数据添加到现场!这有一个倒置的(前缀!If检查,以查看当前字段是否有任何数据,如果它,或者它当前是否为空或空。如果为空,则添加新行,然后添加$ Value数据。如果它没有任何数据,它会跳过新的行位,只需添加数据。

    }
  }
}
$Records+=$Event #Adds the last event to the array of records.

抱歉,我对XML不是很了解。但至少这会让你获得干净的记录。

编辑:好了,现在已经注明了代码,希望所有内容都能得到很好的解释。如果有些事情仍然令人困惑,或许我可以将您推荐给一个比我能解释得更好的网站。我在PasteBin中针对您的示例输入运行了上述内容。