logstash-core 2.0.1.snapshot1-java → 2.1.0-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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dfcc3785015f1606582277fcaa83f916d71e1fc6
4
- data.tar.gz: 62a9a1dce8d852d7fd8c7baa6c9467cb4bc948b5
3
+ metadata.gz: 79705663d762e3aa65d23f6c98c0fb39923546ef
4
+ data.tar.gz: 66c3fc910d9d9290189c55843bdfbf7ed4d3ea65
5
5
  SHA512:
6
- metadata.gz: c7f4ebb46dbece030b609f4151ae1b989227c177f659dd1a26fcafff23ce0848eaafaf938df47d6db4a3a222713704bb7d842f2ec7c7a064dce0422acf22b4e6
7
- data.tar.gz: 29dcfff2b13d39a41568368b71b5711f0a489643fc44db8aff2477db1bd86a53e997b52c66461370c6ea3527cd6343bca80f9b72adcc90b6f7321d2bcb920e37
6
+ metadata.gz: 455434efcd9788628c5350cb6f593eaad237a10269b68627d32e084aaa284c9a76b41d305b71ec4e0ee4d34e09f51a8dcaa2e03985f2be8f8e03ac0bef7fc3b0
7
+ data.tar.gz: b64af5a363fcd0096d06f4163b82595735acb58a3a958b39c6258a8cb08eb415342be4206f7d6a5e7765ba8c7a1cee2339f4eae8543e6e851c2d0d92c77c27a6
@@ -23,7 +23,7 @@ class LogStash::Agent < Clamp::Command
23
23
  option ["-w", "--filterworkers"], "COUNT",
24
24
  I18n.t("logstash.agent.flag.filterworkers"),
25
25
  :attribute_name => :filter_workers,
26
- :default => LogStash::Config::CpuCoreStrategy.fifty_percent, &:to_i
26
+ :default => 0, &:to_i
27
27
 
28
28
  option ["-l", "--log"], "FILE",
29
29
  I18n.t("logstash.agent.flag.log"),
@@ -50,6 +50,11 @@ class LogStash::Agent < Clamp::Command
50
50
  I18n.t("logstash.agent.flag.configtest"),
51
51
  :attribute_name => :config_test
52
52
 
53
+ option "--[no-]allow-unsafe-shutdown", :flag,
54
+ I18n.t("logstash.agent.flag.unsafe_shutdown"),
55
+ :attribute_name => :unsafe_shutdown,
56
+ :default => false
57
+
53
58
  # Emit a warning message.
54
59
  def warn(message)
55
60
  # For now, all warnings are fatal.
@@ -75,6 +80,9 @@ class LogStash::Agent < Clamp::Command
75
80
  require "logstash/plugin"
76
81
  @logger = Cabin::Channel.get(LogStash)
77
82
 
83
+ LogStash::ShutdownController.unsafe_shutdown = unsafe_shutdown?
84
+ LogStash::ShutdownController.logger = @logger
85
+
78
86
  if version?
79
87
  show_version
80
88
  return 0
@@ -143,7 +151,7 @@ class LogStash::Agent < Clamp::Command
143
151
  configure_logging(log_file)
144
152
  end
145
153
 
146
- pipeline.configure("filter-workers", filter_workers)
154
+ pipeline.configure("filter-workers", filter_workers) if filter_workers > 0
147
155
 
148
156
  # Stop now if we are only asking for a config test.
149
157
  if config_test?
@@ -176,8 +184,7 @@ class LogStash::Agent < Clamp::Command
176
184
 
177
185
  def shutdown(pipeline)
178
186
  pipeline.shutdown do
179
- InflightEventsReporter.logger = @logger
180
- InflightEventsReporter.start(pipeline.input_to_filter, pipeline.filter_to_output, pipeline.outputs)
187
+ ::LogStash::ShutdownController.start(pipeline)
181
188
  end
182
189
  end
183
190
 
@@ -312,19 +319,27 @@ class LogStash::Agent < Clamp::Command
312
319
  Dir.glob(path).sort.each do |file|
313
320
  next unless File.file?(file)
314
321
  if file.match(/~$/)
315
- @logger.debug("NOT reading config file because it is a temp file", :file => file)
322
+ @logger.debug("NOT reading config file because it is a temp file", :config_file => file)
316
323
  next
317
324
  end
318
- @logger.debug("Reading config file", :file => file)
325
+ @logger.debug("Reading config file", :config_file => file)
319
326
  cfg = File.read(file)
320
327
  if !cfg.ascii_only? && !cfg.valid_encoding?
321
328
  encoding_issue_files << file
322
329
  end
323
330
  config << cfg + "\n"
331
+ if config_test?
332
+ @logger.debug? && @logger.debug("\nThe following is the content of a file", :config_file => file.to_s)
333
+ @logger.debug? && @logger.debug("\n" + cfg + "\n\n")
334
+ end
324
335
  end
325
336
  if (encoding_issue_files.any?)
326
337
  fail("The following config files contains non-ascii characters but are not UTF-8 encoded #{encoding_issue_files}")
327
338
  end
339
+ if config_test?
340
+ @logger.debug? && @logger.debug("\nThe following is the merged configuration")
341
+ @logger.debug? && @logger.debug("\n" + config + "\n\n")
342
+ end
328
343
  return config
329
344
  end # def load_config
330
345
 
@@ -35,12 +35,6 @@ module LogStash::Config::Mixin
35
35
  attr_accessor :config
36
36
  attr_accessor :original_params
37
37
 
38
- CONFIGSORT = {
39
- Symbol => 0,
40
- String => 0,
41
- Regexp => 100,
42
- }
43
-
44
38
  PLUGIN_VERSION_1_0_0 = LogStash::Util::PluginVersion.new(1, 0, 0)
45
39
  PLUGIN_VERSION_0_9_0 = LogStash::Util::PluginVersion.new(0, 9, 0)
46
40
 
@@ -106,7 +106,7 @@ class LogStash::Event
106
106
 
107
107
  public
108
108
  def to_s
109
- self.sprintf("#{timestamp.to_iso8601} %{host} %{message}")
109
+ "#{timestamp.to_iso8601} #{self.sprintf("%{host} %{message}")}"
110
110
  end # def to_s
111
111
 
112
112
  public
@@ -117,8 +117,6 @@ class LogStash::Filters::Base < LogStash::Plugin
117
117
  # Optional.
118
118
  config :periodic_flush, :validate => :boolean, :default => false
119
119
 
120
- RESERVED = ["type", "tags", "exclude_tags", "include_fields", "exclude_fields", "add_tag", "remove_tag", "add_field", "remove_field", "include_any", "exclude_any"]
121
-
122
120
  public
123
121
  def initialize(params)
124
122
  super
@@ -145,6 +143,7 @@ class LogStash::Filters::Base < LogStash::Plugin
145
143
  # @return [Array<LogStash::Event] filtered events and any new events generated by the filter
146
144
  public
147
145
  def multi_filter(events)
146
+ LogStash::Util.set_thread_plugin(self)
148
147
  result = []
149
148
  events.each do |event|
150
149
  unless event.cancelled?
@@ -89,11 +89,6 @@ class LogStash::Inputs::Base < LogStash::Plugin
89
89
  @stop_called.value
90
90
  end
91
91
 
92
- protected
93
- def to_event(raw, source)
94
- raise LogStash::ThisMethodWasRemoved("LogStash::Inputs::Base#to_event - you should use codecs now instead of to_event. Not sure what this means? Get help on https://discuss.elastic.co/c/logstash")
95
- end # def to_event
96
-
97
92
  protected
98
93
  def decorate(event)
99
94
  # Only set 'type' if not already set. This is backwards-compatible behavior
@@ -1,11 +1,9 @@
1
1
  # encoding: utf-8
2
- require "cgi"
3
2
  require "logstash/event"
4
3
  require "logstash/logging"
5
4
  require "logstash/plugin"
6
5
  require "logstash/namespace"
7
6
  require "logstash/config/mixin"
8
- require "uri"
9
7
 
10
8
  class LogStash::Outputs::Base < LogStash::Plugin
11
9
  include LogStash::Config::Mixin
@@ -25,7 +23,7 @@ class LogStash::Outputs::Base < LogStash::Plugin
25
23
  # Note that this setting may not be useful for all outputs.
26
24
  config :workers, :validate => :number, :default => 1
27
25
 
28
- attr_reader :worker_plugins, :worker_queue
26
+ attr_reader :worker_plugins, :worker_queue, :worker_threads
29
27
 
30
28
  public
31
29
  def workers_not_supported(message=nil)
@@ -58,13 +56,15 @@ class LogStash::Outputs::Base < LogStash::Plugin
58
56
  def worker_setup
59
57
  if @workers == 1
60
58
  @worker_plugins = [self]
59
+ @worker_threads = []
61
60
  else
62
61
  define_singleton_method(:handle, method(:handle_worker))
63
62
  @worker_queue = SizedQueue.new(20)
64
63
  @worker_plugins = @workers.times.map { self.class.new(@original_params.merge("workers" => 1)) }
65
- @worker_plugins.map.with_index do |plugin, i|
64
+ @worker_threads = @worker_plugins.map.with_index do |plugin, i|
66
65
  Thread.new(original_params, @worker_queue) do |params, queue|
67
- LogStash::Util::set_thread_name(">#{self.class.config_name}.#{i}")
66
+ LogStash::Util.set_thread_name(">#{self.class.config_name}.#{i}")
67
+ LogStash::Util.set_thread_plugin(self)
68
68
  plugin.register
69
69
  while true
70
70
  event = queue.pop
@@ -77,10 +77,12 @@ class LogStash::Outputs::Base < LogStash::Plugin
77
77
 
78
78
  public
79
79
  def handle(event)
80
+ LogStash::Util.set_thread_plugin(self)
80
81
  receive(event)
81
82
  end # def handle
82
83
 
83
84
  def handle_worker(event)
85
+ LogStash::Util.set_thread_plugin(self)
84
86
  @worker_queue.push(event)
85
87
  end
86
88
 
@@ -9,11 +9,11 @@ require "logstash/config/file"
9
9
  require "logstash/filters/base"
10
10
  require "logstash/inputs/base"
11
11
  require "logstash/outputs/base"
12
- require "logstash/util/reporter"
13
12
  require "logstash/config/cpu_core_strategy"
14
13
  require "logstash/util/defaults_printer"
14
+ require "logstash/shutdown_controller"
15
15
 
16
- class LogStash::Pipeline
16
+ module LogStash; class Pipeline
17
17
  attr_reader :inputs, :filters, :outputs, :input_to_filter, :filter_to_output
18
18
 
19
19
  def initialize(configstr)
@@ -25,6 +25,7 @@ class LogStash::Pipeline
25
25
 
26
26
  grammar = LogStashConfigParser.new
27
27
  @config = grammar.parse(configstr)
28
+
28
29
  if @config.nil?
29
30
  raise LogStash::ConfigurationError, grammar.failure_reason
30
31
  end
@@ -46,7 +47,7 @@ class LogStash::Pipeline
46
47
  @filter_to_output = filters? ? SizedQueue.new(20) : @input_to_filter
47
48
 
48
49
  @settings = {
49
- "filter-workers" => LogStash::Config::CpuCoreStrategy.fifty_percent
50
+ "default-filter-workers" => LogStash::Config::CpuCoreStrategy.fifty_percent
50
51
  }
51
52
 
52
53
  # @ready requires thread safety since it is typically polled from outside the pipeline thread
@@ -59,14 +60,32 @@ class LogStash::Pipeline
59
60
  end
60
61
 
61
62
  def configure(setting, value)
62
- if setting == "filter-workers" && value > 1
63
- # Abort if we have any filters that aren't threadsafe
64
- plugins = @filters.select { |f| !f.threadsafe? }.collect { |f| f.class.config_name }
65
- if !plugins.size.zero?
66
- raise LogStash::ConfigurationError, "Cannot use more than 1 filter worker because the following plugins don't work with more than one worker: #{plugins.join(", ")}"
63
+ @settings[setting] = value
64
+ end
65
+
66
+ def safe_filter_worker_count
67
+ default = @settings["default-filter-workers"]
68
+ thread_count = @settings["filter-workers"] #override from args "-w 8" or config
69
+ safe_filters, unsafe_filters = @filters.partition(&:threadsafe?)
70
+ if unsafe_filters.any?
71
+ plugins = unsafe_filters.collect { |f| f.class.config_name }
72
+ case thread_count
73
+ when nil
74
+ # user did not specify a worker thread count
75
+ # warn if the default is multiple
76
+ @logger.warn("Defaulting filter worker threads to 1 because there are some filters that might not work with multiple worker threads",
77
+ :count_was => default, :filters => plugins) if default > 1
78
+ 1 # can't allow the default value to propagate if there are unsafe filters
79
+ when 0, 1
80
+ 1
81
+ else
82
+ @logger.warn("Warning: Manual override - there are filters that might not work with multiple worker threads",
83
+ :worker_threads => thread_count, :filters => plugins)
84
+ thread_count # allow user to force this even if there are unsafe filters
67
85
  end
86
+ else
87
+ thread_count || default
68
88
  end
69
- @settings[setting] = value
70
89
  end
71
90
 
72
91
  def filters?
@@ -149,9 +168,14 @@ class LogStash::Pipeline
149
168
 
150
169
  def start_filters
151
170
  @filters.each(&:register)
152
- to_start = @settings["filter-workers"]
153
- @filter_threads = to_start.times.collect do
154
- Thread.new { filterworker }
171
+ # dynamically get thread count based on filter threadsafety
172
+ # moved this test to here to allow for future config reloading
173
+ to_start = safe_filter_worker_count
174
+ @filter_threads = to_start.times.collect do |i|
175
+ Thread.new do
176
+ LogStash::Util.set_thread_name("|filterworker.#{i}")
177
+ filterworker
178
+ end
155
179
  end
156
180
  actually_started = @filter_threads.select(&:alive?).size
157
181
  msg = "Worker threads expected: #{to_start}, worker threads started: #{actually_started}"
@@ -175,7 +199,8 @@ class LogStash::Pipeline
175
199
  end
176
200
 
177
201
  def inputworker(plugin)
178
- LogStash::Util::set_thread_name("<#{plugin.class.config_name}")
202
+ LogStash::Util.set_thread_name("<#{plugin.class.config_name}")
203
+ LogStash::Util.set_thread_plugin(plugin)
179
204
  begin
180
205
  plugin.run(@input_to_filter)
181
206
  rescue => e
@@ -208,7 +233,6 @@ class LogStash::Pipeline
208
233
  end # def inputworker
209
234
 
210
235
  def filterworker
211
- LogStash::Util.set_thread_name("|worker")
212
236
  begin
213
237
  while true
214
238
  event = @input_to_filter.pop
@@ -250,6 +274,7 @@ class LogStash::Pipeline
250
274
  event = @filter_to_output.pop
251
275
  break if event == LogStash::SHUTDOWN
252
276
  output_func(event)
277
+ LogStash::Util.set_thread_plugin(nil)
253
278
  end
254
279
  ensure
255
280
  @outputs.each do |output|
@@ -309,4 +334,49 @@ class LogStash::Pipeline
309
334
  end
310
335
  end # flush_filters_to_output!
311
336
 
312
- end # class Pipeline
337
+ def inflight_count
338
+ data = {}
339
+ total = 0
340
+
341
+ input_to_filter = @input_to_filter.size
342
+ total += input_to_filter
343
+ filter_to_output = @filter_to_output.size
344
+ total += filter_to_output
345
+
346
+ data["input_to_filter"] = input_to_filter if input_to_filter > 0
347
+ data["filter_to_output"] = filter_to_output if filter_to_output > 0
348
+
349
+ output_worker_queues = []
350
+ @outputs.each do |output|
351
+ next unless output.worker_queue && output.worker_queue.size > 0
352
+ plugin_info = output.debug_info
353
+ size = output.worker_queue.size
354
+ total += size
355
+ plugin_info << size
356
+ output_worker_queues << plugin_info
357
+ end
358
+ data["output_worker_queues"] = output_worker_queues unless output_worker_queues.empty?
359
+ data["total"] = total
360
+ data
361
+ end
362
+
363
+ def stalling_threads
364
+ plugin_threads
365
+ .reject {|t| t["blocked_on"] } # known begnin blocking statuses
366
+ .each {|t| t.delete("backtrace") }
367
+ .each {|t| t.delete("blocked_on") }
368
+ .each {|t| t.delete("status") }
369
+ end
370
+
371
+ def plugin_threads
372
+ input_threads = @input_threads.select {|t| t.alive? }.map {|t| thread_info(t) }
373
+ filter_threads = @filter_threads.select {|t| t.alive? }.map {|t| thread_info(t) }
374
+ output_threads = @output_threads.select {|t| t.alive? }.map {|t| thread_info(t) }
375
+ output_worker_threads = @outputs.flat_map {|output| output.worker_threads }.map {|t| thread_info(t) }
376
+ input_threads + filter_threads + output_threads + output_worker_threads
377
+ end
378
+
379
+ def thread_info(thread)
380
+ LogStash::Util.thread_info(thread)
381
+ end
382
+ end; end
@@ -59,6 +59,11 @@ class LogStash::Plugin
59
59
  end
60
60
  end
61
61
 
62
+ public
63
+ def debug_info
64
+ [self.class.to_s, original_params]
65
+ end
66
+
62
67
  # Look up a plugin by type and name.
63
68
  public
64
69
  def self.lookup(type, name)
@@ -0,0 +1,127 @@
1
+ # encoding: utf-8
2
+
3
+ module LogStash
4
+ class ShutdownController
5
+
6
+ CHECK_EVERY = 1 # second
7
+ REPORT_EVERY = 5 # checks
8
+ ABORT_AFTER = 3 # stalled reports
9
+
10
+ attr_reader :cycle_period, :report_every, :abort_threshold
11
+
12
+ def initialize(pipeline, cycle_period=CHECK_EVERY, report_every=REPORT_EVERY, abort_threshold=ABORT_AFTER)
13
+ @pipeline = pipeline
14
+ @cycle_period = cycle_period
15
+ @report_every = report_every
16
+ @abort_threshold = abort_threshold
17
+ @reports = []
18
+ end
19
+
20
+ def self.unsafe_shutdown=(boolean)
21
+ @unsafe_shutdown = boolean
22
+ end
23
+
24
+ def self.unsafe_shutdown?
25
+ @unsafe_shutdown
26
+ end
27
+
28
+ def self.logger=(logger)
29
+ @logger = logger
30
+ end
31
+
32
+ def self.logger
33
+ @logger ||= Cabin::Channel.get(LogStash)
34
+ end
35
+
36
+ def self.start(pipeline, cycle_period=CHECK_EVERY, report_every=REPORT_EVERY, abort_threshold=ABORT_AFTER)
37
+ controller = self.new(pipeline, cycle_period, report_every, abort_threshold)
38
+ Thread.new(controller) { |controller| controller.start }
39
+ end
40
+
41
+ def logger
42
+ self.class.logger
43
+ end
44
+
45
+ def start
46
+ sleep(@cycle_period)
47
+ cycle_number = 0
48
+ stalled_count = 0
49
+ Stud.interval(@cycle_period) do
50
+ @reports << Report.from_pipeline(@pipeline)
51
+ @reports.delete_at(0) if @reports.size > @report_every # expire old report
52
+ if cycle_number == (@report_every - 1) # it's report time!
53
+ logger.warn(@reports.last.to_hash)
54
+
55
+ if shutdown_stalled?
56
+ logger.error("The shutdown process appears to be stalled due to busy or blocked plugins. Check the logs for more information.") if stalled_count == 0
57
+ stalled_count += 1
58
+
59
+ if self.class.unsafe_shutdown? && @abort_threshold == stalled_count
60
+ logger.fatal("Forcefully quitting logstash..")
61
+ force_exit()
62
+ break
63
+ end
64
+ else
65
+ stalled_count = 0
66
+ end
67
+ end
68
+ cycle_number = (cycle_number + 1) % @report_every
69
+ end
70
+ end
71
+
72
+ # A pipeline shutdown is stalled if
73
+ # * at least REPORT_EVERY reports have been created
74
+ # * the inflight event count is in monotonically increasing
75
+ # * there are worker threads running which aren't blocked on SizedQueue pop/push
76
+ # * the stalled thread list is constant in the previous REPORT_EVERY reports
77
+ def shutdown_stalled?
78
+ return false unless @reports.size == @report_every #
79
+ # is stalled if inflight count is either constant or increasing
80
+ stalled_event_count = @reports.each_cons(2).all? do |prev_report, next_report|
81
+ prev_report.inflight_count["total"] <= next_report.inflight_count["total"]
82
+ end
83
+ if stalled_event_count
84
+ @reports.each_cons(2).all? do |prev_report, next_report|
85
+ prev_report.stalling_threads == next_report.stalling_threads
86
+ end
87
+ else
88
+ false
89
+ end
90
+ end
91
+
92
+ def force_exit
93
+ exit(-1)
94
+ end
95
+ end
96
+
97
+ class Report
98
+
99
+ attr_reader :inflight_count, :stalling_threads
100
+
101
+ def self.from_pipeline(pipeline)
102
+ new(pipeline.inflight_count, pipeline.stalling_threads)
103
+ end
104
+
105
+ def initialize(inflight_count, stalling_threads)
106
+ @inflight_count = inflight_count
107
+ @stalling_threads = format_threads_by_plugin(stalling_threads)
108
+ end
109
+
110
+ def to_hash
111
+ {
112
+ "INFLIGHT_EVENT_COUNT" => @inflight_count,
113
+ "STALLING_THREADS" => @stalling_threads
114
+ }
115
+ end
116
+
117
+ def format_threads_by_plugin(stalling_threads)
118
+ stalled_plugins = {}
119
+ stalling_threads.each do |thr|
120
+ key = (thr.delete("plugin") || "other")
121
+ stalled_plugins[key] ||= []
122
+ stalled_plugins[key] << thr
123
+ end
124
+ stalled_plugins
125
+ end
126
+ end
127
+ end