logstash-output-elasticsearch 2.1.2-java → 2.1.4-java
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 +16 -3
- data/Gemfile +1 -1
- data/README.md +2 -0
- data/lib/logstash/outputs/elasticsearch.rb +24 -414
- data/lib/logstash/outputs/elasticsearch/buffer.rb +124 -0
- data/lib/logstash/outputs/elasticsearch/common.rb +173 -0
- data/lib/logstash/outputs/elasticsearch/common_configs.rb +113 -0
- data/lib/logstash/outputs/elasticsearch/elasticsearch-template.json +85 -31
- data/lib/logstash/outputs/elasticsearch/http_client.rb +3 -2
- data/lib/logstash/outputs/elasticsearch/http_client_builder.rb +92 -0
- data/lib/logstash/outputs/elasticsearch/template_manager.rb +35 -0
- data/logstash-output-elasticsearch.gemspec +2 -2
- data/spec/es_spec_helper.rb +4 -4
- data/spec/integration/outputs/create_spec.rb +3 -4
- data/spec/integration/outputs/index_spec.rb +3 -5
- data/spec/integration/outputs/retry_spec.rb +23 -16
- data/spec/integration/outputs/routing_spec.rb +3 -5
- data/spec/integration/outputs/secure_spec.rb +4 -4
- data/spec/integration/outputs/templates_spec.rb +2 -3
- data/spec/integration/outputs/update_spec.rb +5 -6
- data/spec/unit/buffer_spec.rb +118 -0
- data/spec/unit/http_client_builder_spec.rb +27 -0
- data/spec/unit/outputs/elasticsearch/protocol_spec.rb +2 -2
- data/spec/unit/outputs/elasticsearch_spec.rb +46 -12
- data/spec/unit/outputs/elasticsearch_ssl_spec.rb +0 -1
- metadata +68 -59
@@ -4,7 +4,7 @@ require "base64"
|
|
4
4
|
require "elasticsearch"
|
5
5
|
require "elasticsearch/transport/transport/http/manticore"
|
6
6
|
|
7
|
-
module LogStash
|
7
|
+
module LogStash; module Outputs; class ElasticSearch;
|
8
8
|
class HttpClient
|
9
9
|
attr_reader :client, :options, :client_options, :sniffer_thread
|
10
10
|
# This is here in case we use DEFAULT_OPTIONS in the future
|
@@ -30,6 +30,7 @@ module LogStash::Outputs::Elasticsearch
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def bulk(actions)
|
33
|
+
return if actions.empty?
|
33
34
|
bulk_body = actions.collect do |action, args, source|
|
34
35
|
if action == 'update'
|
35
36
|
if args[:_id]
|
@@ -150,4 +151,4 @@ module LogStash::Outputs::Elasticsearch
|
|
150
151
|
@client.indices.put_template(:name => name, :body => template)
|
151
152
|
end
|
152
153
|
end
|
153
|
-
end
|
154
|
+
end end end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module LogStash; module Outputs; class ElasticSearch;
|
2
|
+
module HttpClientBuilder
|
3
|
+
def self.build(logger, hosts, params)
|
4
|
+
client_settings = {}
|
5
|
+
|
6
|
+
common_options = {
|
7
|
+
:client_settings => client_settings,
|
8
|
+
:sniffing => params["sniffing"],
|
9
|
+
:sniffing_delay => params["sniffing_delay"]
|
10
|
+
}
|
11
|
+
|
12
|
+
common_options[:timeout] = params["timeout"] if params["timeout"]
|
13
|
+
client_settings[:path] = "/#{params["path"]}/".gsub(/\/+/, "/") # Normalize slashes
|
14
|
+
logger.debug? && logger.debug("Normalizing http path", :path => params["path"], :normalized => client_settings[:path])
|
15
|
+
|
16
|
+
client_settings.merge! setup_ssl(logger, params)
|
17
|
+
client_settings.merge! setup_proxy(logger, params)
|
18
|
+
common_options.merge! setup_basic_auth(logger, params)
|
19
|
+
|
20
|
+
# Update API setup
|
21
|
+
update_options = {
|
22
|
+
:upsert => params["upsert"],
|
23
|
+
:doc_as_upsert => params["doc_as_upsert"]
|
24
|
+
}
|
25
|
+
common_options.merge! update_options if params["action"] == 'update'
|
26
|
+
|
27
|
+
LogStash::Outputs::ElasticSearch::HttpClient.new(
|
28
|
+
common_options.merge(:hosts => hosts, :logger => logger)
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.setup_proxy(logger, params)
|
33
|
+
proxy = params["proxy"]
|
34
|
+
return {} unless proxy
|
35
|
+
|
36
|
+
# Symbolize keys
|
37
|
+
proxy = if proxy.is_a?(Hash)
|
38
|
+
Hash[proxy.map {|k,v| [k.to_sym, v]}]
|
39
|
+
elsif proxy.is_a?(String)
|
40
|
+
proxy
|
41
|
+
else
|
42
|
+
raise LogStash::ConfigurationError, "Expected 'proxy' to be a string or hash, not '#{proxy}''!"
|
43
|
+
end
|
44
|
+
|
45
|
+
return {:proxy => proxy}
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.setup_ssl(logger, params)
|
49
|
+
return {} unless params["ssl"]
|
50
|
+
|
51
|
+
cacert, truststore, truststore_password, keystore, keystore_password =
|
52
|
+
params.values_at('cacert', 'truststore', 'truststore_password', 'keystore', 'keystore_password')
|
53
|
+
|
54
|
+
if cacert && truststore
|
55
|
+
raise(LogStash::ConfigurationError, "Use either \"cacert\" or \"truststore\" when configuring the CA certificate") if truststore
|
56
|
+
end
|
57
|
+
|
58
|
+
ssl_options = {}
|
59
|
+
|
60
|
+
if cacert
|
61
|
+
ssl_options[:ca_file] = cacert
|
62
|
+
elsif truststore
|
63
|
+
ssl_options[:truststore_password] = truststore_password.value if truststore_password
|
64
|
+
end
|
65
|
+
|
66
|
+
ssl_options[:truststore] = truststore if truststore
|
67
|
+
if keystore
|
68
|
+
ssl_options[:keystore] = keystore
|
69
|
+
ssl_options[:keystore_password] = keystore_password.value if keystore_password
|
70
|
+
end
|
71
|
+
if !params["ssl_certificate_verification"]
|
72
|
+
logger.warn [
|
73
|
+
"** WARNING ** Detected UNSAFE options in elasticsearch output configuration!",
|
74
|
+
"** WARNING ** You have enabled encryption but DISABLED certificate verification.",
|
75
|
+
"** WARNING ** To make sure your data is secure change :ssl_certificate_verification to true"
|
76
|
+
].join("\n")
|
77
|
+
ssl_options[:verify] = false
|
78
|
+
end
|
79
|
+
{ ssl: ssl_options }
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.setup_basic_auth(logger, params)
|
83
|
+
user, password = params["user"], params["password"]
|
84
|
+
return {} unless user && password
|
85
|
+
|
86
|
+
{
|
87
|
+
:user => user,
|
88
|
+
:password => password.value
|
89
|
+
}
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end; end; end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module LogStash; module Outputs; class ElasticSearch
|
2
|
+
class TemplateManager
|
3
|
+
# To be mixed into the elasticsearch plugin base
|
4
|
+
def self.install_template(plugin)
|
5
|
+
return unless plugin.manage_template
|
6
|
+
plugin.logger.info("Using mapping template from", :path => plugin.template)
|
7
|
+
template = get_template(plugin.template)
|
8
|
+
plugin.logger.info("Attempting to install template", :manage_template => template)
|
9
|
+
install(plugin.client, plugin.template_name, template, plugin.template_overwrite)
|
10
|
+
rescue => e
|
11
|
+
plugin.logger.error("Failed to install template.", :message => e.message, :class => e.class.name)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def self.get_template(path)
|
17
|
+
template_path = path || default_template_path
|
18
|
+
read_template_file(template_path)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.install(client, template_name, template, template_overwrite)
|
22
|
+
client.template_install(template_name, template, template_overwrite)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.default_template_path
|
26
|
+
::File.expand_path('elasticsearch-template.json', ::File.dirname(__FILE__))
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.read_template_file(template_path)
|
30
|
+
raise ArgumentError, "Template file '#{@template_path}' could not be found!" unless ::File.exists?(template_path)
|
31
|
+
template_data = ::IO.read(template_path)
|
32
|
+
LogStash::Json.load(template_data)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end end end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
|
3
3
|
s.name = 'logstash-output-elasticsearch'
|
4
|
-
s.version = '2.1.
|
4
|
+
s.version = '2.1.4'
|
5
5
|
s.licenses = ['apache-2.0']
|
6
6
|
s.summary = "Logstash Output to Elasticsearch"
|
7
7
|
s.description = "Output events to elasticsearch"
|
@@ -24,7 +24,7 @@ Gem::Specification.new do |s|
|
|
24
24
|
s.add_runtime_dependency 'elasticsearch', ['>= 1.0.13', '~> 1.0']
|
25
25
|
s.add_runtime_dependency 'stud', ['>= 0.0.17', '~> 0.0']
|
26
26
|
s.add_runtime_dependency 'cabin', ['~> 0.6']
|
27
|
-
s.add_runtime_dependency "logstash-core", ">= 2.0.0
|
27
|
+
s.add_runtime_dependency "logstash-core", ">= 2.0.0", "< 3.0.0"
|
28
28
|
|
29
29
|
s.add_development_dependency 'ftw', '~> 0.0.42'
|
30
30
|
s.add_development_dependency 'logstash-codec-plain'
|
data/spec/es_spec_helper.rb
CHANGED
@@ -13,8 +13,9 @@ CONTAINER_TAG = "1.6"
|
|
13
13
|
DOCKER_INTEGRATION = ENV["DOCKER_INTEGRATION"]
|
14
14
|
|
15
15
|
module ESHelper
|
16
|
-
def
|
17
|
-
DOCKER_INTEGRATION ? Longshoreman.new.get_host_ip : "127.0.0.1"
|
16
|
+
def get_host_port
|
17
|
+
addr = DOCKER_INTEGRATION ? Longshoreman.new.get_host_ip : "127.0.0.1"
|
18
|
+
"#{addr}:#{get_port}"
|
18
19
|
end
|
19
20
|
|
20
21
|
def get_port
|
@@ -26,7 +27,7 @@ module ESHelper
|
|
26
27
|
end
|
27
28
|
|
28
29
|
def get_client
|
29
|
-
Elasticsearch::Client.new(:
|
30
|
+
Elasticsearch::Client.new(:hosts => [get_host_port])
|
30
31
|
end
|
31
32
|
end
|
32
33
|
|
@@ -34,7 +35,6 @@ end
|
|
34
35
|
RSpec.configure do |config|
|
35
36
|
config.include ESHelper
|
36
37
|
|
37
|
-
|
38
38
|
if DOCKER_INTEGRATION
|
39
39
|
# this :all hook gets run before every describe block that is tagged with :integration => true.
|
40
40
|
config.before(:all, :integration => true) do
|
@@ -9,8 +9,7 @@ describe "client create actions", :integration => true do
|
|
9
9
|
"manage_template" => true,
|
10
10
|
"index" => "logstash-create",
|
11
11
|
"template_overwrite" => true,
|
12
|
-
"hosts" =>
|
13
|
-
"port" => get_port(),
|
12
|
+
"hosts" => get_host_port(),
|
14
13
|
"action" => action
|
15
14
|
}
|
16
15
|
settings['document_id'] = id unless id.nil?
|
@@ -31,7 +30,7 @@ describe "client create actions", :integration => true do
|
|
31
30
|
subject = get_es_output("create", "id123")
|
32
31
|
subject.register
|
33
32
|
subject.receive(LogStash::Event.new("message" => "sample message here"))
|
34
|
-
subject.
|
33
|
+
subject.flush
|
35
34
|
@es.indices.refresh
|
36
35
|
# Wait or fail until everything's indexed.
|
37
36
|
Stud::try(3.times) do
|
@@ -44,7 +43,7 @@ describe "client create actions", :integration => true do
|
|
44
43
|
subject = get_es_output("create")
|
45
44
|
subject.register
|
46
45
|
subject.receive(LogStash::Event.new("message" => "sample message here"))
|
47
|
-
subject.
|
46
|
+
subject.flush
|
48
47
|
@es.indices.refresh
|
49
48
|
# Wait or fail until everything's indexed.
|
50
49
|
Stud::try(3.times) do
|
@@ -16,7 +16,7 @@ shared_examples "an indexer" do
|
|
16
16
|
end
|
17
17
|
|
18
18
|
it "ships events" do
|
19
|
-
index_url = "http://#{
|
19
|
+
index_url = "http://#{get_host_port}/#{index}"
|
20
20
|
|
21
21
|
ftw = FTW::Agent.new
|
22
22
|
ftw.post!("#{index_url}/_refresh")
|
@@ -46,8 +46,7 @@ describe "an indexer with custom index_type", :integration => true do
|
|
46
46
|
it_behaves_like "an indexer" do
|
47
47
|
let(:config) {
|
48
48
|
{
|
49
|
-
"hosts" =>
|
50
|
-
"port" => get_port,
|
49
|
+
"hosts" => get_host_port,
|
51
50
|
"index" => index,
|
52
51
|
"flush_size" => flush_size
|
53
52
|
}
|
@@ -60,8 +59,7 @@ describe "an indexer with no type value set (default to logs)", :integration =>
|
|
60
59
|
let(:type) { "logs" }
|
61
60
|
let(:config) {
|
62
61
|
{
|
63
|
-
"hosts" =>
|
64
|
-
"port" => get_port,
|
62
|
+
"hosts" => get_host_port,
|
65
63
|
"index" => index,
|
66
64
|
"flush_size" => flush_size
|
67
65
|
}
|
@@ -22,7 +22,7 @@ describe "failures in bulk class expected behavior", :integration => true do
|
|
22
22
|
}
|
23
23
|
end
|
24
24
|
|
25
|
-
allow_any_instance_of(LogStash::Outputs::
|
25
|
+
allow_any_instance_of(LogStash::Outputs::ElasticSearch::HttpClient).to receive(:bulk).and_return(*expanded_responses)
|
26
26
|
end
|
27
27
|
|
28
28
|
subject! do
|
@@ -30,8 +30,7 @@ describe "failures in bulk class expected behavior", :integration => true do
|
|
30
30
|
"manage_template" => true,
|
31
31
|
"index" => "logstash-2014.11.17",
|
32
32
|
"template_overwrite" => true,
|
33
|
-
"hosts" =>
|
34
|
-
"port" => get_port(),
|
33
|
+
"hosts" => get_host_port(),
|
35
34
|
"retry_max_items" => 10,
|
36
35
|
"retry_max_interval" => 1,
|
37
36
|
"max_retries" => max_retries
|
@@ -50,28 +49,34 @@ describe "failures in bulk class expected behavior", :integration => true do
|
|
50
49
|
@es.indices.refresh
|
51
50
|
end
|
52
51
|
|
52
|
+
after :each do
|
53
|
+
subject.close
|
54
|
+
end
|
55
|
+
|
53
56
|
it "should return no errors if all bulk actions are successful" do
|
54
57
|
mock_actions_with_response({"errors" => false})
|
55
58
|
expect(subject).to receive(:submit).with([action1, action2]).once.and_call_original
|
56
59
|
subject.register
|
57
60
|
subject.receive(event1)
|
58
61
|
subject.receive(event2)
|
59
|
-
subject.
|
62
|
+
subject.flush
|
60
63
|
sleep(2)
|
61
64
|
end
|
62
65
|
|
63
|
-
it "
|
66
|
+
it "retry exceptions within the submit body" do
|
64
67
|
call_count = 0
|
65
|
-
|
68
|
+
subject.register
|
69
|
+
|
70
|
+
expect(subject.client).to receive(:bulk).with(anything).exactly(3).times do
|
66
71
|
if (call_count += 1) <= 2
|
67
72
|
raise "error first two times"
|
68
73
|
else
|
69
74
|
{"errors" => false}
|
70
75
|
end
|
71
76
|
end
|
72
|
-
|
77
|
+
|
73
78
|
subject.receive(event1)
|
74
|
-
subject.
|
79
|
+
subject.flush
|
75
80
|
end
|
76
81
|
|
77
82
|
it "should retry actions with response status of 503" do
|
@@ -87,17 +92,19 @@ describe "failures in bulk class expected behavior", :integration => true do
|
|
87
92
|
subject.receive(event1)
|
88
93
|
subject.receive(event1)
|
89
94
|
subject.receive(event2)
|
90
|
-
subject.
|
95
|
+
subject.flush
|
91
96
|
sleep(3)
|
92
97
|
end
|
93
98
|
|
94
99
|
it "should retry actions with response status of 429" do
|
100
|
+
subject.register
|
101
|
+
|
95
102
|
mock_actions_with_response({"errors" => true, "statuses" => [429]},
|
96
103
|
{"errors" => false})
|
97
104
|
expect(subject).to receive(:submit).with([action1]).twice.and_call_original
|
98
|
-
|
105
|
+
|
99
106
|
subject.receive(event1)
|
100
|
-
subject.
|
107
|
+
subject.flush
|
101
108
|
sleep(3)
|
102
109
|
end
|
103
110
|
|
@@ -108,17 +115,17 @@ describe "failures in bulk class expected behavior", :integration => true do
|
|
108
115
|
{"errors" => true, "statuses" => [429]},
|
109
116
|
{"errors" => true, "statuses" => [429]},
|
110
117
|
{"errors" => true, "statuses" => [429]})
|
111
|
-
expect(subject).to receive(:submit).with([action1]).exactly(max_retries).times.and_call_original
|
118
|
+
expect(subject).to receive(:submit).with([action1]).exactly(max_retries+1).times.and_call_original
|
112
119
|
subject.register
|
113
120
|
subject.receive(event1)
|
114
|
-
subject.
|
115
|
-
sleep(
|
121
|
+
subject.flush
|
122
|
+
sleep(5)
|
116
123
|
end
|
117
124
|
|
118
125
|
it "non-retryable errors like mapping errors (400) should be dropped and not be retried (unfortunately)" do
|
119
126
|
subject.register
|
120
127
|
subject.receive(invalid_event)
|
121
|
-
expect(subject).
|
128
|
+
expect(subject).to receive(:submit).once.and_call_original
|
122
129
|
subject.close
|
123
130
|
|
124
131
|
@es.indices.refresh
|
@@ -132,7 +139,7 @@ describe "failures in bulk class expected behavior", :integration => true do
|
|
132
139
|
it "successful requests should not be appended to retry queue" do
|
133
140
|
subject.register
|
134
141
|
subject.receive(event1)
|
135
|
-
expect(subject).
|
142
|
+
expect(subject).to receive(:submit).once.and_call_original
|
136
143
|
subject.close
|
137
144
|
@es.indices.refresh
|
138
145
|
sleep(5)
|
@@ -18,7 +18,7 @@ shared_examples "a routing indexer" do
|
|
18
18
|
|
19
19
|
|
20
20
|
it "ships events" do
|
21
|
-
index_url = "http://#{
|
21
|
+
index_url = "http://#{get_host_port()}/#{index}"
|
22
22
|
|
23
23
|
ftw = FTW::Agent.new
|
24
24
|
ftw.post!("#{index_url}/_refresh")
|
@@ -40,8 +40,7 @@ describe "(http protocol) index events with static routing", :integration => tru
|
|
40
40
|
let(:routing) { "test" }
|
41
41
|
let(:config) {
|
42
42
|
{
|
43
|
-
"hosts" =>
|
44
|
-
"port" => get_port,
|
43
|
+
"hosts" => get_host_port,
|
45
44
|
"index" => index,
|
46
45
|
"flush_size" => flush_size,
|
47
46
|
"routing" => routing
|
@@ -55,8 +54,7 @@ describe "(http_protocol) index events with fieldref in routing value", :integra
|
|
55
54
|
let(:routing) { "test" }
|
56
55
|
let(:config) {
|
57
56
|
{
|
58
|
-
"hosts" =>
|
59
|
-
"port" => get_port,
|
57
|
+
"hosts" => get_host_port,
|
60
58
|
"index" => index,
|
61
59
|
"flush_size" => flush_size,
|
62
60
|
"routing" => "%{message}"
|
@@ -25,7 +25,7 @@ describe "send messages to ElasticSearch using HTTPS", :elasticsearch_secure =>
|
|
25
25
|
it "sends events to ES" do
|
26
26
|
expect {
|
27
27
|
subject.receive(LogStash::Event.new("message" => "sample message here"))
|
28
|
-
subject.
|
28
|
+
subject.flush
|
29
29
|
}.to_not raise_error
|
30
30
|
end
|
31
31
|
end
|
@@ -49,7 +49,7 @@ describe "connect using HTTP Authentication", :elasticsearch_secure => true do
|
|
49
49
|
it "sends events to ES" do
|
50
50
|
expect {
|
51
51
|
subject.receive(LogStash::Event.new("message" => "sample message here"))
|
52
|
-
subject.
|
52
|
+
subject.flush
|
53
53
|
}.to_not raise_error
|
54
54
|
end
|
55
55
|
end
|
@@ -79,7 +79,7 @@ describe "send messages to ElasticSearch using HTTPS", :elasticsearch_secure =>
|
|
79
79
|
it "sends events to ES" do
|
80
80
|
expect {
|
81
81
|
subject.receive(LogStash::Event.new("message" => "sample message here"))
|
82
|
-
subject.
|
82
|
+
subject.flush
|
83
83
|
}.to_not raise_error
|
84
84
|
end
|
85
85
|
end
|
@@ -102,7 +102,7 @@ describe "connect using HTTP Authentication", :elasticsearch_secure => true do
|
|
102
102
|
it "sends events to ES" do
|
103
103
|
expect {
|
104
104
|
subject.receive(LogStash::Event.new("message" => "sample message here"))
|
105
|
-
subject.
|
105
|
+
subject.flush
|
106
106
|
}.to_not raise_error
|
107
107
|
end
|
108
108
|
end
|
@@ -6,8 +6,7 @@ describe "index template expected behavior", :integration => true do
|
|
6
6
|
settings = {
|
7
7
|
"manage_template" => true,
|
8
8
|
"template_overwrite" => true,
|
9
|
-
"hosts" => "#{
|
10
|
-
"port" => "#{get_port()}"
|
9
|
+
"hosts" => "#{get_host_port()}"
|
11
10
|
}
|
12
11
|
next LogStash::Outputs::ElasticSearch.new(settings)
|
13
12
|
end
|
@@ -32,7 +31,7 @@ describe "index template expected behavior", :integration => true do
|
|
32
31
|
subject.receive(LogStash::Event.new("country" => "us"))
|
33
32
|
subject.receive(LogStash::Event.new("country" => "at"))
|
34
33
|
subject.receive(LogStash::Event.new("geoip" => { "location" => [ 0.0, 0.0 ] }))
|
35
|
-
subject.
|
34
|
+
subject.flush
|
36
35
|
@es.indices.refresh
|
37
36
|
|
38
37
|
# Wait or fail until everything's indexed.
|