logstash-core 5.0.0.alpha5.snapshot1-java → 5.0.0.alpha6.snapshot1-java

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of logstash-core might be problematic. Click here for more details.

Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/lib/logstash-core/version.rb +1 -1
  3. data/lib/logstash/agent.rb +1 -1
  4. data/lib/logstash/api/commands/default_metadata.rb +1 -1
  5. data/lib/logstash/api/commands/hot_threads_reporter.rb +4 -7
  6. data/lib/logstash/api/commands/node.rb +5 -4
  7. data/lib/logstash/api/commands/stats.rb +8 -3
  8. data/lib/logstash/api/modules/base.rb +5 -0
  9. data/lib/logstash/api/modules/node.rb +1 -2
  10. data/lib/logstash/api/modules/node_stats.rb +1 -2
  11. data/lib/logstash/codecs/base.rb +29 -1
  12. data/lib/logstash/config/mixin.rb +1 -1
  13. data/lib/logstash/environment.rb +5 -5
  14. data/lib/logstash/filter_delegator.rb +4 -5
  15. data/lib/logstash/instrument/periodic_poller/jvm.rb +43 -10
  16. data/lib/logstash/output_delegator.rb +33 -168
  17. data/lib/logstash/output_delegator_strategies/legacy.rb +29 -0
  18. data/lib/logstash/output_delegator_strategies/shared.rb +20 -0
  19. data/lib/logstash/output_delegator_strategies/single.rb +23 -0
  20. data/lib/logstash/output_delegator_strategy_registry.rb +36 -0
  21. data/lib/logstash/outputs/base.rb +39 -26
  22. data/lib/logstash/patches/clamp.rb +6 -0
  23. data/lib/logstash/pipeline.rb +42 -14
  24. data/lib/logstash/pipeline_reporter.rb +2 -8
  25. data/lib/logstash/plugin.rb +6 -10
  26. data/lib/logstash/runner.rb +12 -9
  27. data/lib/logstash/settings.rb +124 -21
  28. data/lib/logstash/util/wrapped_synchronous_queue.rb +17 -1
  29. data/lib/logstash/version.rb +1 -1
  30. data/lib/logstash/webserver.rb +44 -33
  31. data/locales/en.yml +5 -1
  32. data/logstash-core.gemspec +2 -2
  33. data/spec/api/lib/api/node_spec.rb +62 -10
  34. data/spec/api/lib/api/node_stats_spec.rb +16 -3
  35. data/spec/api/lib/api/support/resource_dsl_methods.rb +11 -1
  36. data/spec/api/spec_helper.rb +1 -1
  37. data/spec/conditionals_spec.rb +12 -1
  38. data/spec/logstash/agent_spec.rb +3 -0
  39. data/spec/logstash/codecs/base_spec.rb +74 -0
  40. data/spec/logstash/instrument/periodic_poller/jvm_spec.rb +37 -10
  41. data/spec/logstash/output_delegator_spec.rb +64 -89
  42. data/spec/logstash/outputs/base_spec.rb +91 -15
  43. data/spec/logstash/pipeline_reporter_spec.rb +1 -6
  44. data/spec/logstash/pipeline_spec.rb +20 -22
  45. data/spec/logstash/plugin_spec.rb +3 -3
  46. data/spec/logstash/runner_spec.rb +86 -3
  47. data/spec/logstash/settings/integer_spec.rb +20 -0
  48. data/spec/logstash/settings/numeric_spec.rb +28 -0
  49. data/spec/logstash/settings/port_range_spec.rb +93 -0
  50. data/spec/logstash/util/wrapped_synchronous_queue_spec.rb +6 -0
  51. data/spec/logstash/webserver_spec.rb +95 -0
  52. metadata +20 -6
@@ -176,18 +176,43 @@ module LogStash
176
176
  end
177
177
  end
178
178
 
179
- ### Specific settings #####
180
-
181
- class Boolean < Setting
182
- def initialize(name, default, strict=true, &validator_proc)
179
+ class Coercible < Setting
180
+ def initialize(name, klass, default=nil, strict=true, &validator_proc)
183
181
  @name = name
184
- @klass = Object
182
+ unless klass.is_a?(Class)
183
+ raise ArgumentError.new("Setting \"#{@name}\" must be initialized with a class (received #{klass})")
184
+ end
185
+ @klass = klass
186
+ @validator_proc = validator_proc
185
187
  @value = nil
186
188
  @value_is_set = false
187
- @validator_proc = validator_proc
188
- coerced_default = coerce(default)
189
- validate(coerced_default)
190
- @default = coerced_default
189
+
190
+ if strict
191
+ coerced_default = coerce(default)
192
+ validate(coerced_default)
193
+ @default = coerced_default
194
+ else
195
+ @default = default
196
+ end
197
+ end
198
+
199
+ def set(value)
200
+ coerced_value = coerce(value)
201
+ validate(coerced_value)
202
+ @value = coerce(coerced_value)
203
+ @value_is_set = true
204
+ @value
205
+ end
206
+
207
+ def coerce(value)
208
+ raise NotImplementedError.new("Please implement #coerce for #{self.class}")
209
+ end
210
+ end
211
+ ### Specific settings #####
212
+
213
+ class Boolean < Coercible
214
+ def initialize(name, default, strict=true, &validator_proc)
215
+ super(name, Object, default, strict, &validator_proc)
191
216
  end
192
217
 
193
218
  def coerce(value)
@@ -200,25 +225,104 @@ module LogStash
200
225
  raise ArgumentError.new("could not coerce #{value} into a boolean")
201
226
  end
202
227
  end
228
+ end
203
229
 
204
- def set(value)
205
- coerced_value = coerce(value)
206
- validate(coerced_value)
207
- @value = coerce(coerced_value)
208
- @value_is_set = true
209
- @value
230
+ class Numeric < Coercible
231
+ def initialize(name, default=nil, strict=true)
232
+ super(name, ::Numeric, default, strict)
233
+ end
234
+
235
+ def coerce(v)
236
+ return v if v.is_a?(::Numeric)
237
+
238
+ # I hate these "exceptions as control flow" idioms
239
+ # but Ruby's `"a".to_i => 0` makes it hard to do anything else.
240
+ coerced_value = (Integer(v) rescue nil) || (Float(v) rescue nil)
241
+
242
+ if coerced_value.nil?
243
+ raise ArgumentError.new("Failed to coerce value to Numeric. Received #{v} (#{v.class})")
244
+ else
245
+ coerced_value
246
+ end
210
247
  end
211
248
  end
212
249
 
213
- class Numeric < Setting
250
+ class Integer < Coercible
214
251
  def initialize(name, default=nil, strict=true)
215
- super(name, ::Numeric, default, strict)
252
+ super(name, ::Integer, default, strict)
253
+ end
254
+
255
+ def coerce(value)
256
+ return value unless value.is_a?(::String)
257
+
258
+ coerced_value = Integer(value) rescue nil
259
+
260
+ if coerced_value.nil?
261
+ raise ArgumentError.new("Failed to coerce value to Integer. Received #{value} (#{value.class})")
262
+ else
263
+ coerced_value
264
+ end
265
+ end
266
+ end
267
+
268
+ class PositiveInteger < Integer
269
+ def initialize(name, default=nil, strict=true)
270
+ super(name, default, strict) do |v|
271
+ if v > 0
272
+ true
273
+ else
274
+ raise ArgumentError.new("Number must be bigger than 0. Received: #{v}")
275
+ end
276
+ end
216
277
  end
217
278
  end
218
279
 
219
- class Port < Setting
280
+ class Port < Integer
281
+ VALID_PORT_RANGE = 1..65535
282
+
220
283
  def initialize(name, default=nil, strict=true)
221
- super(name, ::Numeric, default, strict) {|value| value >= 1 && value <= 65535 }
284
+ super(name, default, strict) { |value| valid?(value) }
285
+ end
286
+
287
+ def valid?(port)
288
+ VALID_PORT_RANGE.cover?(port)
289
+ end
290
+ end
291
+
292
+ class PortRange < Coercible
293
+ PORT_SEPARATOR = "-"
294
+
295
+ def initialize(name, default=nil, strict=true)
296
+ super(name, ::Range, default, strict=true) { |value| valid?(value) }
297
+ end
298
+
299
+ def valid?(range)
300
+ Port::VALID_PORT_RANGE.first <= range.first && Port::VALID_PORT_RANGE.last >= range.last
301
+ end
302
+
303
+ def coerce(value)
304
+ case value
305
+ when ::Range
306
+ value
307
+ when ::Fixnum
308
+ value..value
309
+ when ::String
310
+ first, last = value.split(PORT_SEPARATOR)
311
+ last = first if last.nil?
312
+ begin
313
+ (Integer(first))..(Integer(last))
314
+ rescue ArgumentError # Trap and reraise a more human error
315
+ raise ArgumentError.new("Could not coerce #{value} into a port range")
316
+ end
317
+ else
318
+ raise ArgumentError.new("Could not coerce #{value} into a port range")
319
+ end
320
+ end
321
+
322
+ def validate(value)
323
+ unless valid?(value)
324
+ raise ArgumentError.new("Invalid value \"#{value}, valid options are within the range of #{Port::VALID_PORT_RANGE.first}-#{Port::VALID_PORT_RANGE.last}")
325
+ end
222
326
  end
223
327
  end
224
328
 
@@ -242,7 +346,7 @@ module LogStash
242
346
  def validate(value)
243
347
  super(value)
244
348
  unless @possible_strings.empty? || @possible_strings.include?(value)
245
- raise ArgumentError.new("invalid value \"#{value}\". Options are: #{@possible_strings.inspect}")
349
+ raise ArgumentError.new("Invalid value \"#{value}\". Options are: #{@possible_strings.inspect}")
246
350
  end
247
351
  end
248
352
  end
@@ -270,7 +374,6 @@ module LogStash
270
374
  end
271
375
  end
272
376
  end
273
-
274
377
  end
275
378
 
276
379
  SETTINGS = Settings.new
@@ -57,6 +57,9 @@ module LogStash; module Util
57
57
  # Note that @infilght_batches as a central mechanism for tracking inflight
58
58
  # batches will fail if we have multiple read clients in the pipeline.
59
59
  @inflight_batches = {}
60
+
61
+ # allow the worker thread to report the execution time of the filter + output
62
+ @inflight_clocks = {}
60
63
  @batch_size = batch_size
61
64
  @wait_for = wait_for
62
65
  end
@@ -89,6 +92,7 @@ module LogStash; module Util
89
92
  batch = ReadBatch.new(@queue, @batch_size, @wait_for)
90
93
  add_starting_metrics(batch)
91
94
  set_current_thread_inflight_batch(batch)
95
+ start_clock
92
96
  batch
93
97
  end
94
98
  end
@@ -100,11 +104,23 @@ module LogStash; module Util
100
104
  def close_batch(batch)
101
105
  @mutex.synchronize do
102
106
  @inflight_batches.delete(Thread.current)
107
+ stop_clock
103
108
  end
104
109
  end
105
110
 
111
+ def start_clock
112
+ @inflight_clocks[Thread.current] = [
113
+ @event_metric.time(:duration_in_millis),
114
+ @pipeline_metric.time(:duration_in_millis)
115
+ ]
116
+ end
117
+
118
+ def stop_clock
119
+ @inflight_clocks[Thread.current].each(&:stop)
120
+ @inflight_clocks.delete(Thread.current)
121
+ end
122
+
106
123
  def add_starting_metrics(batch)
107
- return if @event_metric.nil? || @pipeline_metric.nil?
108
124
  @event_metric.increment(:in, batch.starting_size)
109
125
  @pipeline_metric.increment(:in, batch.starting_size)
110
126
  end
@@ -11,4 +11,4 @@
11
11
  # eventually this file should be in the root logstash lib fir and dependencies in logstash-core should be
12
12
  # fixed.
13
13
 
14
- LOGSTASH_VERSION = "5.0.0.alpha5.snapshot1"
14
+ LOGSTASH_VERSION = "5.0.0.alpha6.snapshot1"
@@ -1,73 +1,84 @@
1
1
  # encoding: utf-8
2
+ require "logstash/api/rack_app"
2
3
  require "puma"
3
4
  require "puma/server"
4
- require "logstash/api/rack_app"
5
+ require "concurrent"
5
6
 
6
- module LogStash
7
+ module LogStash
7
8
  class WebServer
8
9
  extend Forwardable
9
10
 
10
- attr_reader :logger, :status, :config, :options, :cli_options, :runner, :binder, :events, :http_host, :http_port, :http_environment, :agent
11
+ attr_reader :logger, :status, :config, :options, :runner, :binder, :events, :http_host, :http_ports, :http_environment, :agent
11
12
 
12
13
  def_delegator :@runner, :stats
13
14
 
14
15
  DEFAULT_HOST = "127.0.0.1".freeze
15
- DEFAULT_PORT = 9600.freeze
16
+ DEFAULT_PORTS = (9600..9700).freeze
16
17
  DEFAULT_ENVIRONMENT = 'production'.freeze
17
18
 
18
19
  def initialize(logger, agent, options={})
19
20
  @logger = logger
20
21
  @agent = agent
21
22
  @http_host = options[:http_host] || DEFAULT_HOST
22
- @http_port = options[:http_port] || DEFAULT_PORT
23
+ @http_ports = options[:http_ports] || DEFAULT_PORTS
23
24
  @http_environment = options[:http_environment] || DEFAULT_ENVIRONMENT
24
25
  @options = {}
25
- @cli_options = options.merge({ :rackup => ::File.join(::File.dirname(__FILE__), "api", "init.ru"),
26
- :binds => ["tcp://#{http_host}:#{http_port}"],
27
- :debug => logger.debug?,
28
- # Prevent puma from queueing request when not able to properly handling them,
29
- # fixed https://github.com/elastic/logstash/issues/4674. See
30
- # https://github.com/puma/puma/pull/640 for mode internal details in PUMA.
31
- :queue_requests => false
32
- })
33
- @status = nil
26
+ @status = nil
27
+ @running = Concurrent::AtomicBoolean.new(false)
34
28
  end
35
29
 
36
30
  def run
37
- log "=== puma start: #{Time.now} ==="
31
+ logger.debug("Starting puma")
38
32
 
39
33
  stop # Just in case
40
34
 
41
- app = LogStash::Api::RackApp.app(logger, agent, http_environment)
42
- @server = ::Puma::Server.new(app)
43
- @server.add_tcp_listener(http_host, http_port)
35
+ running!
44
36
 
45
- @server.run.join
46
- rescue Errno::EADDRINUSE
47
- message = "Logstash tried to bind to port #{@http_port}, but the port is already in use. You can specify a new port by launching logtash with the --http-port option."
48
- raise Errno::EADDRINUSE.new(message)
37
+ http_ports.each_with_index do |port, idx|
38
+ begin
39
+ if running?
40
+ @port = port
41
+ logger.debug("Trying to start WebServer", :port => @port)
42
+ start_webserver(@port)
43
+ else
44
+ break # we are closing down the server so just get out of the loop
45
+ end
46
+ rescue Errno::EADDRINUSE
47
+ if http_ports.count == 1
48
+ raise Errno::EADDRINUSE.new(I18n.t("logstash.web_api.cant_bind_to_port", :port => http_ports.first))
49
+ elsif idx == http_ports.count-1
50
+ raise Errno::EADDRINUSE.new(I18n.t("logstash.web_api.cant_bind_to_port_in_range", :http_ports => http_ports))
51
+ end
52
+ end
53
+ end
49
54
  end
50
55
 
51
- def log(str)
52
- logger.debug(str)
56
+ def running!
57
+ @running.make_true
53
58
  end
54
59
 
55
- def error(str)
56
- logger.error(str)
60
+ def running?
61
+ @running.value
57
62
  end
58
63
 
59
64
  def address
60
- "#{http_host}:#{http_port}"
65
+ "#{http_host}:#{@port}"
61
66
  end
62
-
63
- # Empty method, this method is required because of the puma usage we make through
64
- # the Single interface, https://github.com/puma/puma/blob/master/lib/puma/single.rb#L82
65
- # for more details. This can always be implemented when we want to keep track of this
66
- # bit of data.
67
- def write_state; end
68
67
 
69
68
  def stop(options={})
69
+ @running.make_false
70
70
  @server.stop(true) if @server
71
71
  end
72
+
73
+ def start_webserver(port)
74
+ app = LogStash::Api::RackApp.app(logger, agent, http_environment)
75
+
76
+ @server = ::Puma::Server.new(app)
77
+ @server.add_tcp_listener(http_host, port)
78
+
79
+ logger.info("Succesfully started Logstash API", :port => @port)
80
+
81
+ @server.run.join
82
+ end
72
83
  end
73
84
  end
@@ -73,12 +73,16 @@ en:
73
73
  non_reloadable_config_register: |-
74
74
  Logstash is not able to start since configuration auto reloading was enabled but the configuration contains plugins that don't support it. Quitting...
75
75
  web_api:
76
+ cant_bind_to_port: |-
77
+ Logstash tried to bind to port %{port}, but the port is already in use. You can specify a new port by launching logtash with the --http-port option."
78
+ cant_bind_to_port_in_range: |-
79
+ Logstash tried to bind to port range %{http_ports}, but all the ports are already in use. You can specify a new port by launching logtash with the --http-port option."
76
80
  hot_threads:
77
81
  title: |-
78
82
  ::: {%{hostname}}
79
83
  Hot threads at %{time}, busiestThreads=%{top_count}:
80
84
  thread_title: |-
81
- %{percent_of_cpu_time} % of cpu usage by %{thread_state} thread named '%{thread_name}'
85
+ %{percent_of_cpu_time} % of cpu usage, state: %{thread_state}, thread name: '%{thread_name}'
82
86
  runner:
83
87
  short-help: |-
84
88
  usage:
@@ -17,7 +17,7 @@ Gem::Specification.new do |gem|
17
17
  gem.require_paths = ["lib"]
18
18
  gem.version = LOGSTASH_CORE_VERSION
19
19
 
20
- gem.add_runtime_dependency "logstash-core-event-java", "5.0.0.alpha5.snapshot1"
20
+ gem.add_runtime_dependency "logstash-core-event-java", "5.0.0.alpha6.snapshot1"
21
21
 
22
22
  gem.add_runtime_dependency "cabin", "~> 0.8.0" #(Apache 2.0 license)
23
23
  gem.add_runtime_dependency "pry", "~> 0.10.1" #(Ruby license)
@@ -46,7 +46,7 @@ Gem::Specification.new do |gem|
46
46
 
47
47
  if RUBY_PLATFORM == 'java'
48
48
  gem.platform = RUBY_PLATFORM
49
- gem.add_runtime_dependency "jrjackson", "~> 0.3.7" #(Apache 2.0 license)
49
+ gem.add_runtime_dependency "jrjackson", "~> 0.4.0" #(Apache 2.0 license)
50
50
  else
51
51
  gem.add_runtime_dependency "oj" #(MIT-style license)
52
52
  end
@@ -39,30 +39,81 @@ describe LogStash::Api::Modules::Node do
39
39
  end
40
40
 
41
41
  context "when asking for human output" do
42
+ [
43
+ "/hot_threads?human",
44
+ "/hot_threads?human=true",
45
+ "/hot_threads?human=1",
46
+ "/hot_threads?human=t",
47
+ ].each do |path|
48
+
49
+ before(:all) do
50
+ do_request { get path }
51
+ end
52
+
53
+ let(:payload) { last_response.body }
54
+
55
+ it "should return a text/plain content type" do
56
+ expect(last_response.content_type).to eq("text/plain;charset=utf-8")
57
+ end
58
+
59
+ it "should return a plain text payload" do
60
+ expect{ JSON.parse(payload) }.to raise_error
61
+ end
62
+ end
63
+ end
42
64
 
65
+ context "When asking for human output and threads count" do
43
66
  before(:all) do
44
- do_request { get "/hot_threads?human" }
67
+ # Make sure we have enough threads for this to work.
68
+ @threads = []
69
+ 5.times { @threads << Thread.new { loop {} } }
70
+
71
+ do_request { get "/hot_threads?human=t&threads=2"}
72
+ end
73
+
74
+ after(:all) do
75
+ @threads.each { |t| t.kill } rescue nil
45
76
  end
46
77
 
47
78
  let(:payload) { last_response.body }
48
79
 
49
- it "should return a text/plain content type" do
50
- expect(last_response.content_type).to eq("text/plain;charset=utf-8")
80
+ it "should return information for <= # requested threads" do
81
+ expect(payload.scan(/thread name/).size).to eq(2)
51
82
  end
83
+ end
52
84
 
53
- it "should return a plain text payload" do
54
- expect{ JSON.parse(payload) }.to raise_error
85
+ context "when not asking for human output" do
86
+ [
87
+ "/hot_threads?human=false",
88
+ "/hot_threads?human=0",
89
+ "/hot_threads?human=f",
90
+ ].each do |path|
91
+ before(:all) do
92
+ do_request { get path }
93
+ end
94
+
95
+ it "should return a json payload content type" do
96
+ expect(last_response.content_type).to eq("application/json")
97
+ end
98
+
99
+ let(:payload) { last_response.body }
100
+
101
+ it "should return a json payload" do
102
+ expect{ JSON.parse(payload) }.not_to raise_error
103
+ end
55
104
  end
56
105
  end
57
106
 
58
107
  describe "Generic JSON testing" do
59
108
  extend ResourceDSLMethods
60
-
109
+
61
110
  root_structure = {
62
111
  "pipeline" => {
63
112
  "workers" => Numeric,
64
113
  "batch_size" => Numeric,
65
- "batch_delay" => Numeric
114
+ "batch_delay" => Numeric,
115
+ "config_reload_automatic" => Boolean,
116
+ "config_reload_interval" => Numeric
66
117
  },
67
118
  "os" => {
68
119
  "name" => String,
@@ -82,7 +133,8 @@ describe LogStash::Api::Modules::Node do
82
133
  "heap_max_in_bytes" => Numeric,
83
134
  "non_heap_init_in_bytes" => Numeric,
84
135
  "non_heap_max_in_bytes" => Numeric
85
- }
136
+ },
137
+ "gc_collectors" => Array
86
138
  },
87
139
  "hot_threads"=> {
88
140
  "time" => String,
@@ -90,8 +142,8 @@ describe LogStash::Api::Modules::Node do
90
142
  "threads" => Array
91
143
  }
92
144
  }
93
-
145
+
94
146
  test_api_and_resources(root_structure, :exclude_from_root => ["hot_threads"])
95
- end
147
+ end
96
148
  end
97
149
  end