logstash-output-elasticsearch-test 11.16.0-x86_64-linux

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +649 -0
  3. data/CONTRIBUTORS +34 -0
  4. data/Gemfile +16 -0
  5. data/LICENSE +202 -0
  6. data/NOTICE.TXT +5 -0
  7. data/README.md +106 -0
  8. data/docs/index.asciidoc +1369 -0
  9. data/lib/logstash/outputs/elasticsearch/data_stream_support.rb +282 -0
  10. data/lib/logstash/outputs/elasticsearch/default-ilm-policy.json +14 -0
  11. data/lib/logstash/outputs/elasticsearch/http_client/manticore_adapter.rb +155 -0
  12. data/lib/logstash/outputs/elasticsearch/http_client/pool.rb +534 -0
  13. data/lib/logstash/outputs/elasticsearch/http_client.rb +497 -0
  14. data/lib/logstash/outputs/elasticsearch/http_client_builder.rb +201 -0
  15. data/lib/logstash/outputs/elasticsearch/ilm.rb +92 -0
  16. data/lib/logstash/outputs/elasticsearch/license_checker.rb +52 -0
  17. data/lib/logstash/outputs/elasticsearch/template_manager.rb +131 -0
  18. data/lib/logstash/outputs/elasticsearch/templates/ecs-disabled/elasticsearch-6x.json +45 -0
  19. data/lib/logstash/outputs/elasticsearch/templates/ecs-disabled/elasticsearch-7x.json +44 -0
  20. data/lib/logstash/outputs/elasticsearch/templates/ecs-disabled/elasticsearch-8x.json +50 -0
  21. data/lib/logstash/outputs/elasticsearch.rb +699 -0
  22. data/lib/logstash/plugin_mixins/elasticsearch/api_configs.rb +237 -0
  23. data/lib/logstash/plugin_mixins/elasticsearch/common.rb +409 -0
  24. data/lib/logstash/plugin_mixins/elasticsearch/noop_license_checker.rb +9 -0
  25. data/logstash-output-elasticsearch.gemspec +40 -0
  26. data/spec/es_spec_helper.rb +225 -0
  27. data/spec/fixtures/_nodes/6x.json +81 -0
  28. data/spec/fixtures/_nodes/7x.json +92 -0
  29. data/spec/fixtures/htpasswd +2 -0
  30. data/spec/fixtures/license_check/active.json +16 -0
  31. data/spec/fixtures/license_check/inactive.json +5 -0
  32. data/spec/fixtures/nginx_reverse_proxy.conf +22 -0
  33. data/spec/fixtures/scripts/painless/scripted_update.painless +2 -0
  34. data/spec/fixtures/scripts/painless/scripted_update_nested.painless +1 -0
  35. data/spec/fixtures/scripts/painless/scripted_upsert.painless +1 -0
  36. data/spec/fixtures/template-with-policy-es6x.json +48 -0
  37. data/spec/fixtures/template-with-policy-es7x.json +45 -0
  38. data/spec/fixtures/template-with-policy-es8x.json +50 -0
  39. data/spec/fixtures/test_certs/ca.crt +29 -0
  40. data/spec/fixtures/test_certs/ca.der.sha256 +1 -0
  41. data/spec/fixtures/test_certs/ca.key +51 -0
  42. data/spec/fixtures/test_certs/renew.sh +13 -0
  43. data/spec/fixtures/test_certs/test.crt +30 -0
  44. data/spec/fixtures/test_certs/test.der.sha256 +1 -0
  45. data/spec/fixtures/test_certs/test.key +51 -0
  46. data/spec/fixtures/test_certs/test.p12 +0 -0
  47. data/spec/fixtures/test_certs/test_invalid.crt +36 -0
  48. data/spec/fixtures/test_certs/test_invalid.key +51 -0
  49. data/spec/fixtures/test_certs/test_invalid.p12 +0 -0
  50. data/spec/fixtures/test_certs/test_self_signed.crt +32 -0
  51. data/spec/fixtures/test_certs/test_self_signed.key +54 -0
  52. data/spec/fixtures/test_certs/test_self_signed.p12 +0 -0
  53. data/spec/integration/outputs/compressed_indexing_spec.rb +70 -0
  54. data/spec/integration/outputs/create_spec.rb +67 -0
  55. data/spec/integration/outputs/data_stream_spec.rb +68 -0
  56. data/spec/integration/outputs/delete_spec.rb +63 -0
  57. data/spec/integration/outputs/ilm_spec.rb +534 -0
  58. data/spec/integration/outputs/index_spec.rb +421 -0
  59. data/spec/integration/outputs/index_version_spec.rb +98 -0
  60. data/spec/integration/outputs/ingest_pipeline_spec.rb +75 -0
  61. data/spec/integration/outputs/metrics_spec.rb +66 -0
  62. data/spec/integration/outputs/no_es_on_startup_spec.rb +78 -0
  63. data/spec/integration/outputs/painless_update_spec.rb +99 -0
  64. data/spec/integration/outputs/parent_spec.rb +94 -0
  65. data/spec/integration/outputs/retry_spec.rb +182 -0
  66. data/spec/integration/outputs/routing_spec.rb +61 -0
  67. data/spec/integration/outputs/sniffer_spec.rb +94 -0
  68. data/spec/integration/outputs/templates_spec.rb +133 -0
  69. data/spec/integration/outputs/unsupported_actions_spec.rb +75 -0
  70. data/spec/integration/outputs/update_spec.rb +114 -0
  71. data/spec/spec_helper.rb +10 -0
  72. data/spec/support/elasticsearch/api/actions/delete_ilm_policy.rb +19 -0
  73. data/spec/support/elasticsearch/api/actions/get_alias.rb +18 -0
  74. data/spec/support/elasticsearch/api/actions/get_ilm_policy.rb +18 -0
  75. data/spec/support/elasticsearch/api/actions/put_alias.rb +24 -0
  76. data/spec/support/elasticsearch/api/actions/put_ilm_policy.rb +25 -0
  77. data/spec/unit/http_client_builder_spec.rb +185 -0
  78. data/spec/unit/outputs/elasticsearch/data_stream_support_spec.rb +612 -0
  79. data/spec/unit/outputs/elasticsearch/http_client/manticore_adapter_spec.rb +151 -0
  80. data/spec/unit/outputs/elasticsearch/http_client/pool_spec.rb +501 -0
  81. data/spec/unit/outputs/elasticsearch/http_client_spec.rb +339 -0
  82. data/spec/unit/outputs/elasticsearch/template_manager_spec.rb +189 -0
  83. data/spec/unit/outputs/elasticsearch_proxy_spec.rb +103 -0
  84. data/spec/unit/outputs/elasticsearch_spec.rb +1573 -0
  85. data/spec/unit/outputs/elasticsearch_ssl_spec.rb +197 -0
  86. data/spec/unit/outputs/error_whitelist_spec.rb +56 -0
  87. data/spec/unit/outputs/license_check_spec.rb +57 -0
  88. metadata +423 -0
@@ -0,0 +1,699 @@
1
+ # encoding: utf-8
2
+ require "logstash/namespace"
3
+ require "logstash/environment"
4
+ require "logstash/outputs/base"
5
+ require "logstash/json"
6
+ require "concurrent/atomic/atomic_boolean"
7
+ require "stud/interval"
8
+ require "socket" # for Socket.gethostname
9
+ require "thread" # for safe queueing
10
+ require "uri" # for escaping user input
11
+ require "forwardable"
12
+ require "set"
13
+
14
+ # .Compatibility Note
15
+ # [NOTE]
16
+ # ================================================================================
17
+ # Starting with Elasticsearch 5.3, there's an {ref}modules-http.html[HTTP setting]
18
+ # called `http.content_type.required`. If this option is set to `true`, and you
19
+ # are using Logstash 2.4 through 5.2, you need to update the Elasticsearch output
20
+ # plugin to version 6.2.5 or higher.
21
+ #
22
+ # ================================================================================
23
+ #
24
+ # This plugin is the recommended method of storing logs in Elasticsearch.
25
+ # If you plan on using the Kibana web interface, you'll want to use this output.
26
+ #
27
+ # This output only speaks the HTTP protocol. HTTP is the preferred protocol for interacting with Elasticsearch as of Logstash 2.0.
28
+ # We strongly encourage the use of HTTP over the node protocol for a number of reasons. HTTP is only marginally slower,
29
+ # yet far easier to administer and work with. When using the HTTP protocol one may upgrade Elasticsearch versions without having
30
+ # to upgrade Logstash in lock-step.
31
+ #
32
+ # You can learn more about Elasticsearch at <https://www.elastic.co/products/elasticsearch>
33
+ #
34
+ # ==== Template management for Elasticsearch 5.x
35
+ # Index template for this version (Logstash 5.0) has been changed to reflect Elasticsearch's mapping changes in version 5.0.
36
+ # Most importantly, the subfield for string multi-fields has changed from `.raw` to `.keyword` to match ES default
37
+ # behavior.
38
+ #
39
+ # ** Users installing ES 5.x and LS 5.x **
40
+ # This change will not affect you and you will continue to use the ES defaults.
41
+ #
42
+ # ** Users upgrading from LS 2.x to LS 5.x with ES 5.x **
43
+ # LS will not force upgrade the template, if `logstash` template already exists. This means you will still use
44
+ # `.raw` for sub-fields coming from 2.x. If you choose to use the new template, you will have to reindex your data after
45
+ # the new template is installed.
46
+ #
47
+ # ==== Retry Policy
48
+ #
49
+ # The retry policy has changed significantly in the 2.2.0 release.
50
+ # This plugin uses the Elasticsearch bulk API to optimize its imports into Elasticsearch. These requests may experience
51
+ # either partial or total failures.
52
+ #
53
+ # The following errors are retried infinitely:
54
+ #
55
+ # - Network errors (inability to connect)
56
+ # - 429 (Too many requests) and
57
+ # - 503 (Service unavailable) errors
58
+ #
59
+ # NOTE: 409 exceptions are no longer retried. Please set a higher `retry_on_conflict` value if you experience 409 exceptions.
60
+ # It is more performant for Elasticsearch to retry these exceptions than this plugin.
61
+ #
62
+ # ==== Batch Sizes ====
63
+ # This plugin attempts to send batches of events as a single request. However, if
64
+ # a request exceeds 20MB we will break it up until multiple batch requests. If a single document exceeds 20MB it will be sent as a single request.
65
+ #
66
+ # ==== DNS Caching
67
+ #
68
+ # This plugin uses the JVM to lookup DNS entries and is subject to the value of https://docs.oracle.com/javase/7/docs/technotes/guides/net/properties.html[networkaddress.cache.ttl],
69
+ # a global setting for the JVM.
70
+ #
71
+ # As an example, to set your DNS TTL to 1 second you would set
72
+ # the `LS_JAVA_OPTS` environment variable to `-Dnetworkaddress.cache.ttl=1`.
73
+ #
74
+ # Keep in mind that a connection with keepalive enabled will
75
+ # not reevaluate its DNS value while the keepalive is in effect.
76
+ #
77
+ # ==== HTTP Compression
78
+ #
79
+ # This plugin supports request and response compression. Response compression is enabled by default and
80
+ # for Elasticsearch versions 5.0 and later, the user doesn't have to set any configs in Elasticsearch for
81
+ # it to send back compressed response. For versions before 5.0, `http.compression` must be set to `true` in
82
+ # Elasticsearch[https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-http.html#modules-http] to take advantage of response compression when using this plugin
83
+ #
84
+ # For requests compression, regardless of the Elasticsearch version, users have to enable `http_compression`
85
+ # setting in their Logstash config file.
86
+ #
87
+ class LogStash::Outputs::ElasticSearch < LogStash::Outputs::Base
88
+ declare_threadsafe!
89
+
90
+ require "logstash/outputs/elasticsearch/license_checker"
91
+ require "logstash/outputs/elasticsearch/http_client"
92
+ require "logstash/outputs/elasticsearch/http_client_builder"
93
+ require "logstash/plugin_mixins/elasticsearch/api_configs"
94
+ require "logstash/plugin_mixins/elasticsearch/common"
95
+ require "logstash/outputs/elasticsearch/ilm"
96
+ require "logstash/outputs/elasticsearch/data_stream_support"
97
+ require 'logstash/plugin_mixins/ecs_compatibility_support'
98
+ require 'logstash/plugin_mixins/deprecation_logger_support'
99
+ require 'logstash/plugin_mixins/normalize_config_support'
100
+
101
+ # Protocol agnostic methods
102
+ include(LogStash::PluginMixins::ElasticSearch::Common)
103
+
104
+ # Config normalization helpers
105
+ include(LogStash::PluginMixins::NormalizeConfigSupport)
106
+
107
+ # Methods for ILM support
108
+ include(LogStash::Outputs::ElasticSearch::Ilm)
109
+
110
+ # ecs_compatibility option, provided by Logstash core or the support adapter.
111
+ include(LogStash::PluginMixins::ECSCompatibilitySupport(:disabled, :v1, :v8))
112
+
113
+ # deprecation logger adapter for older Logstashes
114
+ include(LogStash::PluginMixins::DeprecationLoggerSupport)
115
+
116
+ # Generic/API config options that any document indexer output needs
117
+ include(LogStash::PluginMixins::ElasticSearch::APIConfigs)
118
+
119
+ # DS support
120
+ include(LogStash::Outputs::ElasticSearch::DataStreamSupport)
121
+
122
+ DEFAULT_POLICY = "logstash-policy"
123
+
124
+ config_name "elasticsearch"
125
+
126
+ # The Elasticsearch action to perform. Valid actions are:
127
+ #
128
+ # - index: indexes a document (an event from Logstash).
129
+ # - delete: deletes a document by id (An id is required for this action)
130
+ # - create: indexes a document, fails if a document by that id already exists in the index.
131
+ # - update: updates a document by id. Update has a special case where you can upsert -- update a
132
+ # document if not already present. See the `upsert` option. NOTE: This does not work and is not supported
133
+ # in Elasticsearch 1.x. Please upgrade to ES 2.x or greater to use this feature with Logstash!
134
+ # - A sprintf style string to change the action based on the content of the event. The value `%{[foo]}`
135
+ # would use the foo field for the action
136
+ #
137
+ # For more details on actions, check out the http://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html[Elasticsearch bulk API documentation]
138
+ config :action, :validate => :string # :default => "index" unless data_stream
139
+
140
+ # The index to write events to. This can be dynamic using the `%{foo}` syntax.
141
+ # The default value will partition your indices by day so you can more easily
142
+ # delete old data or only search specific date ranges.
143
+ # Indexes may not contain uppercase characters.
144
+ # For weekly indexes ISO 8601 format is recommended, eg. logstash-%{+xxxx.ww}.
145
+ # LS uses Joda to format the index pattern from event timestamp.
146
+ # Joda formats are defined http://www.joda.org/joda-time/apidocs/org/joda/time/format/DateTimeFormat.html[here].
147
+ config :index, :validate => :string
148
+
149
+ config :document_type,
150
+ :validate => :string,
151
+ :deprecated => "Document types are being deprecated in Elasticsearch 6.0, and removed entirely in 7.0. You should avoid this feature"
152
+
153
+ # From Logstash 1.3 onwards, a template is applied to Elasticsearch during
154
+ # Logstash's startup if one with the name `template_name` does not already exist.
155
+ # By default, the contents of this template is the default template for
156
+ # `logstash-%{+YYYY.MM.dd}` which always matches indices based on the pattern
157
+ # `logstash-*`. Should you require support for other index names, or would like
158
+ # to change the mappings in the template in general, a custom template can be
159
+ # specified by setting `template` to the path of a template file.
160
+ #
161
+ # Setting `manage_template` to false disables this feature. If you require more
162
+ # control over template creation, (e.g. creating indices dynamically based on
163
+ # field names) you should set `manage_template` to false and use the REST
164
+ # API to apply your templates manually.
165
+ #
166
+ # Default value is `true` unless data streams is enabled
167
+ config :manage_template, :validate => :boolean, :default => true
168
+
169
+ # This configuration option defines how the template is named inside Elasticsearch.
170
+ # Note that if you have used the template management features and subsequently
171
+ # change this, you will need to prune the old template manually, e.g.
172
+ #
173
+ # `curl -XDELETE <http://localhost:9200/_template/OldTemplateName?pretty>`
174
+ #
175
+ # where `OldTemplateName` is whatever the former setting was.
176
+ config :template_name, :validate => :string
177
+
178
+ # You can set the path to your own template here, if you so desire.
179
+ # If not set, the included template will be used.
180
+ config :template, :validate => :path
181
+
182
+ # The template_overwrite option will always overwrite the indicated template
183
+ # in Elasticsearch with either the one indicated by template or the included one.
184
+ # This option is set to false by default. If you always want to stay up to date
185
+ # with the template provided by Logstash, this option could be very useful to you.
186
+ # Likewise, if you have your own template file managed by puppet, for example, and
187
+ # you wanted to be able to update it regularly, this option could help there as well.
188
+ #
189
+ # Please note that if you are using your own customized version of the Logstash
190
+ # template (logstash), setting this to true will make Logstash to overwrite
191
+ # the "logstash" template (i.e. removing all customized settings)
192
+ config :template_overwrite, :validate => :boolean, :default => false
193
+
194
+ # Flag for enabling legacy template api for Elasticsearch 8
195
+ # Default auto will use index template api for Elasticsearch 8 and use legacy api for 7
196
+ # Set to legacy to use legacy template api
197
+ config :template_api, :validate => ['auto', 'legacy', 'composable'], :default => 'auto'
198
+
199
+ # The version to use for indexing. Use sprintf syntax like `%{my_version}` to use a field value here.
200
+ # See https://www.elastic.co/blog/elasticsearch-versioning-support.
201
+ config :version, :validate => :string
202
+
203
+ # The version_type to use for indexing.
204
+ # See https://www.elastic.co/blog/elasticsearch-versioning-support.
205
+ # See also https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html#_version_types
206
+ config :version_type, :validate => ["internal", 'external', "external_gt", "external_gte", "force"]
207
+
208
+ # A routing override to be applied to all processed events.
209
+ # This can be dynamic using the `%{foo}` syntax.
210
+ config :routing, :validate => :string
211
+
212
+ # For child documents, ID of the associated parent.
213
+ # This can be dynamic using the `%{foo}` syntax.
214
+ config :parent, :validate => :string, :default => nil
215
+
216
+ # For child documents, name of the join field
217
+ config :join_field, :validate => :string, :default => nil
218
+
219
+ # Set upsert content for update mode.s
220
+ # Create a new document with this parameter as json string if `document_id` doesn't exists
221
+ config :upsert, :validate => :string, :default => ""
222
+
223
+ # Enable `doc_as_upsert` for update mode.
224
+ # Create a new document with source if `document_id` doesn't exist in Elasticsearch
225
+ config :doc_as_upsert, :validate => :boolean, :default => false
226
+
227
+ # Set script name for scripted update mode
228
+ config :script, :validate => :string, :default => ""
229
+
230
+ # Define the type of script referenced by "script" variable
231
+ # inline : "script" contains inline script
232
+ # indexed : "script" contains the name of script directly indexed in elasticsearch
233
+ # file : "script" contains the name of script stored in elasticseach's config directory
234
+ config :script_type, :validate => ["inline", 'indexed', "file"], :default => ["inline"]
235
+
236
+ # Set the language of the used script. If not set, this defaults to painless in ES 5.0
237
+ config :script_lang, :validate => :string, :default => "painless"
238
+
239
+ # Set variable name passed to script (scripted update)
240
+ config :script_var_name, :validate => :string, :default => "event"
241
+
242
+ # if enabled, script is in charge of creating non-existent document (scripted update)
243
+ config :scripted_upsert, :validate => :boolean, :default => false
244
+
245
+ # The number of times Elasticsearch should internally retry an update/upserted document
246
+ # See the https://www.elastic.co/guide/en/elasticsearch/guide/current/partial-updates.html[partial updates]
247
+ # for more info
248
+ config :retry_on_conflict, :validate => :number, :default => 1
249
+
250
+ # Set which ingest pipeline you wish to execute for an event. You can also use event dependent configuration
251
+ # here like `pipeline => "%{INGEST_PIPELINE}"`
252
+ config :pipeline, :validate => :string, :default => nil
253
+
254
+ # -----
255
+ # ILM configurations (beta)
256
+ # -----
257
+ # Flag for enabling Index Lifecycle Management integration.
258
+ config :ilm_enabled, :validate => [true, false, 'true', 'false', 'auto'], :default => 'auto'
259
+
260
+ # Rollover alias used for indexing data. If rollover alias doesn't exist, Logstash will create it and map it to the relevant index
261
+ config :ilm_rollover_alias, :validate => :string
262
+
263
+ # appends “{now/d}-000001” by default for new index creation, subsequent rollover indices will increment based on this pattern i.e. “000002”
264
+ # {now/d} is date math, and will insert the appropriate value automatically.
265
+ config :ilm_pattern, :validate => :string, :default => '{now/d}-000001'
266
+
267
+ # ILM policy to use, if undefined the default policy will be used.
268
+ config :ilm_policy, :validate => :string, :default => DEFAULT_POLICY
269
+
270
+ attr_reader :client
271
+ attr_reader :default_index
272
+ attr_reader :default_ilm_rollover_alias
273
+ attr_reader :default_template_name
274
+
275
+ def initialize(*params)
276
+ super
277
+ setup_ecs_compatibility_related_defaults
278
+ setup_ssl_params!
279
+ end
280
+
281
+ def register
282
+ if !failure_type_logging_whitelist.empty?
283
+ log_message = "'failure_type_logging_whitelist' is deprecated and in a future version of Elasticsearch " +
284
+ "output plugin will be removed, please use 'silence_errors_in_log' instead."
285
+ @deprecation_logger.deprecated log_message
286
+ @logger.warn log_message
287
+ @silence_errors_in_log = silence_errors_in_log | failure_type_logging_whitelist
288
+ end
289
+
290
+ @after_successful_connection_done = Concurrent::AtomicBoolean.new(false)
291
+ @stopping = Concurrent::AtomicBoolean.new(false)
292
+
293
+ check_action_validity
294
+
295
+ @logger.info("1111111-----New Elasticsearch output", :class => self.class.name, :hosts => @hosts.map(&:sanitized).map(&:to_s))
296
+
297
+ # the license_checking behaviour in the Pool class is externalized in the LogStash::ElasticSearchOutputLicenseChecker
298
+ # class defined in license_check.rb. This license checking is specific to the elasticsearch output here and passed
299
+ # to build_client down to the Pool class.
300
+ @client = build_client(LicenseChecker.new(@logger))
301
+
302
+ # Avoids race conditions in the @data_stream_config initialization (invoking check_data_stream_config! twice).
303
+ # It's being concurrently invoked by this register method and by the finish_register on the @after_successful_connection_thread
304
+ data_stream_enabled = data_stream_config?
305
+
306
+ setup_template_manager_defaults(data_stream_enabled)
307
+ # To support BWC, we check if DLQ exists in core (< 5.4). If it doesn't, we use nil to resort to previous behavior.
308
+ @dlq_writer = dlq_enabled? ? execution_context.dlq_writer : nil
309
+
310
+ @dlq_codes = DOC_DLQ_CODES.to_set
311
+
312
+ if dlq_enabled?
313
+ check_dlq_custom_codes
314
+ @dlq_codes.merge(dlq_custom_codes)
315
+ else
316
+ raise LogStash::ConfigurationError, "DLQ feature (dlq_custom_codes) is configured while DLQ is not enabled" unless dlq_custom_codes.empty?
317
+ end
318
+
319
+ setup_mapper_and_target(data_stream_enabled)
320
+
321
+ @bulk_request_metrics = metric.namespace(:bulk_requests)
322
+ @document_level_metrics = metric.namespace(:documents)
323
+
324
+ @after_successful_connection_thread = after_successful_connection do
325
+ begin
326
+ finish_register
327
+ true # thread.value
328
+ rescue => e
329
+ # we do not want to halt the thread with an exception as that has consequences for LS
330
+ e # thread.value
331
+ ensure
332
+ @after_successful_connection_done.make_true
333
+ end
334
+ end
335
+
336
+ end
337
+
338
+ def setup_mapper_and_target(data_stream_enabled)
339
+ if data_stream_enabled
340
+ @event_mapper = -> (e) { data_stream_event_action_tuple(e) }
341
+ @event_target = -> (e) { data_stream_name(e) }
342
+ @index = "#{data_stream_type}-#{data_stream_dataset}-#{data_stream_namespace}".freeze # default name
343
+ else
344
+ @event_mapper = -> (e) { event_action_tuple(e) }
345
+ @event_target = -> (e) { e.sprintf(@index) }
346
+ end
347
+ end
348
+
349
+ # @override post-register when ES connection established
350
+ def finish_register
351
+ assert_es_version_supports_data_streams if data_stream_config?
352
+ discover_cluster_uuid
353
+ install_template
354
+ setup_ilm if ilm_in_use?
355
+ super
356
+ end
357
+
358
+ # @override to handle proxy => '' as if none was set
359
+ def config_init(params)
360
+ proxy = params['proxy']
361
+ if proxy.is_a?(String)
362
+ # environment variables references aren't yet resolved
363
+ proxy = deep_replace(proxy)
364
+ if proxy.empty?
365
+ params.delete('proxy')
366
+ @proxy = ''
367
+ else
368
+ params['proxy'] = proxy # do not do resolving again
369
+ end
370
+ end
371
+ super(params)
372
+ end
373
+
374
+ # Receive an array of events and immediately attempt to index them (no buffering)
375
+ def multi_receive(events)
376
+ wait_for_successful_connection if @after_successful_connection_done
377
+ events_mapped = safe_interpolation_map_events(events)
378
+ retrying_submit(events_mapped.successful_events)
379
+ unless events_mapped.event_mapping_errors.empty?
380
+ handle_event_mapping_errors(events_mapped.event_mapping_errors)
381
+ end
382
+ end
383
+
384
+ # @param: Arrays of FailedEventMapping
385
+ private
386
+ def handle_event_mapping_errors(event_mapping_errors)
387
+ # if DQL is enabled, log the events to provide issue insights to users.
388
+ if @dlq_writer
389
+ @logger.warn("Events could not be indexed and routing to DLQ, count: #{event_mapping_errors.size}")
390
+ end
391
+
392
+ event_mapping_errors.each do |event_mapping_error|
393
+ detailed_message = "#{event_mapping_error.message}; event: `#{event_mapping_error.event.to_hash_with_metadata}`"
394
+ @dlq_writer ? @dlq_writer.write(event_mapping_error.event, detailed_message) : @logger.warn(detailed_message)
395
+ end
396
+ @document_level_metrics.increment(:non_retryable_failures, event_mapping_errors.size)
397
+ end
398
+
399
+ MapEventsResult = Struct.new(:successful_events, :event_mapping_errors)
400
+ FailedEventMapping = Struct.new(:event, :message)
401
+
402
+ private
403
+ def safe_interpolation_map_events(events)
404
+ successful_events = [] # list of LogStash::Outputs::ElasticSearch::EventActionTuple
405
+ event_mapping_errors = [] # list of FailedEventMapping
406
+ events.each do |event|
407
+ begin
408
+ successful_events << @event_mapper.call(event)
409
+ rescue EventMappingError => ie
410
+ event_mapping_errors << FailedEventMapping.new(event, ie.message)
411
+ end
412
+ end
413
+ MapEventsResult.new(successful_events, event_mapping_errors)
414
+ end
415
+
416
+ public
417
+ def map_events(events)
418
+ safe_interpolation_map_events(events).successful_events
419
+ end
420
+
421
+ def wait_for_successful_connection
422
+ after_successful_connection_done = @after_successful_connection_done
423
+ return unless after_successful_connection_done
424
+ stoppable_sleep 1 until (after_successful_connection_done.true? || pipeline_shutdown_requested?)
425
+
426
+ if pipeline_shutdown_requested? && !after_successful_connection_done.true?
427
+ logger.info "Aborting the batch due to shutdown request while waiting for connections to become live"
428
+ abort_batch_if_available!
429
+ end
430
+
431
+ status = @after_successful_connection_thread && @after_successful_connection_thread.value
432
+ if status.is_a?(Exception) # check if thread 'halted' with an error
433
+ # keep logging that something isn't right (from every #multi_receive)
434
+ @logger.error "Elasticsearch setup did not complete normally, please review previously logged errors",
435
+ message: status.message, exception: status.class
436
+ else
437
+ @after_successful_connection_done = nil # do not execute __method__ again if all went well
438
+ end
439
+ end
440
+ private :wait_for_successful_connection
441
+
442
+ def close
443
+ @stopping.make_true if @stopping
444
+ stop_after_successful_connection_thread
445
+ @client.close if @client
446
+ end
447
+
448
+ private
449
+
450
+ def stop_after_successful_connection_thread
451
+ @after_successful_connection_thread.join unless @after_successful_connection_thread.nil?
452
+ end
453
+
454
+ # Convert the event into a 3-tuple of action, params and event hash
455
+ def event_action_tuple(event)
456
+ params = common_event_params(event)
457
+ params[:_type] = get_event_type(event) if use_event_type?(nil)
458
+
459
+ if @parent
460
+ if @join_field
461
+ join_value = event.get(@join_field)
462
+ parent_value = event.sprintf(@parent)
463
+ event.set(@join_field, { "name" => join_value, "parent" => parent_value })
464
+ params[routing_field_name] = event.sprintf(@parent)
465
+ else
466
+ params[:parent] = event.sprintf(@parent)
467
+ end
468
+ end
469
+
470
+ action = event.sprintf(@action || 'index')
471
+ raise UnsupportedActionError, action unless VALID_HTTP_ACTIONS.include?(action)
472
+
473
+ if action == 'update'
474
+ params[:_upsert] = LogStash::Json.load(event.sprintf(@upsert)) if @upsert != ""
475
+ params[:_script] = event.sprintf(@script) if @script != ""
476
+ params[retry_on_conflict_action_name] = @retry_on_conflict
477
+ end
478
+
479
+ params[:version] = event.sprintf(@version) if @version
480
+ params[:version_type] = event.sprintf(@version_type) if @version_type
481
+
482
+ EventActionTuple.new(action, params, event)
483
+ end
484
+
485
+ class EventActionTuple < Array # TODO: acting as an array for compatibility
486
+
487
+ def initialize(action, params, event, event_data = nil)
488
+ super(3)
489
+ self[0] = action
490
+ self[1] = params
491
+ self[2] = event_data || event.to_hash
492
+ @event = event
493
+ end
494
+
495
+ attr_reader :event
496
+
497
+ end
498
+
499
+ class EventMappingError < ArgumentError
500
+ def initialize(msg = nil)
501
+ super
502
+ end
503
+ end
504
+
505
+ class IndexInterpolationError < EventMappingError
506
+ def initialize(bad_formatted_index)
507
+ super("Badly formatted index, after interpolation still contains placeholder: [#{bad_formatted_index}]")
508
+ end
509
+ end
510
+
511
+ class UnsupportedActionError < EventMappingError
512
+ def initialize(bad_action)
513
+ super("Elasticsearch doesn't support [#{bad_action}] action")
514
+ end
515
+ end
516
+
517
+ # @return Hash (initial) parameters for given event
518
+ # @private shared event params factory between index and data_stream mode
519
+ def common_event_params(event)
520
+ sprintf_index = @event_target.call(event)
521
+ raise IndexInterpolationError, sprintf_index if sprintf_index.match(/%{.*?}/) && dlq_on_failed_indexname_interpolation
522
+ params = {
523
+ :_id => @document_id ? event.sprintf(@document_id) : nil,
524
+ :_index => sprintf_index,
525
+ routing_field_name => @routing ? event.sprintf(@routing) : nil
526
+ }
527
+
528
+ target_pipeline = resolve_pipeline(event)
529
+ # convention: empty string equates to not using a pipeline
530
+ # this is useful when using a field reference in the pipeline setting, e.g.
531
+ # elasticsearch {
532
+ # pipeline => "%{[@metadata][pipeline]}"
533
+ # }
534
+ params[:pipeline] = target_pipeline unless (target_pipeline.nil? || target_pipeline.empty?)
535
+
536
+ params
537
+ end
538
+
539
+ def resolve_pipeline(event)
540
+ pipeline_template = @pipeline || event.get("[@metadata][target_ingest_pipeline]")&.to_s
541
+ pipeline_template && event.sprintf(pipeline_template)
542
+ end
543
+
544
+ @@plugins = Gem::Specification.find_all{|spec| spec.name =~ /logstash-output-elasticsearch-/ }
545
+
546
+ @@plugins.each do |plugin|
547
+ name = plugin.name.split('-')[-1]
548
+ require "logstash/outputs/elasticsearch/#{name}"
549
+ end
550
+
551
+ def retry_on_conflict_action_name
552
+ maximum_seen_major_version >= 7 ? :retry_on_conflict : :_retry_on_conflict
553
+ end
554
+
555
+ def routing_field_name
556
+ :routing
557
+ end
558
+
559
+ # Determine the correct value for the 'type' field for the given event
560
+ DEFAULT_EVENT_TYPE_ES6 = "doc".freeze
561
+ DEFAULT_EVENT_TYPE_ES7 = "_doc".freeze
562
+
563
+ def get_event_type(event)
564
+ # Set the 'type' value for the index.
565
+ type = if @document_type
566
+ event.sprintf(@document_type)
567
+ else
568
+ major_version = maximum_seen_major_version
569
+ if major_version == 6
570
+ DEFAULT_EVENT_TYPE_ES6
571
+ elsif major_version == 7
572
+ DEFAULT_EVENT_TYPE_ES7
573
+ else
574
+ nil
575
+ end
576
+ end
577
+
578
+ type.to_s
579
+ end
580
+
581
+ ##
582
+ # WARNING: This method is overridden in a subclass in Logstash Core 7.7-7.8's monitoring,
583
+ # where a `client` argument is both required and ignored. In later versions of
584
+ # Logstash Core it is optional and ignored, but to make it optional here would
585
+ # allow us to accidentally break compatibility with Logstashes where it was required.
586
+ # @param noop_required_client [nil]: required `nil` for legacy reasons.
587
+ # @return [Boolean]
588
+ def use_event_type?(noop_required_client)
589
+ # always set type for ES 6
590
+ # for ES 7 only set it if the user defined it
591
+ (maximum_seen_major_version < 7) || (maximum_seen_major_version == 7 && @document_type)
592
+ end
593
+
594
+ def install_template
595
+ TemplateManager.install_template(self)
596
+ rescue => e
597
+ @logger.error("Failed to install template", message: e.message, exception: e.class, backtrace: e.backtrace)
598
+ end
599
+
600
+ def setup_ecs_compatibility_related_defaults
601
+ case ecs_compatibility
602
+ when :disabled
603
+ @default_index = "logstash-%{+yyyy.MM.dd}"
604
+ @default_ilm_rollover_alias = "logstash"
605
+ @default_template_name = 'logstash'
606
+ when :v1, :v8
607
+ @default_index = "ecs-logstash-%{+yyyy.MM.dd}"
608
+ @default_ilm_rollover_alias = "ecs-logstash"
609
+ @default_template_name = 'ecs-logstash'
610
+ else
611
+ fail("unsupported ECS Compatibility `#{ecs_compatibility}`")
612
+ end
613
+
614
+ @index ||= default_index
615
+ @ilm_rollover_alias ||= default_ilm_rollover_alias
616
+ @template_name ||= default_template_name
617
+ end
618
+
619
+ def setup_template_manager_defaults(data_stream_enabled)
620
+ if original_params["manage_template"].nil? && data_stream_enabled
621
+ logger.debug("Disabling template management since data streams are enabled")
622
+ @manage_template = false
623
+ end
624
+ end
625
+
626
+ def setup_ssl_params!
627
+ @ssl_enabled = normalize_config(:ssl_enabled) do |normalize|
628
+ normalize.with_deprecated_alias(:ssl)
629
+ end
630
+
631
+ @ssl_certificate_authorities = normalize_config(:ssl_certificate_authorities) do |normalize|
632
+ normalize.with_deprecated_mapping(:cacert) do |cacert|
633
+ [cacert]
634
+ end
635
+ end
636
+
637
+ @ssl_keystore_path = normalize_config(:ssl_keystore_path) do |normalize|
638
+ normalize.with_deprecated_alias(:keystore)
639
+ end
640
+
641
+ @ssl_keystore_password = normalize_config(:ssl_keystore_password) do |normalize|
642
+ normalize.with_deprecated_alias(:keystore_password)
643
+ end
644
+
645
+ @ssl_truststore_path = normalize_config(:ssl_truststore_path) do |normalize|
646
+ normalize.with_deprecated_alias(:truststore)
647
+ end
648
+
649
+ @ssl_truststore_password = normalize_config(:ssl_truststore_password) do |normalize|
650
+ normalize.with_deprecated_alias(:truststore_password)
651
+ end
652
+
653
+ @ssl_verification_mode = normalize_config(:ssl_verification_mode) do |normalize|
654
+ normalize.with_deprecated_mapping(:ssl_certificate_verification) do |ssl_certificate_verification|
655
+ if ssl_certificate_verification == true
656
+ "full"
657
+ else
658
+ "none"
659
+ end
660
+ end
661
+ end
662
+
663
+ params['ssl_enabled'] = @ssl_enabled unless @ssl_enabled.nil?
664
+ params['ssl_certificate_authorities'] = @ssl_certificate_authorities unless @ssl_certificate_authorities.nil?
665
+ params['ssl_keystore_path'] = @ssl_keystore_path unless @ssl_keystore_path.nil?
666
+ params['ssl_keystore_password'] = @ssl_keystore_password unless @ssl_keystore_password.nil?
667
+ params['ssl_truststore_path'] = @ssl_truststore_path unless @ssl_truststore_path.nil?
668
+ params['ssl_truststore_password'] = @ssl_truststore_password unless @ssl_truststore_password.nil?
669
+ params['ssl_verification_mode'] = @ssl_verification_mode unless @ssl_verification_mode.nil?
670
+ end
671
+
672
+ # To be overidden by the -java version
673
+ VALID_HTTP_ACTIONS = ["index", "delete", "create", "update"]
674
+ def valid_actions
675
+ VALID_HTTP_ACTIONS
676
+ end
677
+
678
+ def check_action_validity
679
+ return if @action.nil? # not set
680
+ raise LogStash::ConfigurationError, "No action specified!" if @action.empty?
681
+
682
+ # If we're using string interpolation, we're good!
683
+ return if @action =~ /%{.+}/
684
+ return if valid_actions.include?(@action)
685
+
686
+ raise LogStash::ConfigurationError, "Action '#{@action}' is invalid! Pick one of #{valid_actions} or use a sprintf style statement"
687
+ end
688
+
689
+ def check_dlq_custom_codes
690
+ intersection = dlq_custom_codes & DOC_DLQ_CODES
691
+ raise LogStash::ConfigurationError, "#{intersection} are already defined as standard DLQ error codes" unless intersection.empty?
692
+
693
+ intersection = dlq_custom_codes & DOC_SUCCESS_CODES
694
+ raise LogStash::ConfigurationError, "#{intersection} are success codes which cannot be redefined in dlq_custom_codes" unless intersection.empty?
695
+
696
+ intersection = dlq_custom_codes & [DOC_CONFLICT_CODE]
697
+ raise LogStash::ConfigurationError, "#{intersection} are error codes already defined as conflict which cannot be redefined in dlq_custom_codes" unless intersection.empty?
698
+ end
699
+ end