logstash-filter-elasticsearch 4.2.0 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +3 -0
- data/docs/index.asciidoc +132 -6
- data/lib/logstash/filters/elasticsearch/client.rb +8 -0
- data/lib/logstash/filters/elasticsearch/dsl_executor.rb +140 -0
- data/lib/logstash/filters/elasticsearch/esql_executor.rb +178 -0
- data/lib/logstash/filters/elasticsearch.rb +105 -129
- data/logstash-filter-elasticsearch.gemspec +1 -1
- data/spec/filters/elasticsearch_dsl_spec.rb +372 -0
- data/spec/filters/elasticsearch_esql_spec.rb +211 -0
- data/spec/filters/elasticsearch_spec.rb +129 -326
- data/spec/filters/integration/elasticsearch_esql_spec.rb +167 -0
- metadata +10 -2
@@ -0,0 +1,211 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/devutils/rspec/spec_helper"
|
3
|
+
require "logstash/filters/elasticsearch"
|
4
|
+
|
5
|
+
describe LogStash::Filters::Elasticsearch::EsqlExecutor do
|
6
|
+
let(:client) { instance_double(LogStash::Filters::ElasticsearchClient) }
|
7
|
+
let(:logger) { double("logger") }
|
8
|
+
let(:plugin) { LogStash::Filters::Elasticsearch.new(plugin_config) }
|
9
|
+
let(:plugin_config) do
|
10
|
+
{
|
11
|
+
"query_type" => "esql",
|
12
|
+
"query" => "FROM test-index | STATS count() BY field | LIMIT 10"
|
13
|
+
}
|
14
|
+
end
|
15
|
+
let(:esql_executor) { described_class.new(plugin, logger) }
|
16
|
+
|
17
|
+
context "when initializes" do
|
18
|
+
it "sets up the ESQL executor with correct parameters" do
|
19
|
+
allow(logger).to receive(:debug)
|
20
|
+
allow(logger).to receive(:warn)
|
21
|
+
expect(esql_executor.instance_variable_get(:@query)).to eq(plugin_config["query"])
|
22
|
+
expect(esql_executor.instance_variable_get(:@referenced_params)).to eq({})
|
23
|
+
expect(esql_executor.instance_variable_get(:@static_params)).to eq([])
|
24
|
+
expect(esql_executor.instance_variable_get(:@tag_on_failure)).to eq(["_elasticsearch_lookup_failure"])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "when processes" do
|
29
|
+
let(:plugin_config) {
|
30
|
+
super()
|
31
|
+
.merge(
|
32
|
+
{
|
33
|
+
"query" => "FROM my-index | WHERE field = ?foo | LIMIT 5",
|
34
|
+
"query_params" => { "foo" => "[bar]" }
|
35
|
+
})
|
36
|
+
}
|
37
|
+
let(:event) { LogStash::Event.new({}) }
|
38
|
+
let(:response) {
|
39
|
+
{
|
40
|
+
'values' => [["foo", "bar", nil]],
|
41
|
+
'columns' => [{ 'name' => 'id', 'type' => 'keyword' }, { 'name' => 'val', 'type' => 'keyword' }, { 'name' => 'odd', 'type' => 'keyword' }]
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
before do
|
46
|
+
allow(logger).to receive(:debug)
|
47
|
+
allow(logger).to receive(:warn)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "resolves parameters" do
|
51
|
+
expect(event).to receive(:get).with("[bar]").and_return("resolved_value")
|
52
|
+
resolved_params = esql_executor.send(:resolve_parameters, event)
|
53
|
+
expect(resolved_params).to include("foo" => "resolved_value")
|
54
|
+
end
|
55
|
+
|
56
|
+
it "executes the query with resolved parameters" do
|
57
|
+
allow(logger).to receive(:debug)
|
58
|
+
expect(event).to receive(:get).with("[bar]").and_return("resolved_value")
|
59
|
+
expect(client).to receive(:esql_query).with(
|
60
|
+
{ body: { query: plugin_config["query"], params: [{ "foo" => "resolved_value" }] }, format: 'json', drop_null_columns: true, })
|
61
|
+
resolved_params = esql_executor.send(:resolve_parameters, event)
|
62
|
+
esql_executor.send(:execute_query, client, resolved_params)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "informs warning if received warning" do
|
66
|
+
allow(response).to receive(:headers).and_return({ "warning" => "some warning" })
|
67
|
+
expect(logger).to receive(:warn).with("ES|QL executor received warning", { :message => "some warning" })
|
68
|
+
esql_executor.send(:inform_warning, response)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "processes the response and adds metadata" do
|
72
|
+
expect(event).to receive(:set).with("[@metadata][total_values]", 1)
|
73
|
+
# [id], [val] aren't resolved via sprintf, use as it is
|
74
|
+
expect(event).to receive(:set).with("[id]", "foo")
|
75
|
+
expect(event).to receive(:set).with("[val]", "bar")
|
76
|
+
esql_executor.send(:process_response, event, response)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "executes chain of processes" do
|
80
|
+
allow(plugin).to receive(:decorate)
|
81
|
+
allow(logger).to receive(:debug)
|
82
|
+
allow(response).to receive(:headers).and_return({})
|
83
|
+
expect(client).to receive(:esql_query).with(
|
84
|
+
{
|
85
|
+
body: { query: plugin_config["query"], params: [{"foo"=>"resolve_me"}] },
|
86
|
+
format: 'json',
|
87
|
+
drop_null_columns: true,
|
88
|
+
}).and_return(response)
|
89
|
+
|
90
|
+
event = LogStash::Event.new({ "hello" => "world", "bar" => "resolve_me" })
|
91
|
+
expect { esql_executor.process(client, event) }.to_not raise_error
|
92
|
+
expect(event.get("[@metadata][total_values]")).to eq(1)
|
93
|
+
expect(event.get("hello")).to eq("world")
|
94
|
+
expect(event.get("val")).to eq("bar")
|
95
|
+
expect(event.get("odd")).to be_nil # filters out non-exist fields
|
96
|
+
end
|
97
|
+
|
98
|
+
it "tags on plugin failures" do
|
99
|
+
expect(event).to receive(:get).with("[bar]").and_raise("Event#get Invalid FieldReference error")
|
100
|
+
|
101
|
+
expect(logger).to receive(:error).with("Failed to process ES|QL filter", exception: instance_of(RuntimeError))
|
102
|
+
expect(event).to receive(:tag).with("_elasticsearch_lookup_failure")
|
103
|
+
esql_executor.process(client, event)
|
104
|
+
end
|
105
|
+
|
106
|
+
it "tags on query execution failures" do
|
107
|
+
allow(logger).to receive(:debug)
|
108
|
+
allow(client).to receive(:esql_query).and_raise("Query execution error")
|
109
|
+
|
110
|
+
expect(logger).to receive(:error).with("Failed to process ES|QL filter", exception: instance_of(RuntimeError))
|
111
|
+
expect(event).to receive(:tag).with("_elasticsearch_lookup_failure")
|
112
|
+
esql_executor.process(client, event)
|
113
|
+
end
|
114
|
+
|
115
|
+
describe "#target" do
|
116
|
+
let(:event) { LogStash::Event.new({ "hello" => "world", "bar" => "resolve_me" }) }
|
117
|
+
let(:response) {
|
118
|
+
super().merge({ 'values' => [["foo", "bar", nil], %w[hello again world], %w[another value here]] })
|
119
|
+
}
|
120
|
+
before(:each) do
|
121
|
+
expect(client).to receive(:esql_query).with(any_args).and_return(response)
|
122
|
+
allow(plugin).to receive(:decorate)
|
123
|
+
allow(logger).to receive(:debug)
|
124
|
+
allow(response).to receive(:headers).and_return({})
|
125
|
+
end
|
126
|
+
|
127
|
+
context "when specified" do
|
128
|
+
let(:plugin_config) {
|
129
|
+
super().merge({ "target" => "my-target" })
|
130
|
+
}
|
131
|
+
|
132
|
+
it "sets all query results into event" do
|
133
|
+
expected_result = [
|
134
|
+
{"id"=>"foo", "val"=>"bar"},
|
135
|
+
{"id"=>"hello", "val"=>"again", "odd"=>"world"},
|
136
|
+
{"id"=>"another", "val"=>"value", "odd"=>"here"}
|
137
|
+
]
|
138
|
+
expect { esql_executor.process(client, event) }.to_not raise_error
|
139
|
+
expect(event.get("[@metadata][total_values]")).to eq(3)
|
140
|
+
expect(event.get("my-target").size).to eq(3)
|
141
|
+
expect(event.get("my-target")).to eq(expected_result)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context "when not specified" do
|
146
|
+
shared_examples "first result into the event" do
|
147
|
+
it "sets" do
|
148
|
+
expect { esql_executor.process(client, event) }.to_not raise_error
|
149
|
+
expect(event.get("[@metadata][total_values]")).to eq(3)
|
150
|
+
expect(event.get("id")).to eq("foo")
|
151
|
+
expect(event.get("val")).to eq("bar")
|
152
|
+
expect(event.get("odd")).to eq(nil)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
context "when limit is included in the query" do
|
156
|
+
let(:plugin_config) {
|
157
|
+
super().merge({ "query" => "FROM my-index | LIMIT 555" })
|
158
|
+
}
|
159
|
+
it_behaves_like "first result into the event"
|
160
|
+
end
|
161
|
+
|
162
|
+
context "when limit is not included in the query" do
|
163
|
+
let(:plugin_config) {
|
164
|
+
super().merge({ "query" => "FROM my-index" })
|
165
|
+
}
|
166
|
+
it_behaves_like "first result into the event"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
describe "#query placeholders" do
|
173
|
+
before(:each) do
|
174
|
+
allow(logger).to receive(:debug)
|
175
|
+
allow(logger).to receive(:warn)
|
176
|
+
plugin.send(:validate_esql_query_and_params!)
|
177
|
+
end
|
178
|
+
|
179
|
+
context "when `query_params` is an Array contains {key => val} entries" do
|
180
|
+
let(:plugin_config) {
|
181
|
+
super()
|
182
|
+
.merge(
|
183
|
+
{
|
184
|
+
"query" => "FROM my-index | LIMIT 1",
|
185
|
+
"query_params" => [{ "a" => "b" }, { "c" => "[b]" }, { "e" => 1 }, { "f" => "[g]" }],
|
186
|
+
})
|
187
|
+
}
|
188
|
+
|
189
|
+
it "separates references and static params at initialization" do
|
190
|
+
expect(esql_executor.instance_variable_get(:@referenced_params)).to eq({"c" => "[b]", "f" => "[g]"})
|
191
|
+
expect(esql_executor.instance_variable_get(:@static_params)).to eq([{"a" => "b"}, {"e" => 1}])
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
context "when `query_params` is a Hash" do
|
196
|
+
let(:plugin_config) {
|
197
|
+
super()
|
198
|
+
.merge(
|
199
|
+
{
|
200
|
+
"query" => "FROM my-index | LIMIT 1",
|
201
|
+
"query_params" => { "a" => "b", "c" => "[b]", "e" => 1, "f" => "[g]" },
|
202
|
+
})
|
203
|
+
}
|
204
|
+
|
205
|
+
it "separates references and static params at initialization" do
|
206
|
+
expect(esql_executor.instance_variable_get(:@referenced_params)).to eq({"c" => "[b]", "f" => "[g]"})
|
207
|
+
expect(esql_executor.instance_variable_get(:@static_params)).to eq([{"a" => "b"}, {"e" => 1}])
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end if LOGSTASH_VERSION >= LogStash::Filters::Elasticsearch::LS_ESQL_SUPPORT_VERSION
|