logstash-core 2.0.1.snapshot1-java → 2.1.0-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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