logstash-core 5.5.3-java → 5.6.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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/lib/logstash-core/logstash-core.jar +0 -0
  3. data/lib/logstash-core/version.rb +1 -1
  4. data/lib/logstash/api/commands/node.rb +2 -2
  5. data/lib/logstash/api/commands/stats.rb +2 -2
  6. data/lib/logstash/config/config_ast.rb +24 -1
  7. data/lib/logstash/config/modules_common.rb +47 -15
  8. data/lib/logstash/config/source/modules.rb +55 -0
  9. data/lib/logstash/config/string_escape.rb +27 -0
  10. data/lib/logstash/elasticsearch_client.rb +24 -2
  11. data/lib/logstash/environment.rb +2 -0
  12. data/lib/logstash/filter_delegator.rb +9 -6
  13. data/lib/logstash/instrument/collector.rb +7 -5
  14. data/lib/logstash/instrument/metric_store.rb +11 -11
  15. data/lib/logstash/instrument/namespaced_metric.rb +4 -0
  16. data/lib/logstash/instrument/namespaced_null_metric.rb +4 -0
  17. data/lib/logstash/instrument/null_metric.rb +10 -0
  18. data/lib/logstash/instrument/periodic_poller/dlq.rb +19 -0
  19. data/lib/logstash/instrument/periodic_pollers.rb +3 -1
  20. data/lib/logstash/instrument/wrapped_write_client.rb +33 -24
  21. data/lib/logstash/logging/logger.rb +26 -19
  22. data/lib/logstash/modules/{importer.rb → elasticsearch_importer.rb} +3 -3
  23. data/lib/logstash/modules/kibana_base.rb +24 -0
  24. data/lib/logstash/modules/kibana_client.rb +124 -0
  25. data/lib/logstash/modules/kibana_config.rb +29 -28
  26. data/lib/logstash/modules/kibana_dashboards.rb +36 -0
  27. data/lib/logstash/modules/kibana_importer.rb +17 -0
  28. data/lib/logstash/modules/kibana_settings.rb +40 -0
  29. data/lib/logstash/modules/logstash_config.rb +89 -17
  30. data/lib/logstash/modules/resource_base.rb +6 -5
  31. data/lib/logstash/modules/scaffold.rb +11 -3
  32. data/lib/logstash/modules/settings_merger.rb +23 -0
  33. data/lib/logstash/modules/util.rb +17 -0
  34. data/lib/logstash/output_delegator.rb +7 -5
  35. data/lib/logstash/pipeline.rb +34 -2
  36. data/lib/logstash/runner.rb +8 -13
  37. data/lib/logstash/settings.rb +20 -1
  38. data/lib/logstash/util/wrapped_acked_queue.rb +5 -24
  39. data/lib/logstash/util/wrapped_synchronous_queue.rb +14 -24
  40. data/lib/logstash/version.rb +1 -1
  41. data/locales/en.yml +11 -4
  42. data/spec/logstash/agent_spec.rb +19 -6
  43. data/spec/logstash/api/modules/node_spec.rb +2 -1
  44. data/spec/logstash/config/config_ast_spec.rb +47 -8
  45. data/spec/logstash/config/string_escape_spec.rb +24 -0
  46. data/spec/logstash/event_spec.rb +9 -0
  47. data/spec/logstash/filter_delegator_spec.rb +21 -7
  48. data/spec/logstash/instrument/periodic_poller/dlq_spec.rb +17 -0
  49. data/spec/logstash/instrument/periodic_poller/jvm_spec.rb +1 -1
  50. data/spec/logstash/legacy_ruby_event_spec.rb +4 -4
  51. data/spec/logstash/modules/logstash_config_spec.rb +56 -0
  52. data/spec/logstash/modules/scaffold_spec.rb +234 -0
  53. data/spec/logstash/output_delegator_spec.rb +15 -5
  54. data/spec/logstash/pipeline_spec.rb +76 -26
  55. data/spec/logstash/runner_spec.rb +46 -25
  56. data/spec/logstash/settings/splittable_string_array_spec.rb +51 -0
  57. data/spec/logstash/util/wrapped_synchronous_queue_spec.rb +0 -22
  58. metadata +22 -4
  59. data/lib/logstash/modules/kibana_base_resource.rb +0 -10
  60. data/lib/logstash/program.rb +0 -14
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+ require "logstash/namespace"
3
+ require "logstash/logging"
4
+ require_relative "kibana_base"
5
+
6
+ module LogStash module Modules class KibanaDashboards < KibanaBase
7
+ include LogStash::Util::Loggable
8
+
9
+ attr_reader :import_path, :content
10
+
11
+ # content is a list of kibana file resources
12
+ def initialize(import_path, content)
13
+ @import_path, @content = import_path, content
14
+ end
15
+
16
+ def import(client)
17
+ # e.g. curl "http://localhost:5601/api/kibana/dashboards/import"
18
+ # extract and prepare all objects
19
+ objects = []
20
+ content.each do |resource|
21
+ hash = {
22
+ "id" => resource.content_id,
23
+ "type" => resource.content_type,
24
+ "version" => 1,
25
+ "attributes" => resource.content_as_object
26
+ }
27
+ objects << hash
28
+ end
29
+ body = {:version => client.version, :objects => objects}
30
+ response = client.post(import_path, body)
31
+ if response.failed?
32
+ logger.error("Attempted POST failed", :url_path => import_path, :response => response.body)
33
+ end
34
+ response
35
+ end
36
+ end end end
@@ -0,0 +1,17 @@
1
+ # encoding: utf-8
2
+ require "logstash/namespace"
3
+ require "logstash/logging"
4
+
5
+ module LogStash module Modules class KibanaImporter
6
+ include LogStash::Util::Loggable
7
+
8
+ def initialize(client)
9
+ @client = client
10
+ end
11
+
12
+ def put(via_kibana)
13
+ path = via_kibana.import_path
14
+ logger.debug("Attempting POST", :url_path => path, :content => via_kibana.content)
15
+ via_kibana.import(@client)
16
+ end
17
+ end end end
@@ -0,0 +1,40 @@
1
+ # encoding: utf-8
2
+ require "logstash/namespace"
3
+ require "logstash/logging"
4
+ require_relative "kibana_base"
5
+
6
+ module LogStash module Modules class KibanaSettings < KibanaBase
7
+ include LogStash::Util::Loggable
8
+
9
+ class Setting
10
+ attr_reader :name, :value
11
+ def initialize(name, value)
12
+ @name, @value = name, value
13
+ end
14
+ end
15
+
16
+ attr_reader :import_path, :content
17
+
18
+ # content is an array of Setting required for this module
19
+ def initialize(import_path, content)
20
+ @import_path, @content = import_path, content
21
+ end
22
+
23
+ def import(client)
24
+ # e.g. curl "http://localhost:5601/api/kibana/settings"
25
+ # 6.0.0-alpha3 -> {"settings":{"buildNum":{"userValue":15613},"defaultIndex":{"userValue":"arcsight-*"}}}
26
+ # 5.4 -> {"settings":{"defaultIndex":{"userValue":"cef-*"},"metrics:max_buckets":{"userValue":"600000"}}}
27
+ # array of Setting objects
28
+ # The POST api body { "changes": { "defaultIndex": "arcsight-*", "metrics:max_buckets": "400" } }
29
+ settings = {}
30
+ content.each do |setting|
31
+ settings[setting.name] = "#{setting.value}"
32
+ end
33
+ body = {"changes" => settings}
34
+ response = client.post(import_path, body)
35
+ if response.failed?
36
+ logger.error("Attempted POST failed", :url_path => import_path, :response => response.body)
37
+ end
38
+ response
39
+ end
40
+ end end end
@@ -1,9 +1,9 @@
1
1
  # encoding: utf-8
2
2
  require "logstash/namespace"
3
3
  require_relative "file_reader"
4
+ require "logstash/settings"
4
5
 
5
6
  module LogStash module Modules class LogStashConfig
6
-
7
7
  # We name it `modul` here because `module` has meaning in Ruby.
8
8
  def initialize(modul, settings)
9
9
  @directory = ::File.join(modul.directory, "logstash")
@@ -15,27 +15,72 @@ module LogStash module Modules class LogStashConfig
15
15
  ::File.join(@directory, "#{@name}.conf.erb")
16
16
  end
17
17
 
18
- def setting(value, default)
19
- @settings.fetch(value, default)
18
+ def configured_inputs(default = [], aliases = {})
19
+ name = "var.inputs"
20
+ values = get_setting(LogStash::Setting::SplittableStringArray.new(name, String, default))
21
+
22
+ aliases.each { |k,v| values << v if values.include?(k) }
23
+ aliases.invert.each { |k,v| values << v if values.include?(k) }
24
+ values.flatten.uniq
25
+ end
26
+
27
+ def alias_settings_keys!(aliases)
28
+ aliased_settings = alias_matching_keys(aliases, @settings)
29
+ @settings = alias_matching_keys(aliases.invert, aliased_settings)
30
+ end
31
+
32
+ def array_to_string(array)
33
+ "[#{array.collect { |i| "'#{i}'" }.join(", ")}]"
34
+ end
35
+
36
+ def csv_string(array)
37
+ "'#{array.join(',')}'"
38
+ end
39
+
40
+ def get_setting(setting_class)
41
+ raw_value = @settings[setting_class.name]
42
+ # If we dont check for NIL, the Settings class will try to coerce the value
43
+ # and most of the it will fails when a NIL value is explicitely set.
44
+ # This will be fixed once we wrap the plugins settings into a Settings class
45
+ setting_class.set(raw_value) unless raw_value.nil?
46
+ setting_class.value
47
+ end
48
+
49
+ def setting(name, default)
50
+ # by default we use the more permissive setting which is a `NullableString`
51
+ # This is fine because the end format of the logstash configuration is a string representation
52
+ # of the pipeline. There is a good reason why I think we should use the settings classes, we
53
+ # can `preprocess` a template and generate a configuration from the defined settings
54
+ # validate the values and replace them in the template.
55
+ case default
56
+ when String
57
+ get_setting(LogStash::Setting::NullableString.new(name, default.to_s))
58
+ when Numeric
59
+ get_setting(LogStash::Setting::Numeric.new(name, default))
60
+ else
61
+ get_setting(LogStash::Setting::NullableString.new(name, default.to_s))
62
+ end
20
63
  end
21
64
 
22
65
  def elasticsearch_output_config(type_string = nil)
23
- hosts = setting("var.output.elasticsearch.hosts", "localhost:9200").split(',').map do |s|
24
- '"' + s.strip + '"'
25
- end.join(',')
26
- index = "#{@name}-#{setting("var.output.elasticsearch.index_suffix", "%{+YYYY.MM.dd}")}"
27
- password = "#{setting("var.output.elasticsearch.password", "changeme")}"
28
- user = "#{setting("var.output.elasticsearch.user", "elastic")}"
29
- document_type_line = type_string ? "document_type => #{type_string}" : ""
66
+ hosts = array_to_string(get_setting(LogStash::Setting::SplittableStringArray.new("var.elasticsearch.hosts", String, ["localhost:9200"])))
67
+ index = "#{@name}-#{setting("var.elasticsearch.index_suffix", "%{+YYYY.MM.dd}")}"
68
+ user = @settings["var.elasticsearch.username"]
69
+ password = @settings["var.elasticsearch.password"]
70
+ lines = ["hosts => #{hosts}", "index => \"#{index}\""]
71
+ lines.push(user ? "user => \"#{user}\"" : nil)
72
+ lines.push(password ? "password => \"#{password}\"" : nil)
73
+ lines.push(type_string ? "document_type => #{type_string}" : nil)
74
+ lines.push("ssl => #{@settings.fetch('var.elasticsearch.ssl.enabled', false)}")
75
+ if cacert = @settings["var.elasticsearch.ssl.certificate_authority"]
76
+ lines.push("cacert => \"#{cacert}\"") if cacert
77
+ end
78
+ # NOTE: the first line should be indented in the conf.erb
30
79
  <<-CONF
31
80
  elasticsearch {
32
- hosts => [#{hosts}]
33
- index => "#{index}"
34
- password => "#{password}"
35
- user => "#{user}"
36
- manage_template => false
37
- #{document_type_line}
38
- }
81
+ #{lines.compact.join("\n ")}
82
+ manage_template => false
83
+ }
39
84
  CONF
40
85
  end
41
86
 
@@ -45,4 +90,31 @@ CONF
45
90
  renderer = ERB.new(FileReader.read(template))
46
91
  renderer.result(binding)
47
92
  end
93
+
94
+ private
95
+ # For a first version we are copying the values of the original hash,
96
+ # this might become problematic if we users changes the values of the
97
+ # settings in the template, which could result in an inconsistent view of the original data
98
+ #
99
+ # For v1 of the feature I think its an OK compromise, v2 we have a more advanced hash that
100
+ # support alias.
101
+ def alias_matching_keys(aliases, target)
102
+ aliased_target = target.dup
103
+
104
+ aliases.each do |matching_key_prefix, new_key_prefix|
105
+ target.each do |k, v|
106
+ re = /^#{matching_key_prefix}\./
107
+
108
+ if k =~ re
109
+ alias_key = k.gsub(re, "#{new_key_prefix}.")
110
+
111
+ # If the user setup the same values twices with different values lets just halt.
112
+ raise "Cannot create an alias, the destination key has already a value set: original key: #{k}, alias key: #{alias_key}" if (!aliased_target[alias_key].nil? && aliased_target[alias_key] != v)
113
+ aliased_target[alias_key] = v unless v.nil?
114
+ end
115
+ end
116
+ end
117
+
118
+ aliased_target
119
+ end
48
120
  end end end
@@ -1,5 +1,6 @@
1
1
  # encoding: utf-8
2
2
  require "logstash/namespace"
3
+ require "logstash/json"
3
4
  require_relative "file_reader"
4
5
 
5
6
  module LogStash module Modules module ResourceBase
@@ -8,7 +9,11 @@ module LogStash module Modules module ResourceBase
8
9
  def initialize(base, content_type, content_path, content = nil, content_id = nil)
9
10
  @base, @content_type, @content_path = base, content_type, content_path
10
11
  @content_id = content_id || ::File.basename(@content_path, ".*")
12
+ # content at this time will be a JSON string
11
13
  @content = content
14
+ if !@content.nil?
15
+ @content_as_object = LogStash::Json.load(@content) rescue {}
16
+ end
12
17
  end
13
18
 
14
19
  def content
@@ -19,12 +24,8 @@ module LogStash module Modules module ResourceBase
19
24
  "#{base}, #{content_type}, #{content_path}, #{content_id}"
20
25
  end
21
26
 
22
- def contains?(text)
23
- content.include?(text)
24
- end
25
-
26
27
  def content_as_object
27
- LogStash::Json.load(content) rescue nil
28
+ @content_as_object ||= FileReader.read_json(@content_path) rescue nil
28
29
  end
29
30
 
30
31
  def <=>(other)
@@ -1,6 +1,7 @@
1
1
  # encoding: utf-8
2
2
  require "logstash/namespace"
3
3
  require "logstash/logging"
4
+ require "logstash/util/loggable"
4
5
  require "erb"
5
6
 
6
7
  require_relative "elasticsearch_config"
@@ -10,19 +11,26 @@ require_relative "logstash_config"
10
11
  module LogStash module Modules class Scaffold
11
12
  include LogStash::Util::Loggable
12
13
 
13
- attr_reader :directory, :module_name, :logstash_configuration, :kibana_configuration, :elasticsearch_configuration
14
+ attr_reader :directory, :module_name, :kibana_version_parts
15
+ attr_reader :kibana_configuration, :logstash_configuration, :elasticsearch_configuration
14
16
 
15
17
  def initialize(name, directory)
16
18
  @module_name = name
17
19
  @directory = directory # this is the 'configuration folder in the GEM root.'
20
+ @kibana_version_parts = "5.6.0".split('.') # this is backup in case kibana client fails to connect
21
+ logger.info("Initializing module", :module_name => name, :directory => directory)
18
22
  end
19
23
 
20
- def import(import_engine)
24
+ def add_kibana_version(version_parts)
25
+ @kibana_version_parts = version_parts
26
+ end
27
+
28
+ def import(import_engine, kibana_import_engine)
21
29
  @elasticsearch_configuration.resources.each do |resource|
22
30
  import_engine.put(resource)
23
31
  end
24
32
  @kibana_configuration.resources.each do |resource|
25
- import_engine.put(resource)
33
+ kibana_import_engine.put(resource)
26
34
  end
27
35
  end
28
36
 
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+ require "logstash/namespace"
3
+
4
+ module LogStash module Modules class SettingsMerger
5
+ def self.merge(cli_settings, yml_settings)
6
+ # both args are arrays of hashes, e.g.
7
+ # [{"name"=>"mod1", "var.input.tcp.port"=>"3333"}, {"name"=>"mod2"}]
8
+ # [{"name"=>"mod1", "var.input.tcp.port"=>2222, "var.kibana.username"=>"rupert", "var.kibana.password"=>"fotherington"}, {"name"=>"mod3", "var.input.tcp.port"=>4445}]
9
+ merged = []
10
+ # union and group_by preserves order
11
+ # union will also coalesce identical hashes
12
+ union_of_settings = (cli_settings | yml_settings)
13
+ grouped_by_name = union_of_settings.group_by{|e| e["name"]}
14
+ grouped_by_name.each do |name, array|
15
+ if array.size == 2
16
+ merged << array.first.merge(array.last)
17
+ else
18
+ merged.concat(array)
19
+ end
20
+ end
21
+ merged
22
+ end
23
+ end end end
@@ -0,0 +1,17 @@
1
+ # encoding: utf-8
2
+ require_relative "scaffold"
3
+
4
+ # This module function should be used when gems or
5
+ # x-pack defines modules in their folder structures.
6
+ module LogStash module Modules module Util
7
+ def self.register_local_modules(path)
8
+ modules_path = ::File.join(path, "modules")
9
+ ::Dir.foreach(modules_path) do |item|
10
+ # Ignore unix relative path ids
11
+ next if item == '.' or item == '..'
12
+ # Ignore non-directories
13
+ next if !::File.directory?(::File.join(modules_path, ::File::Separator, item))
14
+ LogStash::PLUGIN_REGISTRY.add(:modules, item, Scaffold.new(item, ::File.join(modules_path, item, "configuration")))
15
+ end
16
+ end
17
+ end end end
@@ -19,7 +19,9 @@ module LogStash class OutputDelegator
19
19
  @namespaced_metric = metric.namespace(id.to_sym)
20
20
  @namespaced_metric.gauge(:name, config_name)
21
21
  @metric_events = @namespaced_metric.namespace(:events)
22
-
22
+ @in_counter = @metric_events.counter(:in)
23
+ @out_counter = @metric_events.counter(:out)
24
+ @time_metric = @metric_events.counter(:duration_in_millis)
23
25
  @strategy = strategy_registry.
24
26
  class_for(self.concurrency).
25
27
  new(@logger, @output_class, @namespaced_metric, execution_context, plugin_args)
@@ -42,11 +44,11 @@ module LogStash class OutputDelegator
42
44
  end
43
45
 
44
46
  def multi_receive(events)
45
- @metric_events.increment(:in, events.length)
46
- clock = @metric_events.time(:duration_in_millis)
47
+ @in_counter.increment(events.length)
48
+ start_time = java.lang.System.current_time_millis
47
49
  @strategy.multi_receive(events)
48
- clock.stop
49
- @metric_events.increment(:out, events.length)
50
+ @time_metric.increment(java.lang.System.current_time_millis - start_time)
51
+ @out_counter.increment(events.length)
50
52
  end
51
53
 
52
54
  def do_close
@@ -34,6 +34,7 @@ module LogStash; class BasePipeline
34
34
 
35
35
  def initialize(config_str, settings = SETTINGS)
36
36
  @logger = self.logger
37
+ @mutex = Mutex.new
37
38
  @config_str = config_str
38
39
  @config_hash = Digest::SHA1.hexdigest(@config_str)
39
40
  # Every time #plugin is invoked this is incremented to give each plugin
@@ -58,6 +59,8 @@ module LogStash; class BasePipeline
58
59
  parsed_config = grammar.parse(config_str)
59
60
  raise(ConfigurationError, grammar.failure_reason) if parsed_config.nil?
60
61
 
62
+ parsed_config.process_escape_sequences = settings.get_value("config.support_escapes")
63
+
61
64
  config_code = parsed_config.compile
62
65
 
63
66
  # config_code = BasePipeline.compileConfig(config_str)
@@ -122,6 +125,12 @@ module LogStash; class BasePipeline
122
125
  def non_reloadable_plugins
123
126
  (inputs + filters + outputs).select { |plugin| !plugin.reloadable? }
124
127
  end
128
+
129
+ private
130
+
131
+ def default_logging_keys(other_keys = {})
132
+ { :pipeline_id => pipeline_id }.merge(other_keys)
133
+ end
125
134
  end; end
126
135
 
127
136
  module LogStash; class Pipeline < BasePipeline
@@ -313,6 +322,9 @@ module LogStash; class Pipeline < BasePipeline
313
322
  config_metric.gauge(:batch_delay, batch_delay)
314
323
  config_metric.gauge(:config_reload_automatic, @settings.get("config.reload.automatic"))
315
324
  config_metric.gauge(:config_reload_interval, @settings.get("config.reload.interval"))
325
+ config_metric.gauge(:dead_letter_queue_enabled, dlq_enabled?)
326
+ config_metric.gauge(:dead_letter_queue_path, @dlq_writer.get_path.to_absolute_path.to_s) if dlq_enabled?
327
+
316
328
 
317
329
  @logger.info("Starting pipeline",
318
330
  "id" => self.pipeline_id,
@@ -347,6 +359,10 @@ module LogStash; class Pipeline < BasePipeline
347
359
  end
348
360
  end
349
361
 
362
+ def dlq_enabled?
363
+ @settings.get("dead_letter_queue.enable")
364
+ end
365
+
350
366
  # Main body of what a worker thread does
351
367
  # Repeatedly takes batches off the queue, filters, then outputs them
352
368
  def worker_loop(batch_size, batch_delay)
@@ -419,7 +435,7 @@ module LogStash; class Pipeline < BasePipeline
419
435
  output_events_map.each do |output, events|
420
436
  output.multi_receive(events)
421
437
  end
422
-
438
+
423
439
  @filter_queue_client.add_output_metrics(batch)
424
440
  end
425
441
 
@@ -599,6 +615,13 @@ module LogStash; class Pipeline < BasePipeline
599
615
  .each {|t| t.delete("status") }
600
616
  end
601
617
 
618
+ def collect_dlq_stats
619
+ if dlq_enabled?
620
+ dlq_metric = @metric.namespace([:stats, :pipelines, pipeline_id.to_s.to_sym, :dlq])
621
+ dlq_metric.gauge(:queue_size_in_bytes, @dlq_writer.get_current_queue_size)
622
+ end
623
+ end
624
+
602
625
  def collect_stats
603
626
  pipeline_metric = @metric.namespace([:stats, :pipelines, pipeline_id.to_s.to_sym, :queue])
604
627
  pipeline_metric.gauge(:type, settings.get("queue.type"))
@@ -639,11 +662,20 @@ module LogStash; class Pipeline < BasePipeline
639
662
 
640
663
  private
641
664
 
665
+ def default_logging_keys(other_keys = {})
666
+ keys = super
667
+ keys[:thread] = thread.inspect if thread
668
+ keys
669
+ end
670
+
642
671
  def draining_queue?
643
672
  @drain_queue ? !@filter_queue_client.empty? : false
644
673
  end
645
674
 
646
675
  def wrapped_write_client(plugin)
647
- LogStash::Instrument::WrappedWriteClient.new(@input_queue_client, self, metric, plugin)
676
+ #need to ensure that metrics are initialized one plugin at a time, else a race condition can exist.
677
+ @mutex.synchronize do
678
+ LogStash::Instrument::WrappedWriteClient.new(@input_queue_client, self, metric, plugin)
679
+ end
648
680
  end
649
681
  end; end