Baiklah, saya menemukan solusi yang bekerja untuk saya. Masalah terbesar dengan solusinya adalah bahwa plugin XML adalah ... tidak cukup tidak stabil, tetapi tidak terdokumentasi dengan baik dan bermasalah atau didokumentasikan dengan buruk dan salah.
TLDR
Baris perintah Bash:
gzcat -d file.xml.gz | tr -d "\n\r" | xmllint --format - | logstash -f logstash-csv.conf
Konfigurasi 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 {
...
}
Terperinci
Solusi saya berfungsi karena setidaknya sampai entry
level, input XML saya sangat seragam dan dengan demikian dapat ditangani oleh beberapa jenis pencocokan pola.
Karena ekspor pada dasarnya adalah satu baris XML yang sangat panjang, dan plugin logstash xml pada dasarnya hanya bekerja dengan bidang (baca: kolom dalam baris) yang berisi data XML, saya harus mengubah data menjadi format yang lebih bermanfaat.
Shell: Mempersiapkan file
gzcat -d file.xml.gz |
: Terlalu banyak data - jelas Anda dapat melewati itu
tr -d "\n\r" |
: Hapus jeda baris di dalam elemen XML: Beberapa elemen dapat berisi jeda baris sebagai data karakter. Langkah selanjutnya mengharuskan ini dihapus, atau dikodekan dalam beberapa cara. Meskipun diasumsikan bahwa pada titik ini Anda memiliki semua kode XML dalam satu garis besar, tidak masalah jika perintah ini menghilangkan spasi putih di antara elemen
xmllint --format - |
: Format XML dengan xmllint (dilengkapi dengan libxml)
Di sini satu baris besar spageti XML ( <root><entry><fieldx>...</fieldx></entry></root>
) diformat dengan benar:
<root>
<entry>
<fieldx>...</fieldx>
<fieldy>...</fieldy>
<fieldz>...</fieldz>
<fieldarray>
<fielda>...</fielda>
<fieldb>...</fieldb>
...
</fieldarray>
</entry>
<entry>
...
</entry>
...
</root>
Logstash
logstash -f logstash-csv.conf
(Lihat konten lengkap .conf
file di bagian TL; DR.)
Di sini, multiline
filternya berfungsi. Itu dapat menggabungkan beberapa baris menjadi satu pesan log. Dan inilah mengapa format dengan xmllint
itu diperlukan:
filter {
# add all lines that have more indentation than double-space to the previous line
multiline {
pattern => "^\s\s(\s\s|\<\/entry\>)"
what => previous
}
}
Ini pada dasarnya mengatakan bahwa setiap baris dengan lekukan yang lebih dari dua spasi (atau </entry>
/ xmllint melakukan lekukan dengan dua spasi secara default) milik baris sebelumnya. Ini juga berarti data karakter tidak boleh mengandung baris baru (dilucuti dengan tr
shell) dan bahwa xml harus dinormalisasi (xmllint)