logstash-output-elasticsearch-test 10.3.0-x86_64-linux
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +397 -0
- data/CONTRIBUTORS +33 -0
- data/Gemfile +15 -0
- data/LICENSE +13 -0
- data/NOTICE.TXT +5 -0
- data/README.md +106 -0
- data/docs/index.asciidoc +899 -0
- data/lib/logstash/outputs/elasticsearch/common.rb +441 -0
- data/lib/logstash/outputs/elasticsearch/common_configs.rb +167 -0
- data/lib/logstash/outputs/elasticsearch/default-ilm-policy.json +14 -0
- data/lib/logstash/outputs/elasticsearch/elasticsearch-template-es2x.json +95 -0
- data/lib/logstash/outputs/elasticsearch/elasticsearch-template-es5x.json +46 -0
- data/lib/logstash/outputs/elasticsearch/elasticsearch-template-es6x.json +45 -0
- data/lib/logstash/outputs/elasticsearch/elasticsearch-template-es7x.json +44 -0
- data/lib/logstash/outputs/elasticsearch/elasticsearch-template-es8x.json +44 -0
- data/lib/logstash/outputs/elasticsearch/http_client/manticore_adapter.rb +131 -0
- data/lib/logstash/outputs/elasticsearch/http_client/pool.rb +495 -0
- data/lib/logstash/outputs/elasticsearch/http_client.rb +432 -0
- data/lib/logstash/outputs/elasticsearch/http_client_builder.rb +159 -0
- data/lib/logstash/outputs/elasticsearch/ilm.rb +113 -0
- data/lib/logstash/outputs/elasticsearch/template_manager.rb +61 -0
- data/lib/logstash/outputs/elasticsearch.rb +263 -0
- data/logstash-output-elasticsearch.gemspec +33 -0
- data/spec/es_spec_helper.rb +189 -0
- data/spec/fixtures/_nodes/2x_1x.json +27 -0
- data/spec/fixtures/_nodes/5x_6x.json +81 -0
- data/spec/fixtures/_nodes/7x.json +92 -0
- data/spec/fixtures/htpasswd +2 -0
- data/spec/fixtures/nginx_reverse_proxy.conf +22 -0
- data/spec/fixtures/scripts/groovy/scripted_update.groovy +2 -0
- data/spec/fixtures/scripts/groovy/scripted_update_nested.groovy +2 -0
- data/spec/fixtures/scripts/groovy/scripted_upsert.groovy +2 -0
- data/spec/fixtures/scripts/painless/scripted_update.painless +2 -0
- data/spec/fixtures/scripts/painless/scripted_update_nested.painless +1 -0
- data/spec/fixtures/scripts/painless/scripted_upsert.painless +1 -0
- data/spec/fixtures/template-with-policy-es6x.json +48 -0
- data/spec/fixtures/template-with-policy-es7x.json +45 -0
- data/spec/fixtures/test_certs/ca/ca.crt +32 -0
- data/spec/fixtures/test_certs/ca/ca.key +51 -0
- data/spec/fixtures/test_certs/test.crt +36 -0
- data/spec/fixtures/test_certs/test.key +51 -0
- data/spec/integration/outputs/compressed_indexing_spec.rb +69 -0
- data/spec/integration/outputs/create_spec.rb +67 -0
- data/spec/integration/outputs/delete_spec.rb +65 -0
- data/spec/integration/outputs/groovy_update_spec.rb +150 -0
- data/spec/integration/outputs/ilm_spec.rb +531 -0
- data/spec/integration/outputs/index_spec.rb +178 -0
- data/spec/integration/outputs/index_version_spec.rb +102 -0
- data/spec/integration/outputs/ingest_pipeline_spec.rb +74 -0
- data/spec/integration/outputs/metrics_spec.rb +70 -0
- data/spec/integration/outputs/no_es_on_startup_spec.rb +58 -0
- data/spec/integration/outputs/painless_update_spec.rb +189 -0
- data/spec/integration/outputs/parent_spec.rb +102 -0
- data/spec/integration/outputs/retry_spec.rb +169 -0
- data/spec/integration/outputs/routing_spec.rb +61 -0
- data/spec/integration/outputs/sniffer_spec.rb +133 -0
- data/spec/integration/outputs/templates_5x_spec.rb +98 -0
- data/spec/integration/outputs/templates_spec.rb +98 -0
- data/spec/integration/outputs/update_spec.rb +116 -0
- data/spec/support/elasticsearch/api/actions/delete_ilm_policy.rb +19 -0
- data/spec/support/elasticsearch/api/actions/get_alias.rb +18 -0
- data/spec/support/elasticsearch/api/actions/get_ilm_policy.rb +18 -0
- data/spec/support/elasticsearch/api/actions/put_alias.rb +24 -0
- data/spec/support/elasticsearch/api/actions/put_ilm_policy.rb +25 -0
- data/spec/unit/http_client_builder_spec.rb +185 -0
- data/spec/unit/outputs/elasticsearch/http_client/manticore_adapter_spec.rb +149 -0
- data/spec/unit/outputs/elasticsearch/http_client/pool_spec.rb +274 -0
- data/spec/unit/outputs/elasticsearch/http_client_spec.rb +250 -0
- data/spec/unit/outputs/elasticsearch/template_manager_spec.rb +25 -0
- data/spec/unit/outputs/elasticsearch_proxy_spec.rb +72 -0
- data/spec/unit/outputs/elasticsearch_spec.rb +675 -0
- data/spec/unit/outputs/elasticsearch_ssl_spec.rb +82 -0
- data/spec/unit/outputs/error_whitelist_spec.rb +54 -0
- metadata +300 -0
@@ -0,0 +1,441 @@
|
|
1
|
+
require "logstash/outputs/elasticsearch/template_manager"
|
2
|
+
|
3
|
+
module LogStash; module Outputs; class ElasticSearch;
|
4
|
+
module Common
|
5
|
+
attr_reader :client, :hosts
|
6
|
+
|
7
|
+
# These codes apply to documents, not at the request level
|
8
|
+
DOC_DLQ_CODES = [400, 404]
|
9
|
+
DOC_SUCCESS_CODES = [200, 201]
|
10
|
+
DOC_CONFLICT_CODE = 409
|
11
|
+
|
12
|
+
# When you use external versioning, you are communicating that you want
|
13
|
+
# to ignore conflicts. More obviously, since an external version is a
|
14
|
+
# constant part of the incoming document, we should not retry, as retrying
|
15
|
+
# will never succeed.
|
16
|
+
VERSION_TYPES_PERMITTING_CONFLICT = ["external", "external_gt", "external_gte"]
|
17
|
+
|
18
|
+
def register
|
19
|
+
@template_installed = Concurrent::AtomicBoolean.new(false)
|
20
|
+
@stopping = Concurrent::AtomicBoolean.new(false)
|
21
|
+
# To support BWC, we check if DLQ exists in core (< 5.4). If it doesn't, we use nil to resort to previous behavior.
|
22
|
+
@dlq_writer = dlq_enabled? ? execution_context.dlq_writer : nil
|
23
|
+
|
24
|
+
fill_hosts_from_cloud_id
|
25
|
+
fill_user_password_from_cloud_auth
|
26
|
+
setup_hosts # properly sets @hosts
|
27
|
+
build_client
|
28
|
+
setup_after_successful_connection
|
29
|
+
check_action_validity
|
30
|
+
@bulk_request_metrics = metric.namespace(:bulk_requests)
|
31
|
+
@document_level_metrics = metric.namespace(:documents)
|
32
|
+
@logger.info("New Elasticsearch output", :class => self.class.name, :hosts => @hosts.map(&:sanitized).map(&:to_s))
|
33
|
+
end
|
34
|
+
|
35
|
+
# Receive an array of events and immediately attempt to index them (no buffering)
|
36
|
+
def multi_receive(events)
|
37
|
+
until @template_installed.true?
|
38
|
+
sleep 1
|
39
|
+
end
|
40
|
+
retrying_submit(events.map {|e| event_action_tuple(e)})
|
41
|
+
end
|
42
|
+
|
43
|
+
def setup_after_successful_connection
|
44
|
+
@template_installer ||= Thread.new do
|
45
|
+
sleep_interval = @retry_initial_interval
|
46
|
+
until successful_connection? || @stopping.true?
|
47
|
+
@logger.debug("Waiting for connectivity to Elasticsearch cluster. Retrying in #{sleep_interval}s")
|
48
|
+
Stud.stoppable_sleep(sleep_interval) { @stopping.true? }
|
49
|
+
sleep_interval = next_sleep_interval(sleep_interval)
|
50
|
+
end
|
51
|
+
if successful_connection?
|
52
|
+
discover_cluster_uuid
|
53
|
+
install_template
|
54
|
+
setup_ilm if ilm_in_use?
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def stop_template_installer
|
60
|
+
@template_installer.join unless @template_installer.nil?
|
61
|
+
end
|
62
|
+
|
63
|
+
def successful_connection?
|
64
|
+
!!maximum_seen_major_version
|
65
|
+
end
|
66
|
+
|
67
|
+
def use_event_type?(client)
|
68
|
+
client.maximum_seen_major_version < 8
|
69
|
+
end
|
70
|
+
|
71
|
+
# Convert the event into a 3-tuple of action, params, and event
|
72
|
+
def event_action_tuple(event)
|
73
|
+
action = event.sprintf(@action)
|
74
|
+
|
75
|
+
params = {
|
76
|
+
:_id => @document_id ? event.sprintf(@document_id) : nil,
|
77
|
+
:_index => event.sprintf(@index),
|
78
|
+
routing_field_name => @routing ? event.sprintf(@routing) : nil
|
79
|
+
}
|
80
|
+
|
81
|
+
params[:_type] = get_event_type(event) if use_event_type?(client)
|
82
|
+
|
83
|
+
if @pipeline
|
84
|
+
params[:pipeline] = event.sprintf(@pipeline)
|
85
|
+
end
|
86
|
+
|
87
|
+
if @parent
|
88
|
+
if @join_field
|
89
|
+
join_value = event.get(@join_field)
|
90
|
+
parent_value = event.sprintf(@parent)
|
91
|
+
event.set(@join_field, { "name" => join_value, "parent" => parent_value })
|
92
|
+
params[routing_field_name] = event.sprintf(@parent)
|
93
|
+
else
|
94
|
+
params[:parent] = event.sprintf(@parent)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
if action == 'update'
|
99
|
+
params[:_upsert] = LogStash::Json.load(event.sprintf(@upsert)) if @upsert != ""
|
100
|
+
params[:_script] = event.sprintf(@script) if @script != ""
|
101
|
+
params[retry_on_conflict_action_name] = @retry_on_conflict
|
102
|
+
end
|
103
|
+
|
104
|
+
if @version
|
105
|
+
params[:version] = event.sprintf(@version)
|
106
|
+
end
|
107
|
+
|
108
|
+
if @version_type
|
109
|
+
params[:version_type] = event.sprintf(@version_type)
|
110
|
+
end
|
111
|
+
|
112
|
+
[action, params, event]
|
113
|
+
end
|
114
|
+
|
115
|
+
def setup_hosts
|
116
|
+
@hosts = Array(@hosts)
|
117
|
+
if @hosts.empty?
|
118
|
+
@logger.info("No 'host' set in elasticsearch output. Defaulting to localhost")
|
119
|
+
@hosts.replace(["localhost"])
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def hosts_default?(hosts)
|
124
|
+
# NOTE: would be nice if pipeline allowed us a clean way to detect a config default :
|
125
|
+
hosts.is_a?(Array) && hosts.size == 1 && hosts.first.equal?(CommonConfigs::DEFAULT_HOST)
|
126
|
+
end
|
127
|
+
private :hosts_default?
|
128
|
+
|
129
|
+
def fill_hosts_from_cloud_id
|
130
|
+
return unless @cloud_id
|
131
|
+
|
132
|
+
if @hosts && !hosts_default?(@hosts)
|
133
|
+
raise LogStash::ConfigurationError, 'Both cloud_id and hosts specified, please only use one of those.'
|
134
|
+
end
|
135
|
+
@hosts = parse_host_uri_from_cloud_id(@cloud_id)
|
136
|
+
end
|
137
|
+
|
138
|
+
def fill_user_password_from_cloud_auth
|
139
|
+
return unless @cloud_auth
|
140
|
+
|
141
|
+
if @user || @password
|
142
|
+
raise LogStash::ConfigurationError, 'Both cloud_auth and user/password specified, please only use one.'
|
143
|
+
end
|
144
|
+
@user, @password = parse_user_password_from_cloud_auth(@cloud_auth)
|
145
|
+
params['user'], params['password'] = @user, @password
|
146
|
+
end
|
147
|
+
|
148
|
+
def parse_host_uri_from_cloud_id(cloud_id)
|
149
|
+
begin # might not be available on older LS
|
150
|
+
require 'logstash/util/cloud_setting_id'
|
151
|
+
rescue LoadError
|
152
|
+
raise LogStash::ConfigurationError, 'The cloud_id setting is not supported by your version of Logstash, ' +
|
153
|
+
'please upgrade your installation (or set hosts instead).'
|
154
|
+
end
|
155
|
+
|
156
|
+
begin
|
157
|
+
cloud_id = LogStash::Util::CloudSettingId.new(cloud_id) # already does append ':{port}' to host
|
158
|
+
rescue ArgumentError => e
|
159
|
+
raise LogStash::ConfigurationError, e.message.to_s.sub(/Cloud Id/i, 'cloud_id')
|
160
|
+
end
|
161
|
+
cloud_uri = "#{cloud_id.elasticsearch_scheme}://#{cloud_id.elasticsearch_host}"
|
162
|
+
LogStash::Util::SafeURI.new(cloud_uri)
|
163
|
+
end
|
164
|
+
private :parse_host_uri_from_cloud_id
|
165
|
+
|
166
|
+
def parse_user_password_from_cloud_auth(cloud_auth)
|
167
|
+
begin # might not be available on older LS
|
168
|
+
require 'logstash/util/cloud_setting_auth'
|
169
|
+
rescue LoadError
|
170
|
+
raise LogStash::ConfigurationError, 'The cloud_auth setting is not supported by your version of Logstash, ' +
|
171
|
+
'please upgrade your installation (or set user/password instead).'
|
172
|
+
end
|
173
|
+
|
174
|
+
cloud_auth = cloud_auth.value if cloud_auth.is_a?(LogStash::Util::Password)
|
175
|
+
begin
|
176
|
+
cloud_auth = LogStash::Util::CloudSettingAuth.new(cloud_auth)
|
177
|
+
rescue ArgumentError => e
|
178
|
+
raise LogStash::ConfigurationError, e.message.to_s.sub(/Cloud Auth/i, 'cloud_auth')
|
179
|
+
end
|
180
|
+
[ cloud_auth.username, cloud_auth.password ]
|
181
|
+
end
|
182
|
+
private :parse_user_password_from_cloud_auth
|
183
|
+
|
184
|
+
def maximum_seen_major_version
|
185
|
+
client.maximum_seen_major_version
|
186
|
+
end
|
187
|
+
|
188
|
+
def routing_field_name
|
189
|
+
maximum_seen_major_version >= 6 ? :routing : :_routing
|
190
|
+
end
|
191
|
+
|
192
|
+
def retry_on_conflict_action_name
|
193
|
+
maximum_seen_major_version >= 7 ? :retry_on_conflict : :_retry_on_conflict
|
194
|
+
end
|
195
|
+
|
196
|
+
def install_template
|
197
|
+
TemplateManager.install_template(self)
|
198
|
+
@template_installed.make_true
|
199
|
+
end
|
200
|
+
|
201
|
+
def discover_cluster_uuid
|
202
|
+
return unless defined?(plugin_metadata)
|
203
|
+
cluster_info = client.get('/')
|
204
|
+
plugin_metadata.set(:cluster_uuid, cluster_info['cluster_uuid'])
|
205
|
+
rescue => e
|
206
|
+
# TODO introducing this logging message breaks many tests that need refactoring
|
207
|
+
# @logger.error("Unable to retrieve elasticsearch cluster uuid", error => e.message)
|
208
|
+
end
|
209
|
+
|
210
|
+
def check_action_validity
|
211
|
+
raise LogStash::ConfigurationError, "No action specified!" unless @action
|
212
|
+
|
213
|
+
# If we're using string interpolation, we're good!
|
214
|
+
return if @action =~ /%{.+}/
|
215
|
+
return if valid_actions.include?(@action)
|
216
|
+
|
217
|
+
raise LogStash::ConfigurationError, "Action '#{@action}' is invalid! Pick one of #{valid_actions} or use a sprintf style statement"
|
218
|
+
end
|
219
|
+
|
220
|
+
# To be overidden by the -java version
|
221
|
+
VALID_HTTP_ACTIONS=["index", "delete", "create", "update"]
|
222
|
+
def valid_actions
|
223
|
+
VALID_HTTP_ACTIONS
|
224
|
+
end
|
225
|
+
|
226
|
+
def retrying_submit(actions)
|
227
|
+
# Initially we submit the full list of actions
|
228
|
+
submit_actions = actions
|
229
|
+
|
230
|
+
sleep_interval = @retry_initial_interval
|
231
|
+
|
232
|
+
while submit_actions && submit_actions.length > 0
|
233
|
+
|
234
|
+
# We retry with whatever is didn't succeed
|
235
|
+
begin
|
236
|
+
submit_actions = submit(submit_actions)
|
237
|
+
if submit_actions && submit_actions.size > 0
|
238
|
+
@logger.info("Retrying individual bulk actions that failed or were rejected by the previous bulk request.", :count => submit_actions.size)
|
239
|
+
end
|
240
|
+
rescue => e
|
241
|
+
@logger.error("Encountered an unexpected error submitting a bulk request! Will retry.",
|
242
|
+
:error_message => e.message,
|
243
|
+
:class => e.class.name,
|
244
|
+
:backtrace => e.backtrace)
|
245
|
+
end
|
246
|
+
|
247
|
+
# Everything was a success!
|
248
|
+
break if !submit_actions || submit_actions.empty?
|
249
|
+
|
250
|
+
# If we're retrying the action sleep for the recommended interval
|
251
|
+
# Double the interval for the next time through to achieve exponential backoff
|
252
|
+
Stud.stoppable_sleep(sleep_interval) { @stopping.true? }
|
253
|
+
sleep_interval = next_sleep_interval(sleep_interval)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def sleep_for_interval(sleep_interval)
|
258
|
+
Stud.stoppable_sleep(sleep_interval) { @stopping.true? }
|
259
|
+
next_sleep_interval(sleep_interval)
|
260
|
+
end
|
261
|
+
|
262
|
+
def next_sleep_interval(current_interval)
|
263
|
+
doubled = current_interval * 2
|
264
|
+
doubled > @retry_max_interval ? @retry_max_interval : doubled
|
265
|
+
end
|
266
|
+
|
267
|
+
def submit(actions)
|
268
|
+
bulk_response = safe_bulk(actions)
|
269
|
+
|
270
|
+
# If the response is nil that means we were in a retry loop
|
271
|
+
# and aborted since we're shutting down
|
272
|
+
return if bulk_response.nil?
|
273
|
+
|
274
|
+
# If it did return and there are no errors we're good as well
|
275
|
+
if bulk_response["errors"]
|
276
|
+
@bulk_request_metrics.increment(:with_errors)
|
277
|
+
else
|
278
|
+
@bulk_request_metrics.increment(:successes)
|
279
|
+
@document_level_metrics.increment(:successes, actions.size)
|
280
|
+
return
|
281
|
+
end
|
282
|
+
|
283
|
+
actions_to_retry = []
|
284
|
+
bulk_response["items"].each_with_index do |response,idx|
|
285
|
+
action_type, action_props = response.first
|
286
|
+
|
287
|
+
status = action_props["status"]
|
288
|
+
failure = action_props["error"]
|
289
|
+
action = actions[idx]
|
290
|
+
action_params = action[1]
|
291
|
+
|
292
|
+
# Retry logic: If it is success, we move on. If it is a failure, we have 3 paths:
|
293
|
+
# - For 409, we log and drop. there is nothing we can do
|
294
|
+
# - For a mapping error, we send to dead letter queue for a human to intervene at a later point.
|
295
|
+
# - For everything else there's mastercard. Yep, and we retry indefinitely. This should fix #572 and other transient network issues
|
296
|
+
if DOC_SUCCESS_CODES.include?(status)
|
297
|
+
@document_level_metrics.increment(:successes)
|
298
|
+
next
|
299
|
+
elsif DOC_CONFLICT_CODE == status
|
300
|
+
@document_level_metrics.increment(:non_retryable_failures)
|
301
|
+
@logger.warn "Failed action.", status: status, action: action, response: response if !failure_type_logging_whitelist.include?(failure["type"])
|
302
|
+
next
|
303
|
+
elsif DOC_DLQ_CODES.include?(status)
|
304
|
+
handle_dlq_status("Could not index event to Elasticsearch.", action, status, response)
|
305
|
+
@document_level_metrics.increment(:non_retryable_failures)
|
306
|
+
next
|
307
|
+
else
|
308
|
+
# only log what the user whitelisted
|
309
|
+
@document_level_metrics.increment(:retryable_failures)
|
310
|
+
@logger.info "retrying failed action with response code: #{status} (#{failure})" if !failure_type_logging_whitelist.include?(failure["type"])
|
311
|
+
actions_to_retry << action
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
actions_to_retry
|
316
|
+
end
|
317
|
+
|
318
|
+
def handle_dlq_status(message, action, status, response)
|
319
|
+
# To support bwc, we check if DLQ exists. otherwise we log and drop event (previous behavior)
|
320
|
+
if @dlq_writer
|
321
|
+
# TODO: Change this to send a map with { :status => status, :action => action } in the future
|
322
|
+
@dlq_writer.write(action[2], "#{message} status: #{status}, action: #{action}, response: #{response}")
|
323
|
+
else
|
324
|
+
error_type = response.fetch('index', {}).fetch('error', {})['type']
|
325
|
+
if 'invalid_index_name_exception' == error_type
|
326
|
+
level = :error
|
327
|
+
else
|
328
|
+
level = :warn
|
329
|
+
end
|
330
|
+
@logger.send level, message, status: status, action: action, response: response
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
# Determine the correct value for the 'type' field for the given event
|
335
|
+
DEFAULT_EVENT_TYPE_ES6="doc".freeze
|
336
|
+
DEFAULT_EVENT_TYPE_ES7="_doc".freeze
|
337
|
+
def get_event_type(event)
|
338
|
+
# Set the 'type' value for the index.
|
339
|
+
type = if @document_type
|
340
|
+
event.sprintf(@document_type)
|
341
|
+
else
|
342
|
+
if client.maximum_seen_major_version < 6
|
343
|
+
event.get("type") || DEFAULT_EVENT_TYPE_ES6
|
344
|
+
elsif client.maximum_seen_major_version == 6
|
345
|
+
DEFAULT_EVENT_TYPE_ES6
|
346
|
+
elsif client.maximum_seen_major_version == 7
|
347
|
+
DEFAULT_EVENT_TYPE_ES7
|
348
|
+
else
|
349
|
+
nil
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
if !(type.is_a?(String) || type.is_a?(Numeric))
|
354
|
+
@logger.warn("Bad event type! Non-string/integer type value set!", :type_class => type.class, :type_value => type.to_s, :event => event)
|
355
|
+
end
|
356
|
+
|
357
|
+
type.to_s
|
358
|
+
end
|
359
|
+
|
360
|
+
# Rescue retryable errors during bulk submission
|
361
|
+
def safe_bulk(actions)
|
362
|
+
sleep_interval = @retry_initial_interval
|
363
|
+
begin
|
364
|
+
es_actions = actions.map {|action_type, params, event| [action_type, params, event.to_hash]}
|
365
|
+
response = @client.bulk(es_actions)
|
366
|
+
response
|
367
|
+
rescue ::LogStash::Outputs::ElasticSearch::HttpClient::Pool::HostUnreachableError => e
|
368
|
+
# If we can't even connect to the server let's just print out the URL (:hosts is actually a URL)
|
369
|
+
# and let the user sort it out from there
|
370
|
+
@logger.error(
|
371
|
+
"Attempted to send a bulk request to elasticsearch'"+
|
372
|
+
" but Elasticsearch appears to be unreachable or down!",
|
373
|
+
:error_message => e.message,
|
374
|
+
:class => e.class.name,
|
375
|
+
:will_retry_in_seconds => sleep_interval
|
376
|
+
)
|
377
|
+
@logger.debug("Failed actions for last bad bulk request!", :actions => actions)
|
378
|
+
|
379
|
+
# We retry until there are no errors! Errors should all go to the retry queue
|
380
|
+
sleep_interval = sleep_for_interval(sleep_interval)
|
381
|
+
@bulk_request_metrics.increment(:failures)
|
382
|
+
retry unless @stopping.true?
|
383
|
+
rescue ::LogStash::Outputs::ElasticSearch::HttpClient::Pool::NoConnectionAvailableError => e
|
384
|
+
@logger.error(
|
385
|
+
"Attempted to send a bulk request to elasticsearch, but no there are no living connections in the connection pool. Perhaps Elasticsearch is unreachable or down?",
|
386
|
+
:error_message => e.message,
|
387
|
+
:class => e.class.name,
|
388
|
+
:will_retry_in_seconds => sleep_interval
|
389
|
+
)
|
390
|
+
Stud.stoppable_sleep(sleep_interval) { @stopping.true? }
|
391
|
+
sleep_interval = next_sleep_interval(sleep_interval)
|
392
|
+
@bulk_request_metrics.increment(:failures)
|
393
|
+
retry unless @stopping.true?
|
394
|
+
rescue ::LogStash::Outputs::ElasticSearch::HttpClient::Pool::BadResponseCodeError => e
|
395
|
+
@bulk_request_metrics.increment(:failures)
|
396
|
+
log_hash = {:code => e.response_code, :url => e.url.sanitized.to_s}
|
397
|
+
log_hash[:body] = e.response_body if @logger.debug? # Generally this is too verbose
|
398
|
+
message = "Encountered a retryable error. Will Retry with exponential backoff "
|
399
|
+
|
400
|
+
# We treat 429s as a special case because these really aren't errors, but
|
401
|
+
# rather just ES telling us to back off a bit, which we do.
|
402
|
+
# The other retryable code is 503, which are true errors
|
403
|
+
# Even though we retry the user should be made aware of these
|
404
|
+
if e.response_code == 429
|
405
|
+
logger.debug(message, log_hash)
|
406
|
+
else
|
407
|
+
logger.error(message, log_hash)
|
408
|
+
end
|
409
|
+
|
410
|
+
sleep_interval = sleep_for_interval(sleep_interval)
|
411
|
+
retry
|
412
|
+
rescue => e
|
413
|
+
# Stuff that should never happen
|
414
|
+
# For all other errors print out full connection issues
|
415
|
+
@logger.error(
|
416
|
+
"An unknown error occurred sending a bulk request to Elasticsearch. We will retry indefinitely",
|
417
|
+
:error_message => e.message,
|
418
|
+
:error_class => e.class.name,
|
419
|
+
:backtrace => e.backtrace
|
420
|
+
)
|
421
|
+
|
422
|
+
@logger.debug("Failed actions for last bad bulk request!", :actions => actions)
|
423
|
+
|
424
|
+
sleep_interval = sleep_for_interval(sleep_interval)
|
425
|
+
@bulk_request_metrics.increment(:failures)
|
426
|
+
retry unless @stopping.true?
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
def default_index?(index)
|
431
|
+
@index == LogStash::Outputs::ElasticSearch::CommonConfigs::DEFAULT_INDEX_NAME
|
432
|
+
end
|
433
|
+
|
434
|
+
def dlq_enabled?
|
435
|
+
# TODO there should be a better way to query if DLQ is enabled
|
436
|
+
# See more in: https://github.com/elastic/logstash/issues/8064
|
437
|
+
respond_to?(:execution_context) && execution_context.respond_to?(:dlq_writer) &&
|
438
|
+
!execution_context.dlq_writer.inner_writer.is_a?(::LogStash::Util::DummyDeadLetterQueueWriter)
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end end end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'forwardable' # Needed for logstash core SafeURI. We need to patch this in core: https://github.com/elastic/logstash/pull/5978
|
2
|
+
|
3
|
+
module LogStash; module Outputs; class ElasticSearch
|
4
|
+
module CommonConfigs
|
5
|
+
|
6
|
+
DEFAULT_INDEX_NAME = "logstash-%{+YYYY.MM.dd}"
|
7
|
+
DEFAULT_POLICY = "logstash-policy"
|
8
|
+
DEFAULT_ROLLOVER_ALIAS = 'logstash'
|
9
|
+
|
10
|
+
DEFAULT_HOST = ::LogStash::Util::SafeURI.new("//127.0.0.1")
|
11
|
+
|
12
|
+
def self.included(mod)
|
13
|
+
# The index to write events to. This can be dynamic using the `%{foo}` syntax.
|
14
|
+
# The default value will partition your indices by day so you can more easily
|
15
|
+
# delete old data or only search specific date ranges.
|
16
|
+
# Indexes may not contain uppercase characters.
|
17
|
+
# For weekly indexes ISO 8601 format is recommended, eg. logstash-%{+xxxx.ww}.
|
18
|
+
# LS uses Joda to format the index pattern from event timestamp.
|
19
|
+
# Joda formats are defined http://www.joda.org/joda-time/apidocs/org/joda/time/format/DateTimeFormat.html[here].
|
20
|
+
mod.config :index, :validate => :string, :default => DEFAULT_INDEX_NAME
|
21
|
+
|
22
|
+
mod.config :document_type,
|
23
|
+
:validate => :string,
|
24
|
+
:deprecated => "Document types are being deprecated in Elasticsearch 6.0, and removed entirely in 7.0. You should avoid this feature"
|
25
|
+
|
26
|
+
# From Logstash 1.3 onwards, a template is applied to Elasticsearch during
|
27
|
+
# Logstash's startup if one with the name `template_name` does not already exist.
|
28
|
+
# By default, the contents of this template is the default template for
|
29
|
+
# `logstash-%{+YYYY.MM.dd}` which always matches indices based on the pattern
|
30
|
+
# `logstash-*`. Should you require support for other index names, or would like
|
31
|
+
# to change the mappings in the template in general, a custom template can be
|
32
|
+
# specified by setting `template` to the path of a template file.
|
33
|
+
#
|
34
|
+
# Setting `manage_template` to false disables this feature. If you require more
|
35
|
+
# control over template creation, (e.g. creating indices dynamically based on
|
36
|
+
# field names) you should set `manage_template` to false and use the REST
|
37
|
+
# API to apply your templates manually.
|
38
|
+
mod.config :manage_template, :validate => :boolean, :default => true
|
39
|
+
|
40
|
+
# This configuration option defines how the template is named inside Elasticsearch.
|
41
|
+
# Note that if you have used the template management features and subsequently
|
42
|
+
# change this, you will need to prune the old template manually, e.g.
|
43
|
+
#
|
44
|
+
# `curl -XDELETE <http://localhost:9200/_template/OldTemplateName?pretty>`
|
45
|
+
#
|
46
|
+
# where `OldTemplateName` is whatever the former setting was.
|
47
|
+
mod.config :template_name, :validate => :string, :default => "logstash"
|
48
|
+
|
49
|
+
# You can set the path to your own template here, if you so desire.
|
50
|
+
# If not set, the included template will be used.
|
51
|
+
mod.config :template, :validate => :path
|
52
|
+
|
53
|
+
# The template_overwrite option will always overwrite the indicated template
|
54
|
+
# in Elasticsearch with either the one indicated by template or the included one.
|
55
|
+
# This option is set to false by default. If you always want to stay up to date
|
56
|
+
# with the template provided by Logstash, this option could be very useful to you.
|
57
|
+
# Likewise, if you have your own template file managed by puppet, for example, and
|
58
|
+
# you wanted to be able to update it regularly, this option could help there as well.
|
59
|
+
#
|
60
|
+
# Please note that if you are using your own customized version of the Logstash
|
61
|
+
# template (logstash), setting this to true will make Logstash to overwrite
|
62
|
+
# the "logstash" template (i.e. removing all customized settings)
|
63
|
+
mod.config :template_overwrite, :validate => :boolean, :default => false
|
64
|
+
|
65
|
+
# The document ID for the index. Useful for overwriting existing entries in
|
66
|
+
# Elasticsearch with the same ID.
|
67
|
+
mod.config :document_id, :validate => :string
|
68
|
+
|
69
|
+
# The version to use for indexing. Use sprintf syntax like `%{my_version}` to use a field value here.
|
70
|
+
# See https://www.elastic.co/blog/elasticsearch-versioning-support.
|
71
|
+
mod.config :version, :validate => :string
|
72
|
+
|
73
|
+
# The version_type to use for indexing.
|
74
|
+
# See https://www.elastic.co/blog/elasticsearch-versioning-support.
|
75
|
+
# See also https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html#_version_types
|
76
|
+
mod.config :version_type, :validate => ["internal", 'external', "external_gt", "external_gte", "force"]
|
77
|
+
|
78
|
+
# A routing override to be applied to all processed events.
|
79
|
+
# This can be dynamic using the `%{foo}` syntax.
|
80
|
+
mod.config :routing, :validate => :string
|
81
|
+
|
82
|
+
# For child documents, ID of the associated parent.
|
83
|
+
# This can be dynamic using the `%{foo}` syntax.
|
84
|
+
mod.config :parent, :validate => :string, :default => nil
|
85
|
+
|
86
|
+
# For child documents, name of the join field
|
87
|
+
mod.config :join_field, :validate => :string, :default => nil
|
88
|
+
|
89
|
+
# Sets the host(s) of the remote instance. If given an array it will load balance requests across the hosts specified in the `hosts` parameter.
|
90
|
+
# Remember the `http` protocol uses the http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-http.html#modules-http[http] address (eg. 9200, not 9300).
|
91
|
+
# `"127.0.0.1"`
|
92
|
+
# `["127.0.0.1:9200","127.0.0.2:9200"]`
|
93
|
+
# `["http://127.0.0.1"]`
|
94
|
+
# `["https://127.0.0.1:9200"]`
|
95
|
+
# `["https://127.0.0.1:9200/mypath"]` (If using a proxy on a subpath)
|
96
|
+
# It is important to exclude http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-node.html[dedicated master nodes] from the `hosts` list
|
97
|
+
# to prevent LS from sending bulk requests to the master nodes. So this parameter should only reference either data or client nodes in Elasticsearch.
|
98
|
+
#
|
99
|
+
# Any special characters present in the URLs here MUST be URL escaped! This means `#` should be put in as `%23` for instance.
|
100
|
+
mod.config :hosts, :validate => :uri, :default => [ DEFAULT_HOST ], :list => true
|
101
|
+
|
102
|
+
# Cloud ID, from the Elastic Cloud web console. If set `hosts` should not be used.
|
103
|
+
#
|
104
|
+
# For mode details, check out the https://www.elastic.co/guide/en/logstash/current/connecting-to-cloud.html#_cloud_id[cloud documentation]
|
105
|
+
mod.config :cloud_id, :validate => :string
|
106
|
+
|
107
|
+
# Set upsert content for update mode.s
|
108
|
+
# Create a new document with this parameter as json string if `document_id` doesn't exists
|
109
|
+
mod.config :upsert, :validate => :string, :default => ""
|
110
|
+
|
111
|
+
# Enable `doc_as_upsert` for update mode.
|
112
|
+
# Create a new document with source if `document_id` doesn't exist in Elasticsearch
|
113
|
+
mod.config :doc_as_upsert, :validate => :boolean, :default => false
|
114
|
+
|
115
|
+
# Set script name for scripted update mode
|
116
|
+
mod.config :script, :validate => :string, :default => ""
|
117
|
+
|
118
|
+
# Define the type of script referenced by "script" variable
|
119
|
+
# inline : "script" contains inline script
|
120
|
+
# indexed : "script" contains the name of script directly indexed in elasticsearch
|
121
|
+
# file : "script" contains the name of script stored in elasticseach's config directory
|
122
|
+
mod.config :script_type, :validate => ["inline", 'indexed', "file"], :default => ["inline"]
|
123
|
+
|
124
|
+
# Set the language of the used script. If not set, this defaults to painless in ES 5.0
|
125
|
+
mod.config :script_lang, :validate => :string, :default => "painless"
|
126
|
+
|
127
|
+
# Set variable name passed to script (scripted update)
|
128
|
+
mod.config :script_var_name, :validate => :string, :default => "event"
|
129
|
+
|
130
|
+
# if enabled, script is in charge of creating non-existent document (scripted update)
|
131
|
+
mod.config :scripted_upsert, :validate => :boolean, :default => false
|
132
|
+
|
133
|
+
# Set initial interval in seconds between bulk retries. Doubled on each retry up to `retry_max_interval`
|
134
|
+
mod.config :retry_initial_interval, :validate => :number, :default => 2
|
135
|
+
|
136
|
+
# Set max interval in seconds between bulk retries.
|
137
|
+
mod.config :retry_max_interval, :validate => :number, :default => 64
|
138
|
+
|
139
|
+
# The number of times Elasticsearch should internally retry an update/upserted document
|
140
|
+
# See the https://www.elastic.co/guide/en/elasticsearch/guide/current/partial-updates.html[partial updates]
|
141
|
+
# for more info
|
142
|
+
mod.config :retry_on_conflict, :validate => :number, :default => 1
|
143
|
+
|
144
|
+
# Set which ingest pipeline you wish to execute for an event. You can also use event dependent configuration
|
145
|
+
# here like `pipeline => "%{INGEST_PIPELINE}"`
|
146
|
+
mod.config :pipeline, :validate => :string, :default => nil
|
147
|
+
|
148
|
+
|
149
|
+
# -----
|
150
|
+
# ILM configurations (beta)
|
151
|
+
# -----
|
152
|
+
# Flag for enabling Index Lifecycle Management integration.
|
153
|
+
mod.config :ilm_enabled, :validate => [true, false, 'true', 'false', 'auto'], :default => 'auto'
|
154
|
+
|
155
|
+
# Rollover alias used for indexing data. If rollover alias doesn't exist, Logstash will create it and map it to the relevant index
|
156
|
+
mod.config :ilm_rollover_alias, :validate => :string, :default => DEFAULT_ROLLOVER_ALIAS
|
157
|
+
|
158
|
+
# appends “{now/d}-000001” by default for new index creation, subsequent rollover indices will increment based on this pattern i.e. “000002”
|
159
|
+
# {now/d} is date math, and will insert the appropriate value automatically.
|
160
|
+
mod.config :ilm_pattern, :validate => :string, :default => '{now/d}-000001'
|
161
|
+
|
162
|
+
# ILM policy to use, if undefined the default policy will be used.
|
163
|
+
mod.config :ilm_policy, :validate => :string, :default => DEFAULT_POLICY
|
164
|
+
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end end end
|