Xml

Logstash 解析包含多個日誌條目的 xml 文件

  • September 2, 2017

我目前正在評估 logstash 和 elasticsearch 是否對我們的案例有用。我所擁有的是一個包含多個條目的日誌文件,其形式為

<root>
   <entry>
       <fieldx>...</fieldx>
       <fieldy>...</fieldy>
       <fieldz>...</fieldz>
       ...
       <fieldarray>
           <fielda>...</fielda>
           <fielda>...</fielda>
           ...
       </fieldarray>
   </entry>
   <entry>
   ...
   </entry>
   ...
<root>

每個entry元素將包含一個日誌事件。(如果您有興趣,該文件實際上是一個 Tempo Timesheets (An Atlassian JIRA Plug-in) 工作日誌導出。)

是否可以在不編寫我自己的編解碼器的情況下將這樣的文件轉換為多個日誌事件?

好吧,我找到了一個對我有用的解決方案。該解決方案的最大問題是 XML 外掛……不是很不穩定,但要麼記錄不充分且存在錯誤,要麼記錄不充分且不正確。

TLDR

bash 命令行:

gzcat -d file.xml.gz | tr -d "\n\r" | xmllint --format - | logstash -f logstash-csv.conf

Logstash 配置:

input {
   stdin {}
}

filter {
   # add all lines that have more indentation than double-space to the previous line
   multiline {
       pattern => "^\s\s(\s\s|\<\/entry\>)"
       what => previous
   }
   # multiline filter adds the tag "multiline" only to lines spanning multiple lines
   # We _only_ want those here.
   if "multiline" in [tags] {
       # Add the encoding line here. Could in theory extract this from the
       # first line with a clever filter. Not worth the effort at the moment.
       mutate {
           replace => ["message",'<?xml version="1.0" encoding="UTF-8" ?>%{message}']
       }
       # This filter exports the hierarchy into the field "entry". This will
       # create a very deep structure that elasticsearch does not really like.
       # Which is why I used add_field to flatten it.
       xml {
           target => entry
           source => message
           add_field => {
               fieldx         => "%{[entry][fieldx]}"
               fieldy         => "%{[entry][fieldy]}"
               fieldz         => "%{[entry][fieldz]}"
               # With deeper nested fields, the xml converter actually creates
               # an array containing hashes, which is why you need the [0]
               # -- took me ages to find out.
               fielda         => "%{[entry][fieldarray][0][fielda]}"
               fieldb         => "%{[entry][fieldarray][0][fieldb]}"
               fieldc         => "%{[entry][fieldarray][0][fieldc]}"
           }
       }
       # Remove the intermediate fields before output. "message" contains the
       # original message (XML). You may or may-not want to keep that.
       mutate {
           remove_field => ["message"]
           remove_field => ["entry"]
       }
   }
}

output {
   ...
}

詳細的

我的解決方案有效,因為至少在entry關卡之前,我的 XML 輸入非常統一,因此可以通過某種模式匹配來處理。

由於導出基本上是一行很長的 XML,而 logstash xml 外掛基本上只適用於包含 XML 數據的欄位(讀取:行中的列),我不得不將數據更改為更有用的格式。

Shell:準備文件

  • gzcat -d file.xml.gz |: 數據太多了——顯然你可以跳過它
  • tr -d "\n\r" |:刪除 XML 元素中的換行符:某些元素可以包含換行符作為字元數據。下一步需要將這些刪除或以某種方式編碼。儘管它假設此時您將所有 XML 程式碼放在一個大行中,但此命令是否刪除元素之間的任何空白都無關緊要
  • xmllint --format - |: 用 xmllint 格式化 XML(libxml 自帶)

在這裡,XML ( ) 的單個巨大的意大利麵條行<root><entry><fieldx>...</fieldx></entry></root>格式正確:

<root>
 <entry>
   <fieldx>...</fieldx>
   <fieldy>...</fieldy>
   <fieldz>...</fieldz>
   <fieldarray>
     <fielda>...</fielda>
     <fieldb>...</fieldb>
     ...
   </fieldarray>
 </entry>
 <entry>
   ...
 </entry>
 ...
</root>

日誌儲存

logstash -f logstash-csv.conf

.conf(請參閱TL;DR 部分中文件的完整內容。)

在這裡,multiline過濾器可以解決問題。它可以將多行合併為一條日誌消息。這就是為什麼需要格式化的原因xmllint

filter {
   # add all lines that have more indentation than double-space to the previous line
   multiline {
       pattern => "^\s\s(\s\s|\<\/entry\>)"
       what => previous
   }
}

這基本上說縮進超過兩個空格的每一行(或者是</entry>/ xmllint 預設使用兩個空格縮進)屬於前一行。這也意味著字元數據不能包含換行符(tr在 shell 中被剝離)並且 xml 必須被規範化(xmllint)

引用自:https://serverfault.com/questions/615196