logstash-output-elasticsearch 2.1.2-java → 2.1.4-java
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|