申し分なく、私のために働く解決策を見つけました。ソリューションの最大の問題は、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の非常に長い1行であり、logstash xmlプラグインは基本的にXMLデータを含むフィールド(読み取り:行の列)でのみ機能するため、データをより有用な形式に変更する必要がありました。
シェル:ファイルの準備
gzcat -d file.xml.gz |
:データが多すぎました-明らかにそれをスキップできます
tr -d "\n\r" |
:XML要素内の改行を削除:一部の要素には、文字データとして改行を含めることができます。次の手順では、これらを削除するか、何らかの方法でエンコードする必要があります。この時点ではすべてのXMLコードが1行に収まっていると想定していますが、このコマンドで要素間の空白を削除しても問題ありません。
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
logstash -f logstash-csv.conf
(.conf
TL; DRセクションのファイルの完全な内容を参照してください。)
ここでは、multiline
フィルターがうまく機能します。複数の行を1つのログメッセージにマージできます。そして、これがによるフォーマット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
}
}
これは基本的に、インデントが2つ以上のスペースを持つすべての行(または</entry>
/ xmllintがデフォルトで2つのスペースでインデントを行う)は、前の行に属していることを示しています。これは、文字データに改行(tr
シェルで取り除かれたもの)が含まれていてはならず、xmlが正規化されている必要があること(xmllint)も意味します。