logstash-output-elasticsearch-test 10.3.0-x86_64-linux
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 +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
|