logstash-input-http_poller 5.0.2 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 50ae088dbf54c3b101fdbb36f6743d9525c4664ef9dc592ba3d351580a8712a8
4
- data.tar.gz: 0ea8ab2cfae1a16dd3faadf4a1af9ccac2b9e235d1ac0a83353ed21e95c70b78
3
+ metadata.gz: d7223eb133a657b108ccadc547d953ea940d9c7f814b32e69048bee665ff89fa
4
+ data.tar.gz: ea919bb6bd64fd83cedf9421e2457e60717357ae2ad41798feb2edb227403f40
5
5
  SHA512:
6
- metadata.gz: 47457f51157fb74494d11b03567af00c7855f1824b020740c836977fd99f3b91a06bbde2724148205971c4ff02dc539580537e90088dd36a14e5daf09311d94d
7
- data.tar.gz: 982dd17052f30f21f8a4d6d1e852260f0955d11f0d42eae0dcc003a3478522fb4cb4a8310e57db821238127aefbe728cd758154e689045dc7dbc221ca29f617b
6
+ metadata.gz: 205e05fa3dc29773537dbfa9f58eb9efe489b518b3e210c1668434221b69b3f7c339f57c70951ee83f8ddffa55eaf22e3a269479e079e453080f4089301e9071
7
+ data.tar.gz: a1b9acc31715ea764b3f50a8388da6578d1d3e5380503d154c9d5e8afb347992b8ce7c8dd5fc60c4783c3dfe81b7e722691e70c5631fc9823c8ea979f55d87a9
data/CHANGELOG.md CHANGED
@@ -1,3 +1,6 @@
1
+ ## 5.1.0
2
+ - Add ECS support [#129](https://github.com/logstash-plugins/logstash-input-http_poller/pull/129)
3
+
1
4
  ## 5.0.2
2
5
  - [DOC]Expanded url option to include Manticore keys [#119](https://github.com/logstash-plugins/logstash-input-http_poller/pull/119)
3
6
 
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Logstash HTTP input plugin
2
2
 
3
- [![Travis Build Status](https://travis-ci.org/logstash-plugins/logstash-input-http_poller.svg)](https://travis-ci.org/logstash-plugins/logstash-input-http_poller)
3
+ [![Travis Build Status](https://travis-ci.com/logstash-plugins/logstash-input-http_poller.svg)](https://travis-ci.com/logstash-plugins/logstash-input-http_poller)
4
4
 
5
5
  This plugin is based off [logstash-input-rest](https://github.com/maximede/logstash-input-rest) by @maximede.
6
6
 
data/docs/index.asciidoc CHANGED
@@ -87,6 +87,36 @@ The above snippet will create two files `downloaded_cert.pem` and `downloaded_tr
87
87
  ----------------------------------
88
88
 
89
89
 
90
+ [id="plugins-{type}s-{plugin}-ecs_metadata"]
91
+ ==== Event Metadata and the Elastic Common Schema (ECS)
92
+
93
+ This input will add metadata about the HTTP connection itself to each event.
94
+
95
+ When ECS compatibility is disabled, metadata was added to a variety of non-standard top-level fields, which has the potential to create confusion and schema conflicts downstream.
96
+
97
+ With ECS Compatibility Mode, we can ensure a pipeline maintains access to this metadata throughout the event's lifecycle without polluting the top-level namespace.
98
+
99
+ Here’s how ECS compatibility mode affects output.
100
+ [cols="<l,<l,e,<e"]
101
+ |=======================================================================
102
+ | ECS disabled | ECS v1 | Availability | Description
103
+
104
+ | [@metadata][host] | [@metadata][input][http_poller][request][host][hostname] | Always | Hostname
105
+ | [@metadata][code] | [@metadata][input][http_poller][response][status_code] | When server responds a valid status code | HTTP response code
106
+ | [@metadata][response_headers] | [@metadata][input][http_poller][response][headers] | When server responds with headers | HTTP headers of the response
107
+ | [@metadata][response_message] | [@metadata][input][http_poller][response][status_message] | When server responds with status line | message of status line of HTTP headers
108
+ | [@metadata][runtime_seconds] | [@metadata][input][http_poller][response][elapsed_time_ns] | When server responds a valid status code | elapsed time of calling endpoint. ECS v1 shows in nanoseconds.
109
+ | [http_request_failure][runtime_seconds] | [event][duration] | When server throws exception | elapsed time of calling endpoint. ECS v1 shows in nanoseconds.
110
+ | [@metadata][times_retried] | [@metadata][input][http_poller][request][retry_count] | When the poller calls server successfully | retry count from http client library
111
+ | [@metadata][name] / [http_request_failure][name] | [@metadata][input][http_poller][request][name] | Always | The key of `urls` from poller config
112
+ | [@metadata][request] / [http_request_failure][request]| [@metadata][input][http_poller][request][original] | Always | The whole object of `urls` from poller config
113
+ | [http_request_failure][error] | [error][message] | When server throws exception | Error message
114
+ | [http_request_failure][backtrace] | [error][stack_trace] | When server throws exception | Stack trace of error
115
+ | -- | [url][full] | When server throws exception | The URL of the endpoint
116
+ | -- | [http][request][method] | When server throws exception | HTTP request method
117
+ | -- | [host][hostname] | When server throws exception | Hostname
118
+ |=======================================================================
119
+
90
120
  [id="plugins-{type}s-{plugin}-options"]
91
121
  ==== Http_poller Input Configuration Options
92
122
 
@@ -101,6 +131,7 @@ This plugin supports the following configuration options plus the <<plugins-{typ
101
131
  | <<plugins-{type}s-{plugin}-client_key>> |a valid filesystem path|No
102
132
  | <<plugins-{type}s-{plugin}-connect_timeout>> |<<number,number>>|No
103
133
  | <<plugins-{type}s-{plugin}-cookies>> |<<boolean,boolean>>|No
134
+ | <<plugins-{type}s-{plugin}-ecs_compatibility>> | <<string,string>>|No
104
135
  | <<plugins-{type}s-{plugin}-follow_redirects>> |<<boolean,boolean>>|No
105
136
  | <<plugins-{type}s-{plugin}-keepalive>> |<<boolean,boolean>>|No
106
137
  | <<plugins-{type}s-{plugin}-keystore>> |a valid filesystem path|No
@@ -180,6 +211,81 @@ Timeout (in seconds) to wait for a connection to be established. Default is `10s
180
211
  Enable cookie support. With this enabled the client will persist cookies
181
212
  across requests as a normal web browser would. Enabled by default
182
213
 
214
+ [id="plugins-{type}s-{plugin}-ecs_compatibility"]
215
+ ===== `ecs_compatibility`
216
+
217
+ * Value type is <<string,string>>
218
+ * Supported values are:
219
+ ** `disabled`: unstructured data added at root level
220
+ ** `v1`: uses `error`, `url` and `http` fields that are compatible with Elastic Common Schema
221
+
222
+ Controls this plugin's compatibility with the
223
+ {ecs-ref}[Elastic Common Schema (ECS)].
224
+ See <<plugins-{type}s-{plugin}-ecs_metadata>> for detailed information.
225
+
226
+ Example output:
227
+
228
+ **Sample output: ECS disabled**
229
+ [source,text]
230
+ -----
231
+ {
232
+ "http_poller_data" => {
233
+ "@version" => "1",
234
+ "@timestamp" => 2021-01-01T00:43:22.388Z,
235
+ "status" => "UP"
236
+ },
237
+ "@version" => "1",
238
+ "@timestamp" => 2021-01-01T00:43:22.389Z,
239
+ }
240
+ -----
241
+
242
+ **Sample output: ECS enabled**
243
+ [source,text]
244
+ -----
245
+ {
246
+ "http_poller_data" => {
247
+ "status" => "UP",
248
+ "@version" => "1",
249
+ "event" => {
250
+ "original" => "{\"status\":\"UP\"}"
251
+ },
252
+ "@timestamp" => 2021-01-01T00:40:59.558Z
253
+ },
254
+ "@version" => "1",
255
+ "@timestamp" => 2021-01-01T00:40:59.559Z
256
+ }
257
+ -----
258
+
259
+ **Sample error output: ECS enabled**
260
+ [source,text]
261
+ ----
262
+ {
263
+ "@timestamp" => 2021-07-09T09:53:48.721Z,
264
+ "@version" => "1",
265
+ "host" => {
266
+ "hostname" => "MacBook-Pro"
267
+ },
268
+ "http" => {
269
+ "request" => {
270
+ "method" => "get"
271
+ }
272
+ },
273
+ "event" => {
274
+ "duration" => 259019
275
+ },
276
+ "error" => {
277
+ "stack_trace" => nil,
278
+ "message" => "Connection refused (Connection refused)"
279
+ },
280
+ "url" => {
281
+ "full" => "http://localhost:8080/actuator/health"
282
+ },
283
+ "tags" => [
284
+ [0] "_http_request_failure"
285
+ ]
286
+ }
287
+ ----
288
+
183
289
  [id="plugins-{type}s-{plugin}-follow_redirects"]
184
290
  ===== `follow_redirects`
185
291
 
@@ -315,6 +421,10 @@ Timeout (in seconds) to wait for data on the socket. Default is `10s`
315
421
 
316
422
  Define the target field for placing the received data. If this setting is omitted, the data will be stored at the root (top level) of the event.
317
423
 
424
+ TIP: When ECS is enabled, set `target` in the codec (if the codec has a `target` option).
425
+ Example: `codec => json { target => "TARGET_FIELD_NAME" }`
426
+
427
+
318
428
  [id="plugins-{type}s-{plugin}-truststore"]
319
429
  ===== `truststore`
320
430
 
@@ -5,9 +5,18 @@ require "logstash/plugin_mixins/http_client"
5
5
  require "socket" # for Socket.gethostname
6
6
  require "manticore"
7
7
  require "rufus/scheduler"
8
+ require "logstash/plugin_mixins/ecs_compatibility_support"
9
+ require 'logstash/plugin_mixins/ecs_compatibility_support/target_check'
10
+ require 'logstash/plugin_mixins/validator_support/field_reference_validation_adapter'
11
+ require 'logstash/plugin_mixins/event_support/event_factory_adapter'
8
12
 
9
13
  class LogStash::Inputs::HTTP_Poller < LogStash::Inputs::Base
10
14
  include LogStash::PluginMixins::HttpClient
15
+ include LogStash::PluginMixins::ECSCompatibilitySupport(:disabled, :v1, :v8 => :v1)
16
+ include LogStash::PluginMixins::ECSCompatibilitySupport::TargetCheck
17
+ include LogStash::PluginMixins::EventSupport::EventFactoryAdapter
18
+
19
+ extend LogStash::PluginMixins::ValidatorSupport::FieldReferenceValidationAdapter
11
20
 
12
21
  config_name "http_poller"
13
22
 
@@ -28,7 +37,7 @@ class LogStash::Inputs::HTTP_Poller < LogStash::Inputs::Base
28
37
  config :schedule, :validate => :hash, :required => true
29
38
 
30
39
  # Define the target field for placing the received data. If this setting is omitted, the data will be stored at the root (top level) of the event.
31
- config :target, :validate => :string
40
+ config :target, :validate => :field_reference
32
41
 
33
42
  # If you'd like to work with the request/response metadata.
34
43
  # Set this value to the name of the field you'd like to store a nested
@@ -42,6 +51,7 @@ class LogStash::Inputs::HTTP_Poller < LogStash::Inputs::Base
42
51
 
43
52
  @logger.info("Registering http_poller Input", :type => @type, :schedule => @schedule, :timeout => @timeout)
44
53
 
54
+ setup_ecs_field!
45
55
  setup_requests!
46
56
  end
47
57
 
@@ -55,6 +65,35 @@ class LogStash::Inputs::HTTP_Poller < LogStash::Inputs::Base
55
65
  @requests = Hash[@urls.map {|name, url| [name, normalize_request(url)] }]
56
66
  end
57
67
 
68
+ private
69
+ # In the context of ECS, there are two type of events in this plugin, valid HTTP response and failure
70
+ # For a valid HTTP response, `url`, `request_method` and `host` are metadata of request.
71
+ # The call could retrieve event which contain `[url]`, `[http][request][method]`, `[host][hostname]` data
72
+ # Therefore, metadata should not write to those fields
73
+ # For a failure, `url`, `request_method` and `host` are primary data of the event because the plugin owns this event,
74
+ # so it writes to url.*, http.*, host.*
75
+ def setup_ecs_field!
76
+ @request_host_field = ecs_select[disabled: "[#{metadata_target}][host]", v1: "[#{metadata_target}][input][http_poller][request][host][hostname]"]
77
+ @response_code_field = ecs_select[disabled: "[#{metadata_target}][code]", v1: "[#{metadata_target}][input][http_poller][response][status_code]"]
78
+ @response_headers_field = ecs_select[disabled: "[#{metadata_target}][response_headers]", v1: "[#{metadata_target}][input][http_poller][response][headers]"]
79
+ @response_message_field = ecs_select[disabled: "[#{metadata_target}][response_message]", v1: "[#{metadata_target}][input][http_poller][response][status_message]"]
80
+ @response_time_s_field = ecs_select[disabled: "[#{metadata_target}][runtime_seconds]", v1: nil]
81
+ @response_time_ns_field = ecs_select[disabled: nil, v1: "[#{metadata_target}][input][http_poller][response][elapsed_time_ns]"]
82
+ @request_retry_count_field = ecs_select[disabled: "[#{metadata_target}][times_retried]", v1: "[#{metadata_target}][input][http_poller][request][retry_count]"]
83
+ @request_name_field = ecs_select[disabled: "[#{metadata_target}][name]", v1: "[#{metadata_target}][input][http_poller][request][name]"]
84
+ @original_request_field = ecs_select[disabled: "[#{metadata_target}][request]", v1: "[#{metadata_target}][input][http_poller][request][original]"]
85
+
86
+ @error_msg_field = ecs_select[disabled: "[http_request_failure][error]", v1: "[error][message]"]
87
+ @stack_trace_field = ecs_select[disabled: "[http_request_failure][backtrace]", v1: "[error][stack_trace]"]
88
+ @fail_original_request_field = ecs_select[disabled: "[http_request_failure][request]", v1: nil]
89
+ @fail_request_name_field = ecs_select[disabled: "[http_request_failure][name]", v1: nil]
90
+ @fail_response_time_s_field = ecs_select[disabled: "[http_request_failure][runtime_seconds]", v1: nil]
91
+ @fail_response_time_ns_field = ecs_select[disabled: nil, v1: "[event][duration]"]
92
+ @fail_request_url_field = ecs_select[disabled: nil, v1: "[url][full]"]
93
+ @fail_request_method_field = ecs_select[disabled: nil, v1: "[http][request][method]"]
94
+ @fail_request_host_field = ecs_select[disabled: nil, v1: "[host][hostname]"]
95
+ end
96
+
58
97
  private
59
98
  def normalize_request(url_or_spec)
60
99
  if url_or_spec.is_a?(String)
@@ -151,10 +190,14 @@ class LogStash::Inputs::HTTP_Poller < LogStash::Inputs::Base
151
190
 
152
191
  method, *request_opts = request
153
192
  client.async.send(method, *request_opts).
154
- on_success {|response| handle_success(queue, name, request, response, Time.now - started)}.
155
- on_failure {|exception|
156
- handle_failure(queue, name, request, exception, Time.now - started)
157
- }
193
+ on_success {|response| handle_success(queue, name, request, response, Time.now - started) }.
194
+ on_failure {|exception| handle_failure(queue, name, request, exception, Time.now - started) }
195
+ end
196
+
197
+ private
198
+ # time diff in float to nanoseconds
199
+ def to_nanoseconds(time_diff)
200
+ (time_diff * 1000000).to_i
158
201
  end
159
202
 
160
203
  private
@@ -164,11 +207,11 @@ class LogStash::Inputs::HTTP_Poller < LogStash::Inputs::Base
164
207
  # responses come up as "" which will cause the codec to not yield anything
165
208
  if body && body.size > 0
166
209
  decode_and_flush(@codec, body) do |decoded|
167
- event = @target ? LogStash::Event.new(@target => decoded.to_hash) : decoded
210
+ event = @target ? targeted_event_factory.new_event(decoded.to_hash) : decoded
168
211
  handle_decoded_event(queue, name, request, response, event, execution_time)
169
212
  end
170
213
  else
171
- event = ::LogStash::Event.new
214
+ event = event_factory.new_event
172
215
  handle_decoded_event(queue, name, request, response, event, execution_time)
173
216
  end
174
217
  end
@@ -197,20 +240,10 @@ class LogStash::Inputs::HTTP_Poller < LogStash::Inputs::Base
197
240
  private
198
241
  # Beware, on old versions of manticore some uncommon failures are not handled
199
242
  def handle_failure(queue, name, request, exception, execution_time)
200
- event = LogStash::Event.new
201
- apply_metadata(event, name, request)
202
-
243
+ event = event_factory.new_event
203
244
  event.tag("_http_request_failure")
204
-
205
- # This is also in the metadata, but we send it anyone because we want this
206
- # persisted by default, whereas metadata isn't. People don't like mysterious errors
207
- event.set("http_request_failure", {
208
- "request" => structure_request(request),
209
- "name" => name,
210
- "error" => exception.to_s,
211
- "backtrace" => exception.backtrace,
212
- "runtime_seconds" => execution_time
213
- })
245
+ apply_metadata(event, name, request, nil, execution_time)
246
+ apply_failure_fields(event, name, request, exception, execution_time)
214
247
 
215
248
  queue << event
216
249
  rescue StandardError, java.lang.Exception => e
@@ -231,29 +264,39 @@ class LogStash::Inputs::HTTP_Poller < LogStash::Inputs::Base
231
264
  end
232
265
 
233
266
  private
234
- def apply_metadata(event, name, request, response=nil, execution_time=nil)
267
+ def apply_metadata(event, name, request, response, execution_time)
235
268
  return unless @metadata_target
236
- event.set(@metadata_target, event_metadata(name, request, response, execution_time))
237
- end
238
-
239
- private
240
- def event_metadata(name, request, response=nil, execution_time=nil)
241
- m = {
242
- "name" => name,
243
- "host" => @host,
244
- "request" => structure_request(request),
245
- }
246
269
 
247
- m["runtime_seconds"] = execution_time
270
+ event.set(@request_host_field, @host)
271
+ event.set(@response_time_s_field, execution_time) if @response_time_s_field
272
+ event.set(@response_time_ns_field, to_nanoseconds(execution_time)) if @response_time_ns_field
273
+ event.set(@request_name_field, name)
274
+ event.set(@original_request_field, structure_request(request))
248
275
 
249
276
  if response
250
- m["code"] = response.code
251
- m["response_headers"] = response.headers
252
- m["response_message"] = response.message
253
- m["times_retried"] = response.times_retried
277
+ event.set(@response_code_field, response.code)
278
+ event.set(@response_headers_field, response.headers)
279
+ event.set(@response_message_field, response.message)
280
+ event.set(@request_retry_count_field, response.times_retried)
254
281
  end
282
+ end
255
283
 
256
- m
284
+ private
285
+ def apply_failure_fields(event, name, request, exception, execution_time)
286
+ # This is also in the metadata, but we send it anyone because we want this
287
+ # persisted by default, whereas metadata isn't. People don't like mysterious errors
288
+ event.set(@fail_original_request_field, structure_request(request)) if @fail_original_request_field
289
+ event.set(@fail_request_name_field, name) if @fail_request_name_field
290
+
291
+ method, url, _ = request
292
+ event.set(@fail_request_url_field, url) if @fail_request_url_field
293
+ event.set(@fail_request_method_field, method.to_s) if @fail_request_method_field
294
+ event.set(@fail_request_host_field, @host) if @fail_request_host_field
295
+
296
+ event.set(@fail_response_time_s_field, execution_time) if @fail_response_time_s_field
297
+ event.set(@fail_response_time_ns_field, to_nanoseconds(execution_time)) if @fail_response_time_ns_field
298
+ event.set(@error_msg_field, exception.to_s)
299
+ event.set(@stack_trace_field, exception.backtrace)
257
300
  end
258
301
 
259
302
  private
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'logstash-input-http_poller'
3
- s.version = '5.0.2'
3
+ s.version = '5.1.0'
4
4
  s.licenses = ['Apache License (2.0)']
5
5
  s.summary = "Decodes the output of an HTTP API into events"
6
6
  s.description = "This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This gem is not a stand-alone program"
@@ -23,6 +23,9 @@ Gem::Specification.new do |s|
23
23
  s.add_runtime_dependency 'logstash-mixin-http_client', "~> 7"
24
24
  s.add_runtime_dependency 'stud', "~> 0.0.22"
25
25
  s.add_runtime_dependency 'rufus-scheduler', "~>3.0.9"
26
+ s.add_runtime_dependency 'logstash-mixin-ecs_compatibility_support', '~>1.3'
27
+ s.add_runtime_dependency 'logstash-mixin-event_support', '~> 1.0', '>= 1.0.1'
28
+ s.add_runtime_dependency 'logstash-mixin-validator_support', '~> 1.0'
26
29
 
27
30
  s.add_development_dependency 'logstash-codec-json'
28
31
  s.add_development_dependency 'logstash-codec-line'
@@ -5,6 +5,7 @@ require 'flores/random'
5
5
  require "timecop"
6
6
  # Workaround for the bug reported in https://github.com/jruby/jruby/issues/4637
7
7
  require 'rspec/matchers/built_in/raise_error.rb'
8
+ require 'logstash/plugin_mixins/ecs_compatibility_support/spec_helper'
8
9
 
9
10
  describe LogStash::Inputs::HTTP_Poller do
10
11
  let(:metadata_target) { "_http_poller_metadata" }
@@ -29,6 +30,7 @@ describe LogStash::Inputs::HTTP_Poller do
29
30
  }
30
31
  let(:klass) { LogStash::Inputs::HTTP_Poller }
31
32
 
33
+
32
34
  describe "instances" do
33
35
  subject { klass.new(default_opts) }
34
36
 
@@ -133,10 +135,10 @@ describe LogStash::Inputs::HTTP_Poller do
133
135
 
134
136
  it "should properly set the auth parameter" do
135
137
  expect(normalized[2][:auth]).to eq({
136
- :user => auth["user"],
137
- :pass => auth["password"],
138
- :eager => true
139
- })
138
+ :user => auth["user"],
139
+ :pass => auth["password"],
140
+ :eager => true
141
+ })
140
142
  end
141
143
  end
142
144
  end
@@ -144,14 +146,14 @@ describe LogStash::Inputs::HTTP_Poller do
144
146
  # Legacy way of doing things, kept for backwards compat.
145
147
  describe "auth with nested auth hash" do
146
148
  let(:url) { {"url" => "http://localhost", "method" => "get", "auth" => auth} }
147
-
149
+
148
150
  include_examples("auth")
149
151
  end
150
152
 
151
153
  # The new 'right' way to do things
152
154
  describe "auth with direct auth options" do
153
155
  let(:url) { {"url" => "http://localhost", "method" => "get", "user" => auth["user"], "password" => auth["password"]} }
154
-
156
+
155
157
  include_examples("auth")
156
158
  end
157
159
  end
@@ -255,7 +257,7 @@ describe LogStash::Inputs::HTTP_Poller do
255
257
  instance.stop
256
258
  runner.kill
257
259
  runner.join
258
- expect(queue.size).to eq(3)
260
+ expect(queue.size).to be_between(2, 3)
259
261
  end
260
262
  end
261
263
 
@@ -284,216 +286,267 @@ describe LogStash::Inputs::HTTP_Poller do
284
286
  end
285
287
  end
286
288
 
287
- describe "events" do
288
- shared_examples("matching metadata") {
289
- let(:metadata) { event.get(metadata_target) }
290
-
291
- it "should have the correct name" do
292
- expect(metadata["name"]).to eql(name)
289
+ describe "events", :ecs_compatibility_support, :aggregate_failures do
290
+ ecs_compatibility_matrix(:disabled, :v1, :v8 => :v1) do |ecs_select|
291
+ before do
292
+ allow_any_instance_of(described_class).to receive(:ecs_compatibility).and_return(ecs_compatibility)
293
293
  end
294
294
 
295
- it "should have the correct request url" do
296
- if url.is_a?(Hash) # If the url was specified as a complex test the whole thing
297
- expect(metadata["request"]).to eql(url)
298
- else # Otherwise we have to make some assumptions
299
- expect(metadata["request"]["url"]).to eql(url)
300
- end
301
- end
295
+ shared_examples("matching metadata") {
296
+ let(:metadata) { event.get(metadata_target) }
302
297
 
303
- it "should have the correct code" do
304
- expect(metadata["code"]).to eql(code)
305
- end
306
- }
298
+ it "should have the correct name" do
299
+ field = ecs_select[disabled: "[#{metadata_target}][name]", v1: "[#{metadata_target}][input][http_poller][request][name]"]
300
+ expect(event.get(field)).to eql(name)
301
+ end
307
302
 
308
- shared_examples "unprocessable_requests" do
309
- let(:poller) { LogStash::Inputs::HTTP_Poller.new(settings) }
310
- subject(:event) {
311
- poller.send(:run_once, queue)
312
- queue.pop(true)
313
- }
303
+ it "should have the correct request url" do
304
+ if url.is_a?(Hash) # If the url was specified as a complex test the whole thing
305
+ http_client_field = ecs_select[disabled: "[#{metadata_target}][request]",
306
+ v1: "[#{metadata_target}][input][http_poller][request][original]"]
307
+ expect(event.get(http_client_field)).to eql(url)
308
+ else # Otherwise we have to make some assumptions
309
+ url_field = ecs_select[disabled: "[#{metadata_target}][request][url]",
310
+ v1: "[#{metadata_target}][input][http_poller][request][original][url]"]
311
+ expect(event.get(url_field)).to eql(url)
312
+ end
313
+ end
314
314
 
315
- before do
316
- poller.register
317
- allow(poller).to receive(:handle_failure).and_call_original
318
- allow(poller).to receive(:handle_success)
319
- event # materialize the subject
320
- end
315
+ it "should have the correct code" do
316
+ expect(event.get(ecs_select[disabled: "[#{metadata_target}][code]",
317
+ v1: "[#{metadata_target}][input][http_poller][response][status_code]"]))
318
+ .to eql(code)
319
+ end
321
320
 
322
- it "should enqueue a message" do
323
- expect(event).to be_a(LogStash::Event)
324
- end
321
+ it "should have the correct host" do
322
+ expect(event.get(ecs_select[disabled: "[#{metadata_target}][host]",
323
+ v1: "[#{metadata_target}][input][http_poller][request][host][hostname]"]))
324
+ .not_to be_nil
325
+ end
326
+ }
325
327
 
326
- it "should enqueue a message with 'http_request_failure' set" do
327
- expect(event.get("http_request_failure")).to be_a(Hash)
328
- end
328
+ shared_examples "unprocessable_requests" do
329
+ let(:poller) { LogStash::Inputs::HTTP_Poller.new(settings) }
330
+ subject(:event) {
331
+ poller.send(:run_once, queue)
332
+ queue.pop(true)
333
+ }
329
334
 
330
- it "should tag the event with '_http_request_failure'" do
331
- expect(event.get("tags")).to include('_http_request_failure')
332
- end
335
+ before do
336
+ poller.register
337
+ allow(poller).to receive(:handle_failure).and_call_original
338
+ allow(poller).to receive(:handle_success)
339
+ event # materialize the subject
340
+ end
333
341
 
334
- it "should invoke handle failure exactly once" do
335
- expect(poller).to have_received(:handle_failure).once
336
- end
342
+ it "should enqueue a message" do
343
+ expect(event).to be_a(LogStash::Event)
344
+ end
337
345
 
338
- it "should not invoke handle success at all" do
339
- expect(poller).not_to have_received(:handle_success)
340
- end
346
+ it "should enqueue a message with 'http_request_failure' set" do
347
+ if ecs_compatibility == :disabled
348
+ expect(event.get("http_request_failure")).to be_a(Hash)
349
+ expect(event.get("[http_request_failure][runtime_seconds]")).to be_a(Float)
350
+ else
351
+ expect(event.get("http_request_failure")).to be_nil
352
+ expect(event.get("error")).to be_a(Hash)
353
+ expect(event.get("[event][duration]")).to be_a(Integer)
354
+ expect(event.get("[url][full]")).to eq(url)
355
+ expect(event.get("[http][request][method]")).to be_a(String)
356
+ expect(event.get("[host][hostname]")).to be_a(String)
357
+ end
358
+ end
341
359
 
342
- include_examples("matching metadata")
343
- end
360
+ it "should tag the event with '_http_request_failure'" do
361
+ expect(event.get("tags")).to include('_http_request_failure')
362
+ end
344
363
 
345
- context "with a non responsive server" do
346
- context "due to a non-existant host" do # Fail with handlers
347
- let(:name) { default_name }
348
- let(:url) { "http://thouetnhoeu89ueoueohtueohtneuohn" }
349
- let(:code) { nil } # no response expected
364
+ it "should invoke handle failure exactly once" do
365
+ expect(poller).to have_received(:handle_failure).once
366
+ end
350
367
 
351
- let(:settings) { default_opts.merge("urls" => { name => url}) }
368
+ it "should not invoke handle success at all" do
369
+ expect(poller).not_to have_received(:handle_success)
370
+ end
352
371
 
353
- include_examples("unprocessable_requests")
372
+ include_examples("matching metadata")
354
373
  end
355
374
 
356
- context "due to a bogus port number" do # fail with return?
357
- let(:invalid_port) { Flores::Random.integer(65536..1000000) }
375
+ context "with a non responsive server" do
376
+ context "due to a non-existant host" do # Fail with handlers
377
+ let(:name) { default_name }
378
+ let(:url) { "http://thouetnhoeu89ueoueohtueohtneuohn" }
379
+ let(:code) { nil } # no response expected
358
380
 
359
- let(:name) { default_name }
360
- let(:url) { "http://127.0.0.1:#{invalid_port}" }
361
- let(:settings) { default_opts.merge("urls" => {name => url}) }
362
- let(:code) { nil } # No response expected
381
+ let(:settings) { default_opts.merge("urls" => { name => url}) }
363
382
 
364
- include_examples("unprocessable_requests")
365
- end
366
- end
383
+ include_examples("unprocessable_requests")
384
+ end
367
385
 
368
- describe "a valid request and decoded response" do
369
- let(:payload) { {"a" => 2, "hello" => ["a", "b", "c"]} }
370
- let(:response_body) { LogStash::Json.dump(payload) }
371
- let(:opts) { default_opts }
372
- let(:instance) {
373
- klass.new(opts)
374
- }
375
- let(:name) { default_name }
376
- let(:url) { default_url }
377
- let(:code) { 202 }
386
+ context "due to a bogus port number" do # fail with return?
387
+ let(:invalid_port) { Flores::Random.integer(65536..1000000) }
378
388
 
379
- subject(:event) {
380
- queue.pop(true)
381
- }
389
+ let(:name) { default_name }
390
+ let(:url) { "http://127.0.0.1:#{invalid_port}" }
391
+ let(:settings) { default_opts.merge("urls" => {name => url}) }
392
+ let(:code) { nil } # No response expected
382
393
 
383
- before do
384
- instance.register
385
- u = url.is_a?(Hash) ? url["url"] : url # handle both complex specs and simple string URLs
386
- instance.client.stub(u,
387
- :body => response_body,
388
- :code => code
389
- )
390
- allow(instance).to receive(:decorate)
391
- instance.send(:run_once, queue)
394
+ include_examples("unprocessable_requests")
395
+ end
392
396
  end
393
397
 
394
- it "should have a matching message" do
395
- expect(event.to_hash).to include(payload)
396
- end
398
+ describe "a valid request and decoded response" do
399
+ let(:payload) { {"a" => 2, "hello" => ["a", "b", "c"]} }
400
+ let(:response_body) { LogStash::Json.dump(payload) }
401
+ let(:opts) { default_opts }
402
+ let(:instance) {
403
+ klass.new(opts)
404
+ }
405
+ let(:name) { default_name }
406
+ let(:url) { default_url }
407
+ let(:code) { 202 }
397
408
 
398
- it "should decorate the event" do
399
- expect(instance).to have_received(:decorate).once
400
- end
409
+ subject(:event) {
410
+ queue.pop(true)
411
+ }
401
412
 
402
- include_examples("matching metadata")
403
-
404
- context "with an empty body" do
405
- let(:response_body) { "" }
406
- it "should return an empty event" do
413
+ before do
414
+ instance.register
415
+ u = url.is_a?(Hash) ? url["url"] : url # handle both complex specs and simple string URLs
416
+ instance.client.stub(u,
417
+ :body => response_body,
418
+ :code => code
419
+ )
420
+ allow(instance).to receive(:decorate)
407
421
  instance.send(:run_once, queue)
408
- expect(event.get("[_http_poller_metadata][response_headers][content-length]")).to eql("0")
409
422
  end
410
- end
411
423
 
412
- context "with metadata omitted" do
413
- let(:opts) {
414
- opts = default_opts.clone
415
- opts.delete("metadata_target")
416
- opts
417
- }
424
+ it "should have a matching message" do
425
+ expect(event.to_hash).to include(payload)
426
+ end
418
427
 
419
- it "should not have any metadata on the event" do
420
- instance.send(:run_once, queue)
421
- expect(event.get(metadata_target)).to be_nil
428
+ it "should decorate the event" do
429
+ expect(instance).to have_received(:decorate).once
422
430
  end
423
- end
424
431
 
425
- context "with a complex URL spec" do
426
- let(:url) {
427
- {
428
- "method" => "get",
429
- "url" => default_url,
430
- "headers" => {
431
- "X-Fry" => "I'm having one of those things, like a headache, with pictures..."
432
+ include_examples("matching metadata")
433
+
434
+ context "with an empty body" do
435
+ let(:response_body) { "" }
436
+ it "should return an empty event" do
437
+ instance.send(:run_once, queue)
438
+ headers_field = ecs_select[disabled: "[#{metadata_target}][response_headers]",
439
+ v1: "[#{metadata_target}][input][http_poller][response][headers]"]
440
+ expect(event.get("#{headers_field}[content-length]")).to eql("0")
441
+ end
442
+ end
443
+
444
+ context "with metadata omitted" do
445
+ let(:opts) {
446
+ opts = default_opts.clone
447
+ opts.delete("metadata_target")
448
+ opts
449
+ }
450
+
451
+ it "should not have any metadata on the event" do
452
+ instance.send(:run_once, queue)
453
+ expect(event.get(metadata_target)).to be_nil
454
+ end
455
+ end
456
+
457
+ context "with a complex URL spec" do
458
+ let(:url) {
459
+ {
460
+ "method" => "get",
461
+ "url" => default_url,
462
+ "headers" => {
463
+ "X-Fry" => "I'm having one of those things, like a headache, with pictures..."
464
+ }
432
465
  }
433
466
  }
434
- }
435
- let(:opts) {
436
- {
437
- "schedule" => {
438
- "cron" => "* * * * * UTC"
467
+ let(:opts) {
468
+ {
469
+ "schedule" => {
470
+ "cron" => "* * * * * UTC"
471
+ },
472
+ "urls" => {
473
+ default_name => url
439
474
  },
440
- "urls" => {
441
- default_name => url
442
- },
443
- "codec" => "json",
444
- "metadata_target" => metadata_target
475
+ "codec" => "json",
476
+ "metadata_target" => metadata_target
477
+ }
445
478
  }
446
- }
447
479
 
448
- include_examples("matching metadata")
480
+ include_examples("matching metadata")
449
481
 
450
- it "should have a matching message" do
451
- expect(event.to_hash).to include(payload)
482
+ it "should have a matching message" do
483
+ expect(event.to_hash).to include(payload)
484
+ end
452
485
  end
453
- end
454
486
 
455
- context "with a specified target" do
456
- let(:target) { "mytarget" }
457
- let(:opts) { default_opts.merge("target" => target) }
487
+ context "with a specified target" do
488
+ let(:target) { "mytarget" }
489
+ let(:opts) { default_opts.merge("target" => target) }
458
490
 
459
- it "should store the event info in the target" do
460
- # When events go through the pipeline they are java-ified
461
- # this normalizes the payload to java types
462
- payload_normalized = LogStash::Json.load(LogStash::Json.dump(payload))
463
- expect(event.get(target)).to include(payload_normalized)
491
+ it "should store the event info in the target" do
492
+ # When events go through the pipeline they are java-ified
493
+ # this normalizes the payload to java types
494
+ payload_normalized = LogStash::Json.load(LogStash::Json.dump(payload))
495
+ expect(event.get(target)).to include(payload_normalized)
496
+ end
464
497
  end
465
- end
466
498
 
467
- context 'using a line codec' do
468
- let(:opts) do
469
- default_opts.merge({"codec" => "line"})
470
- end
471
- subject(:events) do
472
- [].tap do |events|
473
- events << queue.pop until queue.empty?
499
+ context "with default metadata target" do
500
+ let(:metadata_target) { "@metadata" }
501
+
502
+ it "should store the metadata info in @metadata" do
503
+ if ecs_compatibility == :disabled
504
+ expect(event.get("[@metadata][response_headers]")).to be_a(Hash)
505
+ expect(event.get("[@metadata][runtime_seconds]")).to be_a(Float)
506
+ expect(event.get("[@metadata][times_retried]")).to eq(0)
507
+ expect(event.get("[@metadata][name]")).to eq(default_name)
508
+ expect(event.get("[@metadata][request]")).to be_a(Hash)
509
+ else
510
+ expect(event.get("[@metadata][input][http_poller][response][headers]")).to be_a(Hash)
511
+ expect(event.get("[@metadata][input][http_poller][response][elapsed_time_ns]")).to be_a(Integer)
512
+ expect(event.get("[@metadata][input][http_poller][request][retry_count]")).to eq(0)
513
+ expect(event.get("[@metadata][input][http_poller][request][name]")).to eq(default_name)
514
+ expect(event.get("[@metadata][input][http_poller][request][original]")).to be_a(Hash)
515
+ end
474
516
  end
475
517
  end
476
518
 
477
- context 'when response has a trailing newline' do
478
- let(:response_body) { "one\ntwo\nthree\nfour\n" }
479
- it 'emits all events' do
480
- expect(events.size).to equal(4)
481
- messages = events.map{|e| e.get('message')}
482
- expect(messages).to include('one')
483
- expect(messages).to include('two')
484
- expect(messages).to include('three')
485
- expect(messages).to include('four')
519
+ context 'using a line codec' do
520
+ let(:opts) do
521
+ default_opts.merge({"codec" => "line"})
486
522
  end
487
- end
488
- context 'when response has no trailing newline' do
489
- let(:response_body) { "one\ntwo\nthree\nfour" }
490
- it 'emits all events' do
491
- expect(events.size).to equal(4)
492
- messages = events.map{|e| e.get('message')}
493
- expect(messages).to include('one')
494
- expect(messages).to include('two')
495
- expect(messages).to include('three')
496
- expect(messages).to include('four')
523
+ subject(:events) do
524
+ [].tap do |events|
525
+ events << queue.pop until queue.empty?
526
+ end
527
+ end
528
+
529
+ context 'when response has a trailing newline' do
530
+ let(:response_body) { "one\ntwo\nthree\nfour\n" }
531
+ it 'emits all events' do
532
+ expect(events.size).to equal(4)
533
+ messages = events.map{|e| e.get('message')}
534
+ expect(messages).to include('one')
535
+ expect(messages).to include('two')
536
+ expect(messages).to include('three')
537
+ expect(messages).to include('four')
538
+ end
539
+ end
540
+ context 'when response has no trailing newline' do
541
+ let(:response_body) { "one\ntwo\nthree\nfour" }
542
+ it 'emits all events' do
543
+ expect(events.size).to equal(4)
544
+ messages = events.map{|e| e.get('message')}
545
+ expect(messages).to include('one')
546
+ expect(messages).to include('two')
547
+ expect(messages).to include('three')
548
+ expect(messages).to include('four')
549
+ end
497
550
  end
498
551
  end
499
552
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-input-http_poller
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.2
4
+ version: 5.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elastic
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2020-08-05 00:00:00.000000000 Z
12
+ date: 2021-08-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  requirement: !ruby/object:Gem::Requirement
@@ -87,6 +87,54 @@ dependencies:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
89
  version: 3.0.9
90
+ - !ruby/object:Gem::Dependency
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '1.3'
96
+ name: logstash-mixin-ecs_compatibility_support
97
+ prerelease: false
98
+ type: :runtime
99
+ version_requirements: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.3'
104
+ - !ruby/object:Gem::Dependency
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '1.0'
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: 1.0.1
113
+ name: logstash-mixin-event_support
114
+ prerelease: false
115
+ type: :runtime
116
+ version_requirements: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - "~>"
119
+ - !ruby/object:Gem::Version
120
+ version: '1.0'
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: 1.0.1
124
+ - !ruby/object:Gem::Dependency
125
+ requirement: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - "~>"
128
+ - !ruby/object:Gem::Version
129
+ version: '1.0'
130
+ name: logstash-mixin-validator_support
131
+ prerelease: false
132
+ type: :runtime
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: '1.0'
90
138
  - !ruby/object:Gem::Dependency
91
139
  requirement: !ruby/object:Gem::Requirement
92
140
  requirements:
@@ -195,8 +243,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
195
243
  - !ruby/object:Gem::Version
196
244
  version: '0'
197
245
  requirements: []
198
- rubyforge_project:
199
- rubygems_version: 2.6.13
246
+ rubygems_version: 3.1.6
200
247
  signing_key:
201
248
  specification_version: 4
202
249
  summary: Decodes the output of an HTTP API into events