В настоящее время я проверяю, полезны ли logstash и elasticsearch для нашего варианта использования. У меня есть лог-файл содержащие несколько записей, которые имеют форму
<root>
<entry>
<fieldx>...</fieldx>
<fieldy>...</fieldy>
<fieldz>...</fieldz>
...
<fieldarray>
<fielda>...</fielda>
<fielda>...</fielda>
...
</fieldarray>
</entry>
<entry>
...
</entry>
...
<root>
Каждый entry
элемент будет содержать одно событие журнала. (Если вам интересно, файл на самом деле является экспортом рабочего журнала Tempo Timesheets (подключаемый модуль Atlassian JIRA).)
Можно ли преобразовать такой файл в несколько событий журнала без написания собственного кодека?
Хорошо, я нашел решение, которое мне подходит. Самая большая проблема с решением заключается в том, что плагин XML ... не совсем нестабилен, но либо плохо документирован и содержит ошибки, либо плохо и неправильно задокументирован.
Командная строка 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, мне пришлось преобразовать данные в более удобный формат.
gzcat -d file.xml.gz |
: Было слишком много данных - очевидно, вы можете пропустить этоtr -d "\n\r" |
: Удалить разрывы строк внутри элементов XML: Некоторые элементы могут содержать разрывы строк в виде символьных данных. Следующий шаг требует что они удалены или каким-то образом закодированы. Несмотря на то, что предполагалось, что на данный момент у вас есть весь XML-код в одной массивной строке, не имеет значения, удаляет ли эта команда любые пробелы между элементами.
xmllint --format - |
: Отформатируйте XML с помощью xmllint (поставляется с 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
в оболочке) и что xml должен быть нормализован (xmllint)
У меня был похожий случай. Чтобы проанализировать этот xml:
<ROOT number="34">
<EVENTLIST>
<EVENT name="hey"/>
<EVENT name="you"/>
</EVENTLIST>
</ROOT>
Я использую эту конфигурацию для logstash:
input {
file {
path => "/path/events.xml"
start_position => "beginning"
sincedb_path => "/dev/null"
codec => multiline {
pattern => "<ROOT"
negate => "true"
what => "previous"
auto_flush_interval => 1
}
}
}
filter {
xml {
source => "message"
target => "xml_content"
}
split {
field => "xml_content[EVENTLIST]"
}
split {
field => "xml_content[EVENTLIST][EVENT]"
}
mutate {
add_field => { "number" => "%{xml_content[number]}" }
add_field => { "name" => "%{xml_content[EVENTLIST][EVENT][name]}" }
remove_field => ['xml_content', 'message', 'path']
}
}
output {
stdout {
codec => rubydebug
}
}
Надеюсь, это кому-то поможет. Мне потребовалось много времени, чтобы это понять.