logstash-filter-elasticsearch 4.1.1 → 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 +6 -0
- data/docs/index.asciidoc +181 -9
- 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 +114 -114
- data/logstash-filter-elasticsearch.gemspec +3 -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 +140 -310
- data/spec/filters/integration/elasticsearch_esql_spec.rb +167 -0
- data/spec/filters/integration/elasticsearch_spec.rb +9 -2
- metadata +38 -2
@@ -0,0 +1,372 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/devutils/rspec/spec_helper"
|
3
|
+
require "logstash/filters/elasticsearch"
|
4
|
+
|
5
|
+
describe LogStash::Filters::Elasticsearch::DslExecutor 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
|
+
"index" => "test_index",
|
12
|
+
"query" => "test_query",
|
13
|
+
"fields" => { "field1" => "field1_mapped" },
|
14
|
+
"result_size" => 10,
|
15
|
+
"docinfo_fields" => { "_id" => "doc_id" },
|
16
|
+
"tag_on_failure" => ["_failure"],
|
17
|
+
"enable_sort" => true,
|
18
|
+
"sort" => "@timestamp:desc",
|
19
|
+
"aggregation_fields" => { "agg1" => "agg1_mapped" }
|
20
|
+
}
|
21
|
+
end
|
22
|
+
let(:dsl_executor) { described_class.new(plugin, logger) }
|
23
|
+
let(:event) { LogStash::Event.new({}) }
|
24
|
+
|
25
|
+
describe "#initialize" do
|
26
|
+
it "initializes instance variables correctly" do
|
27
|
+
expect(dsl_executor.instance_variable_get(:@index)).to eq("test_index")
|
28
|
+
expect(dsl_executor.instance_variable_get(:@query)).to eq("test_query")
|
29
|
+
expect(dsl_executor.instance_variable_get(:@query_dsl)).to eq(nil)
|
30
|
+
expect(dsl_executor.instance_variable_get(:@fields)).to eq({ "field1" => "field1_mapped" })
|
31
|
+
expect(dsl_executor.instance_variable_get(:@result_size)).to eq(10)
|
32
|
+
expect(dsl_executor.instance_variable_get(:@docinfo_fields)).to eq({ "_id" => "doc_id" })
|
33
|
+
expect(dsl_executor.instance_variable_get(:@tag_on_failure)).to eq(["_failure"])
|
34
|
+
expect(dsl_executor.instance_variable_get(:@enable_sort)).to eq(true)
|
35
|
+
expect(dsl_executor.instance_variable_get(:@sort)).to eq("@timestamp:desc")
|
36
|
+
expect(dsl_executor.instance_variable_get(:@aggregation_fields)).to eq({ "agg1" => "agg1_mapped" })
|
37
|
+
expect(dsl_executor.instance_variable_get(:@logger)).to eq(logger)
|
38
|
+
expect(dsl_executor.instance_variable_get(:@event_decorator)).not_to be_nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "data fetch" do
|
43
|
+
let(:plugin_config) do
|
44
|
+
{
|
45
|
+
"hosts" => ["localhost:9200"],
|
46
|
+
"query" => "response: 404",
|
47
|
+
"fields" => { "response" => "code" },
|
48
|
+
"docinfo_fields" => { "_index" => "es_index" },
|
49
|
+
"aggregation_fields" => { "bytes_avg" => "bytes_avg_ls_field" }
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
let(:response) do
|
54
|
+
LogStash::Json.load(File.read(File.join(File.dirname(__FILE__), "fixtures", "request_x_1.json")))
|
55
|
+
end
|
56
|
+
|
57
|
+
let(:client) { double(:client) }
|
58
|
+
|
59
|
+
before(:each) do
|
60
|
+
allow(LogStash::Filters::ElasticsearchClient).to receive(:new).and_return(client)
|
61
|
+
if defined?(Elastic::Transport)
|
62
|
+
allow(client).to receive(:es_transport_client_type).and_return('elastic_transport')
|
63
|
+
else
|
64
|
+
allow(client).to receive(:es_transport_client_type).and_return('elasticsearch_transport')
|
65
|
+
end
|
66
|
+
allow(client).to receive(:search).and_return(response)
|
67
|
+
allow(plugin).to receive(:test_connection!)
|
68
|
+
allow(plugin).to receive(:setup_serverless)
|
69
|
+
plugin.register
|
70
|
+
end
|
71
|
+
|
72
|
+
after(:each) do
|
73
|
+
Thread.current[:filter_elasticsearch_client] = nil
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should enhance the current event with new data" do
|
77
|
+
plugin.filter(event)
|
78
|
+
expect(event.get("code")).to eq(404)
|
79
|
+
expect(event.get("es_index")).to eq("logstash-2014.08.26")
|
80
|
+
expect(event.get("bytes_avg_ls_field")["value"]).to eq(294)
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should receive all necessary params to perform the search" do
|
84
|
+
expect(client).to receive(:search).with({:q=>"response: 404", :size=>1, :index=>"", :sort=>"@timestamp:desc"})
|
85
|
+
plugin.filter(event)
|
86
|
+
end
|
87
|
+
|
88
|
+
context "when asking to hit specific index" do
|
89
|
+
|
90
|
+
let(:plugin_config) do
|
91
|
+
{
|
92
|
+
"index" => "foo*",
|
93
|
+
"hosts" => ["localhost:9200"],
|
94
|
+
"query" => "response: 404",
|
95
|
+
"fields" => { "response" => "code" }
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should receive all necessary params to perform the search" do
|
100
|
+
expect(client).to receive(:search).with({:q=>"response: 404", :size=>1, :index=>"foo*", :sort=>"@timestamp:desc"})
|
101
|
+
plugin.filter(event)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context "when asking for more than one result" do
|
106
|
+
|
107
|
+
let(:plugin_config) do
|
108
|
+
{
|
109
|
+
"hosts" => ["localhost:9200"],
|
110
|
+
"query" => "response: 404",
|
111
|
+
"fields" => { "response" => "code" },
|
112
|
+
"result_size" => 10
|
113
|
+
}
|
114
|
+
end
|
115
|
+
|
116
|
+
let(:response) do
|
117
|
+
LogStash::Json.load(File.read(File.join(File.dirname(__FILE__), "fixtures", "request_x_10.json")))
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should enhance the current event with new data" do
|
121
|
+
plugin.filter(event)
|
122
|
+
expect(event.get("code")).to eq([404]*10)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
context 'when Elasticsearch 7.x gives us a totals object instead of an integer' do
|
127
|
+
let(:plugin_config) do
|
128
|
+
{
|
129
|
+
"hosts" => ["localhost:9200"],
|
130
|
+
"query" => "response: 404",
|
131
|
+
"fields" => { "response" => "code" },
|
132
|
+
"result_size" => 10
|
133
|
+
}
|
134
|
+
end
|
135
|
+
|
136
|
+
let(:response) do
|
137
|
+
LogStash::Json.load(File.read(File.join(File.dirname(__FILE__), "fixtures", "elasticsearch_7.x_hits_total_as_object.json")))
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should enhance the current event with new data" do
|
141
|
+
plugin.filter(event)
|
142
|
+
expect(event.get("[@metadata][total_hits]")).to eq(13476)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
context "if something wrong happen during connection" do
|
147
|
+
|
148
|
+
before(:each) do
|
149
|
+
allow(LogStash::Filters::ElasticsearchClient).to receive(:new).and_return(client)
|
150
|
+
allow(client).to receive(:search).and_raise("connection exception")
|
151
|
+
plugin.register
|
152
|
+
end
|
153
|
+
|
154
|
+
it "tag the event as something happened, but still deliver it" do
|
155
|
+
expect(plugin.logger).to receive(:warn)
|
156
|
+
plugin.filter(event)
|
157
|
+
expect(event.to_hash["tags"]).to include("_elasticsearch_lookup_failure")
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Tagging test for positive results
|
162
|
+
context "Tagging should occur if query returns results" do
|
163
|
+
let(:plugin_config) do
|
164
|
+
{
|
165
|
+
"index" => "foo*",
|
166
|
+
"hosts" => ["localhost:9200"],
|
167
|
+
"query" => "response: 404",
|
168
|
+
"add_tag" => ["tagged"]
|
169
|
+
}
|
170
|
+
end
|
171
|
+
|
172
|
+
let(:response) do
|
173
|
+
LogStash::Json.load(File.read(File.join(File.dirname(__FILE__), "fixtures", "request_x_10.json")))
|
174
|
+
end
|
175
|
+
|
176
|
+
it "should tag the current event if results returned" do
|
177
|
+
plugin.filter(event)
|
178
|
+
expect(event.to_hash["tags"]).to include("tagged")
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
context "an aggregation search with size 0 that matches" do
|
183
|
+
let(:plugin_config) do
|
184
|
+
{
|
185
|
+
"index" => "foo*",
|
186
|
+
"hosts" => ["localhost:9200"],
|
187
|
+
"query" => "response: 404",
|
188
|
+
"add_tag" => ["tagged"],
|
189
|
+
"result_size" => 0,
|
190
|
+
"aggregation_fields" => { "bytes_avg" => "bytes_avg_ls_field" }
|
191
|
+
}
|
192
|
+
end
|
193
|
+
|
194
|
+
let(:response) do
|
195
|
+
LogStash::Json.load(File.read(File.join(File.dirname(__FILE__), "fixtures", "request_size0_agg.json")))
|
196
|
+
end
|
197
|
+
|
198
|
+
it "should tag the current event" do
|
199
|
+
plugin.filter(event)
|
200
|
+
expect(event.get("tags")).to include("tagged")
|
201
|
+
expect(event.get("bytes_avg_ls_field")["value"]).to eq(294)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# Tagging test for negative results
|
206
|
+
context "Tagging should not occur if query has no results" do
|
207
|
+
let(:plugin_config) do
|
208
|
+
{
|
209
|
+
"index" => "foo*",
|
210
|
+
"hosts" => ["localhost:9200"],
|
211
|
+
"query" => "response: 404",
|
212
|
+
"add_tag" => ["tagged"]
|
213
|
+
}
|
214
|
+
end
|
215
|
+
|
216
|
+
let(:response) do
|
217
|
+
LogStash::Json.load(File.read(File.join(File.dirname(__FILE__), "fixtures", "request_error.json")))
|
218
|
+
end
|
219
|
+
|
220
|
+
it "should not tag the current event" do
|
221
|
+
plugin.filter(event)
|
222
|
+
expect(event.to_hash["tags"]).to_not include("tagged")
|
223
|
+
end
|
224
|
+
end
|
225
|
+
context "testing a simple query template" do
|
226
|
+
let(:plugin_config) do
|
227
|
+
{
|
228
|
+
"hosts" => ["localhost:9200"],
|
229
|
+
"query_template" => File.join(File.dirname(__FILE__), "fixtures", "query_template.json"),
|
230
|
+
"fields" => { "response" => "code" },
|
231
|
+
"result_size" => 1
|
232
|
+
}
|
233
|
+
end
|
234
|
+
|
235
|
+
let(:response) do
|
236
|
+
LogStash::Json.load(File.read(File.join(File.dirname(__FILE__), "fixtures", "request_x_1.json")))
|
237
|
+
end
|
238
|
+
|
239
|
+
it "should enhance the current event with new data" do
|
240
|
+
plugin.filter(event)
|
241
|
+
expect(event.get("code")).to eq(404)
|
242
|
+
end
|
243
|
+
|
244
|
+
end
|
245
|
+
|
246
|
+
context "testing a simple index substitution" do
|
247
|
+
let(:event) {
|
248
|
+
LogStash::Event.new(
|
249
|
+
{
|
250
|
+
"subst_field" => "subst_value"
|
251
|
+
}
|
252
|
+
)
|
253
|
+
}
|
254
|
+
let(:plugin_config) do
|
255
|
+
{
|
256
|
+
"index" => "foo_%{subst_field}*",
|
257
|
+
"hosts" => ["localhost:9200"],
|
258
|
+
"query" => "response: 404",
|
259
|
+
"fields" => { "response" => "code" }
|
260
|
+
}
|
261
|
+
end
|
262
|
+
|
263
|
+
it "should receive substituted index name" do
|
264
|
+
expect(client).to receive(:search).with({:q => "response: 404", :size => 1, :index => "foo_subst_value*", :sort => "@timestamp:desc"})
|
265
|
+
plugin.filter(event)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
context "if query result errored but no exception is thrown" do
|
270
|
+
let(:response) do
|
271
|
+
LogStash::Json.load(File.read(File.join(File.dirname(__FILE__), "fixtures", "request_error.json")))
|
272
|
+
end
|
273
|
+
|
274
|
+
before(:each) do
|
275
|
+
allow(LogStash::Filters::ElasticsearchClient).to receive(:new).and_return(client)
|
276
|
+
allow(client).to receive(:search).and_return(response)
|
277
|
+
plugin.register
|
278
|
+
end
|
279
|
+
|
280
|
+
it "tag the event as something happened, but still deliver it" do
|
281
|
+
expect(plugin.logger).to receive(:warn)
|
282
|
+
plugin.filter(event)
|
283
|
+
expect(event.to_hash["tags"]).to include("_elasticsearch_lookup_failure")
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
context 'with client-level retries' do
|
288
|
+
let(:plugin_config) do
|
289
|
+
super().merge(
|
290
|
+
"retry_on_failure" => 3,
|
291
|
+
"retry_on_status" => [500]
|
292
|
+
)
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
context "with custom headers" do
|
297
|
+
let(:plugin_config) do
|
298
|
+
{
|
299
|
+
"query" => "*",
|
300
|
+
"custom_headers" => { "Custom-Header-1" => "Custom Value 1", "Custom-Header-2" => "Custom Value 2" }
|
301
|
+
}
|
302
|
+
end
|
303
|
+
|
304
|
+
let(:plugin) { LogStash::Filters::Elasticsearch.new(plugin_config) }
|
305
|
+
let(:client_double) { double("client") }
|
306
|
+
let(:transport_double) { double("transport", options: { transport_options: { headers: plugin_config["custom_headers"] } }) }
|
307
|
+
|
308
|
+
before do
|
309
|
+
allow(plugin).to receive(:get_client).and_return(client_double)
|
310
|
+
if defined?(Elastic::Transport)
|
311
|
+
allow(client_double).to receive(:es_transport_client_type).and_return('elastic_transport')
|
312
|
+
else
|
313
|
+
allow(client_double).to receive(:es_transport_client_type).and_return('elasticsearch_transport')
|
314
|
+
end
|
315
|
+
allow(client_double).to receive(:client).and_return(transport_double)
|
316
|
+
end
|
317
|
+
|
318
|
+
it "sets custom headers" do
|
319
|
+
plugin.register
|
320
|
+
client = plugin.send(:get_client).client
|
321
|
+
expect(client.options[:transport_options][:headers]).to match(hash_including(plugin_config["custom_headers"]))
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
context "if query is on nested field" do
|
326
|
+
let(:plugin_config) do
|
327
|
+
{
|
328
|
+
"hosts" => ["localhost:9200"],
|
329
|
+
"query" => "response: 404",
|
330
|
+
"fields" => [ ["[geoip][ip]", "ip_address"] ]
|
331
|
+
}
|
332
|
+
end
|
333
|
+
|
334
|
+
it "should enhance the current event with new data" do
|
335
|
+
plugin.filter(event)
|
336
|
+
expect(event.get("ip_address")).to eq("66.249.73.185")
|
337
|
+
end
|
338
|
+
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
describe "#set_to_event_target" do
|
343
|
+
it 'is ready to set to `target`' do
|
344
|
+
expect(dsl_executor.apply_target("path")).to eq("path")
|
345
|
+
end
|
346
|
+
|
347
|
+
context "when `@target` is nil, default behavior" do
|
348
|
+
it "sets the value directly to the top-level event field" do
|
349
|
+
dsl_executor.send(:set_to_event_target, event, "new_field", %w[value1 value2])
|
350
|
+
expect(event.get("new_field")).to eq(%w[value1 value2])
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
context "when @target is defined" do
|
355
|
+
let(:plugin_config) {
|
356
|
+
super().merge({ "target" => "nested" })
|
357
|
+
}
|
358
|
+
|
359
|
+
it "creates a nested structure under the target field" do
|
360
|
+
dsl_executor.send(:set_to_event_target, event, "new_field", %w[value1 value2])
|
361
|
+
expect(event.get("nested")).to eq({ "new_field" => %w[value1 value2] })
|
362
|
+
end
|
363
|
+
|
364
|
+
it "overwrites existing target field with new data" do
|
365
|
+
event.set("nested", { "existing_field" => "existing_value", "new_field" => "value0" })
|
366
|
+
dsl_executor.send(:set_to_event_target, event, "new_field", ["value1"])
|
367
|
+
expect(event.get("nested")).to eq({ "existing_field" => "existing_value", "new_field" => ["value1"] })
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
end
|
@@ -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
|