logstash-input-elasticsearch 4.21.2 → 4.23.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 +300 -5
- data/lib/logstash/inputs/elasticsearch/aggregation.rb +11 -8
- data/lib/logstash/inputs/elasticsearch/cursor_tracker.rb +58 -0
- data/lib/logstash/inputs/elasticsearch/esql.rb +153 -0
- data/lib/logstash/inputs/elasticsearch/paginated_search.rb +12 -2
- data/lib/logstash/inputs/elasticsearch.rb +130 -28
- data/logstash-input-elasticsearch.gemspec +1 -1
- data/spec/inputs/cursor_tracker_spec.rb +72 -0
- data/spec/inputs/elasticsearch_esql_spec.rb +180 -0
- data/spec/inputs/elasticsearch_spec.rb +130 -1
- data/spec/inputs/integration/elasticsearch_esql_spec.rb +150 -0
- data/spec/inputs/integration/elasticsearch_spec.rb +9 -1
- metadata +10 -2
@@ -1152,7 +1152,7 @@ describe LogStash::Inputs::Elasticsearch, :ecs_compatibility_support do
|
|
1152
1152
|
|
1153
1153
|
context "when there's an exception" do
|
1154
1154
|
before(:each) do
|
1155
|
-
allow(client).to receive(:search).and_raise RuntimeError
|
1155
|
+
allow(client).to receive(:search).and_raise RuntimeError.new("test exception")
|
1156
1156
|
end
|
1157
1157
|
it 'produces no events' do
|
1158
1158
|
plugin.run queue
|
@@ -1297,6 +1297,10 @@ describe LogStash::Inputs::Elasticsearch, :ecs_compatibility_support do
|
|
1297
1297
|
|
1298
1298
|
let(:mock_queue) { double('queue', :<< => nil) }
|
1299
1299
|
|
1300
|
+
before(:each) do
|
1301
|
+
plugin.send(:setup_cursor_tracker)
|
1302
|
+
end
|
1303
|
+
|
1300
1304
|
it 'pushes a generated event to the queue' do
|
1301
1305
|
plugin.send(:push_hit, hit, mock_queue)
|
1302
1306
|
expect(mock_queue).to have_received(:<<) do |event|
|
@@ -1353,4 +1357,129 @@ describe LogStash::Inputs::Elasticsearch, :ecs_compatibility_support do
|
|
1353
1357
|
client.transport.respond_to?(:transport) ? client.transport.transport : client.transport
|
1354
1358
|
end
|
1355
1359
|
|
1360
|
+
describe "#ESQL" do
|
1361
|
+
let(:config) do
|
1362
|
+
{
|
1363
|
+
"query" => "FROM test-index | STATS count() BY field",
|
1364
|
+
"query_type" => "esql",
|
1365
|
+
"retries" => 3
|
1366
|
+
}
|
1367
|
+
end
|
1368
|
+
let(:es_version) { LogStash::Inputs::Elasticsearch::ES_ESQL_SUPPORT_VERSION }
|
1369
|
+
let(:ls_version) { LogStash::Inputs::Elasticsearch::LS_ESQL_SUPPORT_VERSION }
|
1370
|
+
|
1371
|
+
before(:each) do
|
1372
|
+
stub_const("LOGSTASH_VERSION", ls_version)
|
1373
|
+
end
|
1374
|
+
|
1375
|
+
describe "#initialize" do
|
1376
|
+
it "sets up the ESQL client with correct parameters" do
|
1377
|
+
expect(plugin.instance_variable_get(:@query_type)).to eq(config["query_type"])
|
1378
|
+
expect(plugin.instance_variable_get(:@query)).to eq(config["query"])
|
1379
|
+
expect(plugin.instance_variable_get(:@retries)).to eq(config["retries"])
|
1380
|
+
end
|
1381
|
+
end
|
1382
|
+
|
1383
|
+
describe "#register" do
|
1384
|
+
before(:each) do
|
1385
|
+
Elasticsearch::Client.send(:define_method, :ping) { }
|
1386
|
+
allow_any_instance_of(Elasticsearch::Client).to receive(:info).and_return(cluster_info)
|
1387
|
+
end
|
1388
|
+
it "creates ES|QL executor" do
|
1389
|
+
plugin.register
|
1390
|
+
expect(plugin.instance_variable_get(:@query_executor)).to be_an_instance_of(LogStash::Inputs::Elasticsearch::Esql)
|
1391
|
+
end
|
1392
|
+
end
|
1393
|
+
|
1394
|
+
describe "#validation" do
|
1395
|
+
|
1396
|
+
describe "LS version" do
|
1397
|
+
context "when compatible" do
|
1398
|
+
|
1399
|
+
it "does not raise an error" do
|
1400
|
+
expect { plugin.send(:validate_ls_version_for_esql_support!) }.not_to raise_error
|
1401
|
+
end
|
1402
|
+
end
|
1403
|
+
|
1404
|
+
context "when incompatible" do
|
1405
|
+
before(:each) do
|
1406
|
+
stub_const("LOGSTASH_VERSION", "8.10.0")
|
1407
|
+
end
|
1408
|
+
|
1409
|
+
it "raises a runtime error" do
|
1410
|
+
expect { plugin.send(:validate_ls_version_for_esql_support!) }
|
1411
|
+
.to raise_error(RuntimeError, /Current version of Logstash does not include Elasticsearch client which supports ES|QL. Please upgrade Logstash to at least #{ls_version}/)
|
1412
|
+
end
|
1413
|
+
end
|
1414
|
+
end
|
1415
|
+
|
1416
|
+
describe "ES version" do
|
1417
|
+
before(:each) do
|
1418
|
+
allow(plugin).to receive(:es_version).and_return("8.10.5")
|
1419
|
+
end
|
1420
|
+
|
1421
|
+
context "when incompatible" do
|
1422
|
+
it "raises a runtime error" do
|
1423
|
+
expect { plugin.send(:validate_es_for_esql_support!) }
|
1424
|
+
.to raise_error(RuntimeError, /Connected Elasticsearch 8.10.5 version does not supports ES|QL. ES|QL feature requires at least Elasticsearch #{es_version} version./)
|
1425
|
+
end
|
1426
|
+
end
|
1427
|
+
end
|
1428
|
+
|
1429
|
+
context "ES|QL query and DSL params used together" do
|
1430
|
+
let(:config) {
|
1431
|
+
super().merge({
|
1432
|
+
"index" => "my-index",
|
1433
|
+
"size" => 1,
|
1434
|
+
"slices" => 1,
|
1435
|
+
"search_api" => "auto",
|
1436
|
+
"docinfo" => true,
|
1437
|
+
"docinfo_target" => "[@metadata][docinfo]",
|
1438
|
+
"docinfo_fields" => ["_index"],
|
1439
|
+
"response_type" => "hits",
|
1440
|
+
"tracking_field" => "[@metadata][tracking]"
|
1441
|
+
})}
|
1442
|
+
|
1443
|
+
it "raises a config error" do
|
1444
|
+
mixed_fields = %w[index size slices docinfo_fields response_type tracking_field]
|
1445
|
+
expect { plugin.register }.to raise_error(LogStash::ConfigurationError, /Configured #{mixed_fields} params are not allowed while using ES|QL query/)
|
1446
|
+
end
|
1447
|
+
end
|
1448
|
+
|
1449
|
+
describe "ES|QL query" do
|
1450
|
+
context "when query is valid" do
|
1451
|
+
it "does not raise an error" do
|
1452
|
+
expect { plugin.send(:validate_esql_query!) }.not_to raise_error
|
1453
|
+
end
|
1454
|
+
end
|
1455
|
+
|
1456
|
+
context "when query is empty" do
|
1457
|
+
let(:config) do
|
1458
|
+
{
|
1459
|
+
"query" => " "
|
1460
|
+
}
|
1461
|
+
end
|
1462
|
+
|
1463
|
+
it "raises a configuration error" do
|
1464
|
+
expect { plugin.send(:validate_esql_query!) }
|
1465
|
+
.to raise_error(LogStash::ConfigurationError, /`query` cannot be empty/)
|
1466
|
+
end
|
1467
|
+
end
|
1468
|
+
|
1469
|
+
context "when query doesn't align with ES syntax" do
|
1470
|
+
let(:config) do
|
1471
|
+
{
|
1472
|
+
"query" => "RANDOM query"
|
1473
|
+
}
|
1474
|
+
end
|
1475
|
+
|
1476
|
+
it "raises a configuration error" do
|
1477
|
+
source_commands = %w[FROM ROW SHOW]
|
1478
|
+
expect { plugin.send(:validate_esql_query!) }
|
1479
|
+
.to raise_error(LogStash::ConfigurationError, "`query` needs to start with any of #{source_commands}")
|
1480
|
+
end
|
1481
|
+
end
|
1482
|
+
end
|
1483
|
+
end
|
1484
|
+
end
|
1356
1485
|
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/devutils/rspec/spec_helper"
|
3
|
+
require "logstash/inputs/elasticsearch"
|
4
|
+
require "elasticsearch"
|
5
|
+
require_relative "../../../spec/es_helper"
|
6
|
+
|
7
|
+
describe LogStash::Inputs::Elasticsearch, integration: true do
|
8
|
+
|
9
|
+
SECURE_INTEGRATION = ENV['SECURE_INTEGRATION'].eql? 'true'
|
10
|
+
ES_HOSTS = ["http#{SECURE_INTEGRATION ? 's' : nil}://#{ESHelper.get_host_port}"]
|
11
|
+
|
12
|
+
let(:plugin) { described_class.new(config) }
|
13
|
+
let(:es_index) { "logstash-esql-integration-#{rand(1000)}" }
|
14
|
+
let(:test_documents) do
|
15
|
+
[
|
16
|
+
{ "message" => "test message 1", "type" => "a", "count" => 1 },
|
17
|
+
{ "message" => "test message 2", "type" => "a", "count" => 2 },
|
18
|
+
{ "message" => "test message 3", "type" => "b", "count" => 3 },
|
19
|
+
{ "message" => "test message 4", "type" => "b", "count" => 4 },
|
20
|
+
{ "message" => "test message 5", "type" => "c", "count" => 5 }
|
21
|
+
]
|
22
|
+
end
|
23
|
+
let(:config) do
|
24
|
+
{
|
25
|
+
"hosts" => ES_HOSTS,
|
26
|
+
"query_type" => "esql"
|
27
|
+
}
|
28
|
+
end
|
29
|
+
let(:es_client) do
|
30
|
+
Elasticsearch::Client.new(hosts: ES_HOSTS)
|
31
|
+
end
|
32
|
+
|
33
|
+
before(:all) do
|
34
|
+
is_ls_with_esql_supported_client = Gem::Version.create(LOGSTASH_VERSION) >= Gem::Version.create(LogStash::Inputs::Elasticsearch::LS_ESQL_SUPPORT_VERSION)
|
35
|
+
skip "LS version does not have ES client which supports ES|QL" unless is_ls_with_esql_supported_client
|
36
|
+
|
37
|
+
# Skip tests if ES version doesn't support ES||QL
|
38
|
+
es_client = Elasticsearch::Client.new(hosts: ES_HOSTS) # need to separately create since let isn't allowed in before(:context)
|
39
|
+
es_version_info = es_client.info["version"]
|
40
|
+
es_gem_version = Gem::Version.create(es_version_info["number"])
|
41
|
+
skip "ES version does not support ES|QL" if es_gem_version.nil? || es_gem_version < Gem::Version.create(LogStash::Inputs::Elasticsearch::ES_ESQL_SUPPORT_VERSION)
|
42
|
+
end
|
43
|
+
|
44
|
+
before(:each) do
|
45
|
+
# Create index with test documents
|
46
|
+
es_client.indices.create(index: es_index, body: {}) unless es_client.indices.exists?(index: es_index)
|
47
|
+
|
48
|
+
test_documents.each do |doc|
|
49
|
+
es_client.index(index: es_index, body: doc, refresh: true)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
after(:each) do
|
54
|
+
es_client.indices.delete(index: es_index) if es_client.indices.exists?(index: es_index)
|
55
|
+
end
|
56
|
+
|
57
|
+
context "#run ES|QL queries" do
|
58
|
+
|
59
|
+
before do
|
60
|
+
stub_const("LOGSTASH_VERSION", LogStash::Inputs::Elasticsearch::LS_ESQL_SUPPORT_VERSION)
|
61
|
+
allow_any_instance_of(LogStash::Inputs::Elasticsearch).to receive(:exit_plugin?).and_return false, true
|
62
|
+
end
|
63
|
+
|
64
|
+
before(:each) do
|
65
|
+
plugin.register
|
66
|
+
end
|
67
|
+
|
68
|
+
shared_examples "ESQL query execution" do |expected_count|
|
69
|
+
it "correctly retrieves documents" do
|
70
|
+
queue = Queue.new
|
71
|
+
plugin.run(queue)
|
72
|
+
|
73
|
+
event_count = 0
|
74
|
+
expected_count.times do |i|
|
75
|
+
event = queue.pop
|
76
|
+
expect(event).to be_a(LogStash::Event)
|
77
|
+
event_count += 1
|
78
|
+
end
|
79
|
+
expect(event_count).to eq(expected_count)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context "#FROM query" do
|
84
|
+
let(:config) do
|
85
|
+
super().merge("query" => "FROM #{es_index} | SORT count")
|
86
|
+
end
|
87
|
+
|
88
|
+
include_examples "ESQL query execution", 5
|
89
|
+
end
|
90
|
+
|
91
|
+
context "#FROM query and WHERE clause" do
|
92
|
+
let(:config) do
|
93
|
+
super().merge("query" => "FROM #{es_index} | WHERE type == \"a\" | SORT count")
|
94
|
+
end
|
95
|
+
|
96
|
+
include_examples "ESQL query execution", 2
|
97
|
+
end
|
98
|
+
|
99
|
+
context "#STATS aggregation" do
|
100
|
+
let(:config) do
|
101
|
+
super().merge("query" => "FROM #{es_index} | STATS avg(count) BY type")
|
102
|
+
end
|
103
|
+
|
104
|
+
it "retrieves aggregated stats" do
|
105
|
+
queue = Queue.new
|
106
|
+
plugin.run(queue)
|
107
|
+
results = []
|
108
|
+
3.times do
|
109
|
+
event = queue.pop
|
110
|
+
expect(event).to be_a(LogStash::Event)
|
111
|
+
results << event.get("avg(count)")
|
112
|
+
end
|
113
|
+
|
114
|
+
expected_averages = [1.5, 3.5, 5.0]
|
115
|
+
expect(results.sort).to eq(expected_averages)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
context "#METADATA" do
|
120
|
+
let(:config) do
|
121
|
+
super().merge("query" => "FROM #{es_index} METADATA _index, _id, _version | DROP message.keyword, type.keyword | SORT count")
|
122
|
+
end
|
123
|
+
|
124
|
+
it "includes document metadata" do
|
125
|
+
queue = Queue.new
|
126
|
+
plugin.run(queue)
|
127
|
+
|
128
|
+
5.times do
|
129
|
+
event = queue.pop
|
130
|
+
expect(event).to be_a(LogStash::Event)
|
131
|
+
expect(event.get("_index")).not_to be_nil
|
132
|
+
expect(event.get("_id")).not_to be_nil
|
133
|
+
expect(event.get("_version")).not_to be_nil
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context "#invalid ES|QL query" do
|
139
|
+
let(:config) do
|
140
|
+
super().merge("query" => "FROM undefined index | LIMIT 1")
|
141
|
+
end
|
142
|
+
|
143
|
+
it "doesn't produce events" do
|
144
|
+
queue = Queue.new
|
145
|
+
plugin.run(queue)
|
146
|
+
expect(queue.empty?).to eq(true)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -76,6 +76,14 @@ describe LogStash::Inputs::Elasticsearch do
|
|
76
76
|
shared_examples 'secured_elasticsearch' do
|
77
77
|
it_behaves_like 'an elasticsearch index plugin'
|
78
78
|
|
79
|
+
let(:unauth_exception_class) do
|
80
|
+
begin
|
81
|
+
Elasticsearch::Transport::Transport::Errors::Unauthorized
|
82
|
+
rescue
|
83
|
+
Elastic::Transport::Transport::Errors::Unauthorized
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
79
87
|
context "incorrect auth credentials" do
|
80
88
|
|
81
89
|
let(:config) do
|
@@ -85,7 +93,7 @@ describe LogStash::Inputs::Elasticsearch do
|
|
85
93
|
let(:queue) { [] }
|
86
94
|
|
87
95
|
it "fails to run the plugin" do
|
88
|
-
expect { plugin.register }.to raise_error
|
96
|
+
expect { plugin.register }.to raise_error unauth_exception_class
|
89
97
|
end
|
90
98
|
end
|
91
99
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: logstash-input-elasticsearch
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.23.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Elastic
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-06-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
@@ -278,6 +278,8 @@ files:
|
|
278
278
|
- lib/logstash/helpers/loggable_try.rb
|
279
279
|
- lib/logstash/inputs/elasticsearch.rb
|
280
280
|
- lib/logstash/inputs/elasticsearch/aggregation.rb
|
281
|
+
- lib/logstash/inputs/elasticsearch/cursor_tracker.rb
|
282
|
+
- lib/logstash/inputs/elasticsearch/esql.rb
|
281
283
|
- lib/logstash/inputs/elasticsearch/paginated_search.rb
|
282
284
|
- lib/logstash/inputs/elasticsearch/patches/_elasticsearch_transport_connections_selector.rb
|
283
285
|
- lib/logstash/inputs/elasticsearch/patches/_elasticsearch_transport_http_manticore.rb
|
@@ -291,8 +293,11 @@ files:
|
|
291
293
|
- spec/fixtures/test_certs/es.crt
|
292
294
|
- spec/fixtures/test_certs/es.key
|
293
295
|
- spec/fixtures/test_certs/renew.sh
|
296
|
+
- spec/inputs/cursor_tracker_spec.rb
|
297
|
+
- spec/inputs/elasticsearch_esql_spec.rb
|
294
298
|
- spec/inputs/elasticsearch_spec.rb
|
295
299
|
- spec/inputs/elasticsearch_ssl_spec.rb
|
300
|
+
- spec/inputs/integration/elasticsearch_esql_spec.rb
|
296
301
|
- spec/inputs/integration/elasticsearch_spec.rb
|
297
302
|
- spec/inputs/paginated_search_spec.rb
|
298
303
|
homepage: https://elastic.co/logstash
|
@@ -330,7 +335,10 @@ test_files:
|
|
330
335
|
- spec/fixtures/test_certs/es.crt
|
331
336
|
- spec/fixtures/test_certs/es.key
|
332
337
|
- spec/fixtures/test_certs/renew.sh
|
338
|
+
- spec/inputs/cursor_tracker_spec.rb
|
339
|
+
- spec/inputs/elasticsearch_esql_spec.rb
|
333
340
|
- spec/inputs/elasticsearch_spec.rb
|
334
341
|
- spec/inputs/elasticsearch_ssl_spec.rb
|
342
|
+
- spec/inputs/integration/elasticsearch_esql_spec.rb
|
335
343
|
- spec/inputs/integration/elasticsearch_spec.rb
|
336
344
|
- spec/inputs/paginated_search_spec.rb
|