logstash-filter-ruby 3.0.4 → 3.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
  SHA1:
3
- metadata.gz: c4ddfea521d6f9a2fb33a994635e6fe2e3527a56
4
- data.tar.gz: dd8208bffa6ff7ef0f47be2f4c2855834a88b981
3
+ metadata.gz: 273ed2da72e09943995b2bcbbeb25e5ff5066173
4
+ data.tar.gz: adccac89c884c0123b10a382a138bcec9ee58529
5
5
  SHA512:
6
- metadata.gz: 033d7c85b4acf7d894abf39766729821d0bd044e983b2991ab28a6d031af1046f7b6c56d7c8c3525ba67f74bf37bf0195a39d1d389f7ea2e13d574ed9af45b86
7
- data.tar.gz: 6b1db324b71e23d3468d0a613355b9d60cf8e3ace06d310feaffeb1ae4383ae20e050b00c186126676dffa954c76c97b269733d70b21dcd7dcaa7027dc370c4b
6
+ metadata.gz: 465d9a82af6a18daf470d767b4e3a07ad57783d4cfee66a2979631333922381360058cedecbd4b08423a5e0c84f78bf0a6e89dfb4cbec4bc43a9940f1340aea4
7
+ data.tar.gz: b28977f8356f8ab9639c96025da48d8bef8260b279c5dacd8cb1ed953d7ea76ea7613513c743e8a56999aa9b9109760a3a208369af20e47c9056a63d0d0a9bc1
@@ -1,3 +1,6 @@
1
+ ## 3.1.0
2
+ - Add file base ruby script support
3
+
1
4
  ## 3.0.4
2
5
  - Fix some documentation issues
3
6
 
@@ -20,7 +20,13 @@ include::{include_path}/plugin_header.asciidoc[]
20
20
 
21
21
  ==== Description
22
22
 
23
- Execute ruby code.
23
+ Execute ruby code. This filter accepts inline ruby code or a ruby file.
24
+ The two options are mutually exclusive and have slightly different ways of working,
25
+ which are described below.
26
+
27
+ ===== Inline ruby code
28
+
29
+ To inline ruby in your filter, place all code in the `code` option. This code will be executed for every event the filter receives. You can also place ruby code in the `init` option - it will be executed only once during the plugin's register phase.
24
30
 
25
31
  For example, to cancel 90% of events, you can do this:
26
32
  [source,ruby]
@@ -31,8 +37,7 @@ For example, to cancel 90% of events, you can do this:
31
37
  }
32
38
  }
33
39
 
34
- If you need to create additional events, it cannot be done as in other filters where you would use `yield`,
35
- you must use a specific syntax `new_event_block.call(event)` like in this example duplicating the input event
40
+ If you need to create additional events, you must use a specific syntax `new_event_block.call(event)` like in this example duplicating the input event
36
41
  [source,ruby]
37
42
  filter {
38
43
  ruby {
@@ -40,6 +45,92 @@ filter {
40
45
  }
41
46
  }
42
47
 
48
+ ===== Using a Ruby script file
49
+
50
+ As the inline code can become complex and hard to structure inside of a text string in `code`, it's then preferrable to place the Ruby code in a .rb file, using the `path` option.
51
+
52
+ [source,ruby]
53
+ filter {
54
+ ruby {
55
+ # Cancel 90% of events
56
+ path => "/etc/logstash/drop_percentage.rb"
57
+ script_params => { "percentage" => 0.9 }
58
+ }
59
+ }
60
+
61
+ The ruby script file should define the following methods:
62
+
63
+ * `register(params)`: An optional register method that receives the key/value hash passed in the `script_params` configuration option
64
+ * `filter(event)`: A mandatory Ruby method that accepts a Logstash event and must return an array of events
65
+
66
+ Below is an example implementation of the `drop_percentage.rb` ruby script that drops a configurable percentage of events:
67
+
68
+ [source,ruby]
69
+ ----
70
+ # the value of `params` is the value of the hash passed to `script_params`
71
+ # in the logstash configuration
72
+ def register(params)
73
+ @drop_percentage = params["percentage"]
74
+ end
75
+
76
+ # the filter method receives an event and must return a list of events.
77
+ # Dropping an event means not including it in the return array,
78
+ # while creating new ones only requires you to add a new instance of
79
+ # LogStash::Event to the returned array
80
+ def filter(event)
81
+ if rand >= @drop_percentage
82
+ return [event]
83
+ else
84
+ return [] # return empty array to cancel event
85
+ end
86
+ end
87
+ ----
88
+
89
+ ====== Testing the ruby script
90
+
91
+ To validate the behaviour of the `filter` method you implemented,
92
+ the Ruby filter plugin provides an inline test framework where you
93
+ can assert expectations.
94
+ The tests you define will run when the pipeline is created and will
95
+ prevent it from starting if a test fails.
96
+
97
+ You can also verify if the tests pass using the logstash `-t` flag.
98
+
99
+ For example above, you can write at the bottom of the `drop_percentage.rb`
100
+ ruby script the following test:
101
+
102
+ [source,ruby]
103
+ ----
104
+ def register(params)
105
+ # ..
106
+ end
107
+
108
+ def filter(event)
109
+ # ..
110
+ end
111
+
112
+ test "drop percentage 100%" do
113
+ parameters do
114
+ { "percentage" => 1 }
115
+ end
116
+
117
+ in_event { { "message" => "hello" } }
118
+
119
+ expect("drops the event") do |events|
120
+ events.size == 0
121
+ end
122
+ end
123
+ ----
124
+
125
+ We can now test that the ruby script we're using is implemented correctly:
126
+
127
+ [source]
128
+ ----
129
+ % bin/logstash -e "filter { ruby { path => '/etc/logstash/drop_percentage.rb' script_params => { 'drop_percentage' => 0.5 } } }" -t
130
+ [2017-10-13T13:44:29,723][INFO ][logstash.filters.ruby.script] Test run complete {:script_path=>"/etc/logstash/drop_percentage.rb", :results=>{:passed=>1, :failed=>0, :errored=>0}}
131
+ Configuration OK
132
+ [2017-10-13T13:44:29,887][INFO ][logstash.runner ] Using config.test_and_exit mode. Config Validation Result: OK. Exiting Logstash
133
+ ----
43
134
 
44
135
  [id="plugins-{type}s-{plugin}-options"]
45
136
  ==== Ruby Filter Configuration Options
@@ -49,8 +140,11 @@ This plugin supports the following configuration options plus the <<plugins-{typ
49
140
  [cols="<,<,<",options="header",]
50
141
  |=======================================================================
51
142
  |Setting |Input type|Required
52
- | <<plugins-{type}s-{plugin}-code>> |<<string,string>>|Yes
143
+ | <<plugins-{type}s-{plugin}-code>> |<<string,string>>|No
53
144
  | <<plugins-{type}s-{plugin}-init>> |<<string,string>>|No
145
+ | <<plugins-{type}s-{plugin}-path>> |<<string,string>>|No
146
+ | <<plugins-{type}s-{plugin}-script_params>> |<<hash,hash>>,{}|No
147
+ | <<plugins-{type}s-{plugin}-tag_on_exception>> |<<string,string>>,_rubyexception|No
54
148
  |=======================================================================
55
149
 
56
150
  Also see <<plugins-{type}s-{plugin}-common-options>> for a list of options supported by all
@@ -59,24 +153,40 @@ filter plugins.
59
153
  &nbsp;
60
154
 
61
155
  [id="plugins-{type}s-{plugin}-code"]
62
- ===== `code`
156
+ ===== `code`
63
157
 
64
- * This is a required setting.
65
158
  * Value type is <<string,string>>
66
159
  * There is no default value for this setting.
160
+ * This setting cannot be used together with `path`.
67
161
 
68
162
  The code to execute for every event.
69
163
  You will have an `event` variable available that is the event itself. See the <<event-api,Event API>> for more information.
70
164
 
71
165
  [id="plugins-{type}s-{plugin}-init"]
72
- ===== `init`
166
+ ===== `init`
73
167
 
74
168
  * Value type is <<string,string>>
75
169
  * There is no default value for this setting.
76
170
 
77
171
  Any code to execute at logstash startup-time
78
172
 
173
+ [id="plugins-{type}s-{plugin}-path"]
174
+ ===== `path`
175
+
176
+ * Value type is <<string,string>>
177
+ * There is no default value for this setting.
178
+ * This setting cannot be used together with `code`.
179
+
180
+ The path of the ruby script file that implements the `filter` method.
181
+
182
+ [id="plugins-{type}s-{plugin}-script_params"]
183
+ ===== `script_params`
184
+
185
+ * Value type is <<hash,hash>>
186
+ * Default value is `{}`
79
187
 
188
+ A key/value hash with parameters that are passed to the register method
189
+ of your ruby script file defined in `path`.
80
190
 
81
191
  [id="plugins-{type}s-{plugin}-common-options"]
82
- include::{include_path}/{type}.asciidoc[]
192
+ include::{include_path}/{type}.asciidoc[]
@@ -23,6 +23,9 @@ require "logstash/namespace"
23
23
  # }
24
24
  #
25
25
  class LogStash::Filters::Ruby < LogStash::Filters::Base
26
+ require "logstash/filters/ruby/script_error"
27
+ require "logstash/filters/ruby/script"
28
+
26
29
  config_name "ruby"
27
30
 
28
31
  # Any code to execute at logstash startup-time
@@ -30,21 +33,92 @@ class LogStash::Filters::Ruby < LogStash::Filters::Base
30
33
 
31
34
  # The code to execute for every event.
32
35
  # You will have an `event` variable available that is the event itself. See the <<event-api,Event API>> for more information.
33
- config :code, :validate => :string, :required => true
36
+ config :code, :validate => :string
37
+
38
+ # Path to the script
39
+ config :path, :validate => :path
40
+
41
+ # Parameters for this specific script
42
+ config :script_params, :type => :hash, :default => {}
43
+
44
+ # Tag to add to events that cause an exception in the script filter
45
+ config :tag_on_exception, :type => :string, :default => "_rubyexception"
46
+
47
+ def initialize(*params)
48
+ super(*params)
49
+ if @path # run tests on the ruby file script
50
+ @script = Script.new(@path, @script_params)
51
+ @script.load
52
+ @script.test
53
+ end
54
+ end
34
55
 
35
56
  def register
36
- # TODO(sissel): Compile the ruby code
37
- eval(@init, binding, "(ruby filter init)") if @init
38
- eval("@codeblock = lambda { |event, &new_event_block| #{@code} }", binding, "(ruby filter code)")
39
- end # def register
57
+ if @code && @path.nil?
58
+ eval(@init, binding, "(ruby filter init)") if @init
59
+ eval("@codeblock = lambda { |event, &new_event_block| #{@code} }", binding, "(ruby filter code)")
60
+ elsif @path && @code.nil?
61
+ @script.register
62
+ else
63
+ @logger.fatal("You must either use an inline script with the \"code\" option or a script file using \"path\".")
64
+ end
65
+ end
66
+
67
+ def self.check_result_events!(results)
68
+ if !results.is_a?(Array)
69
+ raise "Custom script did not return an array from 'filter'. Only arrays may be returned!"
70
+ end
71
+
72
+ results.each do |r_event|
73
+ if !r_event.is_a?(::LogStash::Event)
74
+ raise "Custom script returned a non event object '#{r_event.inspect}'!" +
75
+ " You must an array of events from this function! To drop an event simply return nil."
76
+ end
77
+ end
78
+ end
79
+
80
+ def filter(event, &block)
81
+ if @code
82
+ inline_script(event, &block)
83
+ elsif @path
84
+ file_script(event)
85
+ end
86
+ end
87
+
88
+ def inline_script(event, &block)
89
+ @codeblock.call(event, &block)
90
+ filter_matched(event)
91
+ rescue Exception => e
92
+ @logger.error("Ruby exception occurred: #{e}")
93
+ event.tag(@tag_on_exception)
94
+ end
40
95
 
41
- def filter(event,&block)
96
+ def file_script(event)
42
97
  begin
43
- @codeblock.call(event,&block)
44
- filter_matched(event)
45
- rescue Exception => e
46
- @logger.error("Ruby exception occurred: #{e}")
47
- event.tag("_rubyexception")
98
+ results = @script.execute(event)
99
+
100
+ self.class.check_result_events!(results)
101
+ rescue => e
102
+ event.tag(@tag_on_exception)
103
+ message = "Could not process event: " + e.message
104
+ @logger.error(message, :script_path => @path,
105
+ :class => e.class.name,
106
+ :backtrace => e.backtrace)
107
+ return event
108
+ end
109
+
110
+ returned_original = false
111
+ results.each do |r_event|
112
+ # If the user has generated a new event we yield that for them here
113
+ if event == r_event
114
+ returned_original = true
115
+ else
116
+ yield r_event
117
+ end
118
+
119
+ r_event
48
120
  end
121
+
122
+ event.cancel unless returned_original
49
123
  end
50
124
  end
@@ -0,0 +1,40 @@
1
+ class LogStash::Filters::Ruby::Script
2
+ include ::LogStash::Util::Loggable
3
+ require "logstash/filters/ruby/script/context"
4
+
5
+ attr_reader :content, :script_path
6
+
7
+ def initialize(script_path, parameters)
8
+ @content = File.read(script_path)
9
+ @script_path = script_path
10
+ @context = Context.new(self, parameters)
11
+ end
12
+
13
+ def load
14
+ @context.load_script
15
+
16
+ if !@context.execution_context.methods.include?(:filter)
17
+ raise "Script does not define a filter! Please ensure that you have defined a filter method!"
18
+ end
19
+ rescue => e
20
+ raise ::LogStash::Filters::Ruby::ScriptError.new("Error during load of '#{script_path}': #{e.inspect}")
21
+ end
22
+
23
+ def register
24
+ @context.execute_register
25
+ rescue => e
26
+ raise ::LogStash::Filters::Ruby::ScriptError.new("Error during register of '#{script_path}': #{e.inspect}")
27
+ end
28
+
29
+ def execute(event)
30
+ @context.execute_filter(event)
31
+ end
32
+
33
+ def test
34
+ results = @context.execute_tests
35
+ logger.info("Test run complete", :script_path => script_path, :results => results)
36
+ if results[:failed] > 0 || results[:errored] > 0
37
+ raise ::LogStash::Filters::Ruby::ScriptError.new("Script '#{script_path}' had #{results[:failed] + results[:errored]} failing tests! Check the error log for details.")
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,75 @@
1
+ class LogStash::Filters::Ruby::Script::Context
2
+ require "logstash/filters/ruby/script/execution_context"
3
+ require "logstash/filters/ruby/script/test_context"
4
+
5
+ include ::LogStash::Util::Loggable
6
+
7
+ attr_reader :script,
8
+ :execution_context
9
+
10
+ def initialize(script, parameters)
11
+ @script = script
12
+ @script_path = @script.script_path
13
+ @parameters = parameters
14
+ @test_contexts = []
15
+ @script_lock = Mutex.new
16
+ @concurrency = :single
17
+ end
18
+
19
+ def make_execution_context(name, test_mode)
20
+ execution_context = LogStash::Filters::Ruby::Script::ExecutionContext.new(name, logger)
21
+
22
+ # Proxy all the methods from this instance needed to be run from the execution context
23
+ this = self # We need to use a clojure to retain access to this object
24
+ # If we aren't in test mode we define the test. If we *are* then we don't define anything
25
+ # since our tests are already defined
26
+ if test_mode
27
+ execution_context.define_singleton_method(:test) {|name,&block| nil }
28
+ else
29
+ execution_context.define_singleton_method(:test) {|name,&block| this.test(name, &block) }
30
+ end
31
+ execution_context
32
+ end
33
+
34
+ def load_execution_context(ec)
35
+ ec.instance_eval(@script.content, @script_path, 1)
36
+ end
37
+
38
+ def load_script
39
+ @execution_context = self.make_execution_context(:main, false)
40
+ load_execution_context(@execution_context)
41
+ end
42
+
43
+ def execute_register()
44
+ @execution_context.register(@parameters)
45
+ end
46
+
47
+ def concurrency(type)
48
+ @concurrency = type
49
+ end
50
+
51
+ def execute_filter(event)
52
+ if @concurrency == :shared
53
+ @script_lock.synchronize { @execution_context.filter(event) }
54
+ else
55
+ @execution_context.filter(event)
56
+ end
57
+ end
58
+
59
+ def execute_tests
60
+ @test_contexts.
61
+ map(&:execute).
62
+ reduce({:passed => 0, :failed => 0, :errored => 0}) do |acc,res|
63
+ acc[:passed] += res[:passed]
64
+ acc[:failed] += res[:failed]
65
+ acc[:errored] += res[:errored]
66
+ acc
67
+ end
68
+ end
69
+
70
+ def test(name, &block)
71
+ test_context = LogStash::Filters::Ruby::Script::TestContext.new(self, name)
72
+ test_context.instance_eval(&block)
73
+ @test_contexts << test_context
74
+ end
75
+ end
@@ -0,0 +1,24 @@
1
+ # A blank area for our script to live in.
2
+ # Everything is instance_e{val,exec}'d against this
3
+ # to eliminate instance var and method def conflicts against other
4
+ # objects
5
+ class LogStash::Filters::Ruby::Script::ExecutionContext
6
+ def initialize(name, logger)
7
+ # Namespaced with underscore so as not to conflict with anything the user sets
8
+ @__name__ = name
9
+ @__logger__ = logger
10
+ end
11
+
12
+ def logger
13
+ @__logger__
14
+ end
15
+
16
+ def register(params)
17
+ logger.debug("skipping register since the script didn't define it")
18
+ end
19
+
20
+ def to_s
21
+ "<ExecutionContext #{@__name__}>"
22
+ end
23
+ end
24
+
@@ -0,0 +1,40 @@
1
+ # Handle expect blocks inside of test blocks
2
+ class ExpectContext
3
+ include ::LogStash::Util::Loggable
4
+
5
+ attr_reader :name
6
+
7
+ def initialize(test_context, name, block)
8
+ @test_context = test_context
9
+ @name = name
10
+ @block = block
11
+ end
12
+
13
+ def to_s
14
+ "<Expect #{@test_context.name}/#{self.name}>"
15
+ end
16
+
17
+ def execute(events)
18
+ begin
19
+ if @block.call(events)
20
+ return :passed
21
+ else
22
+ result = :failed
23
+ message = "***TEST FAILURE FOR: '#{@test_context.name} #{@name}'***"
24
+ log_hash = {}
25
+ end
26
+ rescue => e
27
+ result = :errored
28
+ message = "***TEST RAISED ERROR: '#{@test_context.name} #{@name}'***"
29
+ log_hash = { "exception" => e.inspect, "backtrace" => e.backtrace }
30
+ end
31
+ script_path = @test_context.script_context.script.script_path
32
+ log_hash.merge!({
33
+ :parameters => @test_context.parameters,
34
+ :in_events => @test_context.in_events.map(&:to_hash_with_metadata),
35
+ :results => events.map(&:to_hash_with_metadata)
36
+ })
37
+ logger.error(message, log_hash)
38
+ result
39
+ end
40
+ end
@@ -0,0 +1,65 @@
1
+ # Handle top level test blocks
2
+ class LogStash::Filters::Ruby::Script::TestContext
3
+ require "logstash/filters/ruby/script/expect_context"
4
+ attr_reader :name, :script_context
5
+
6
+ def initialize(script_context, name)
7
+ @name = name
8
+ @script_context = script_context
9
+ @expect_contexts = []
10
+ @parameters = {}
11
+ @execution_context = script_context.make_execution_context("Test/#{name}", true)
12
+ @script_context.load_execution_context(@execution_context)
13
+ end
14
+
15
+ def parameters(&block)
16
+ # Can act as a reader if no block passed
17
+ return @parameters unless block
18
+
19
+ @parameters = block.call
20
+ if !@parameters.is_a?(Hash)
21
+ raise ArgumentError, "Test parameters must be a hash in #{@name}!"
22
+ end
23
+
24
+ @execution_context.register(@parameters)
25
+ end
26
+
27
+ def in_event(&block)
28
+ return @in_events unless block
29
+
30
+ orig = block.call
31
+ event_hashes = orig.is_a?(Hash) ? [orig] : orig
32
+ event_hashes.each do |e|
33
+ if !e.is_a?(Hash)
34
+ raise ArgumentError,
35
+ "In event for #{self.name} must receive either a hash or an array of hashes! got a '#{e.class}' in #{event_hashes.inspect}"
36
+ end
37
+ end
38
+ @in_events = Array(event_hashes).map {|h| ::LogStash::Event.new(h) }
39
+ end
40
+ alias_method :in_events, :in_event
41
+
42
+ def execute
43
+ if !@in_events
44
+ raise "You must declare an `in_event` to run tests!"
45
+ end
46
+
47
+ results = []
48
+ @in_events.each do |e|
49
+ single_result = @execution_context.filter(e)
50
+ ::LogStash::Filters::Ruby.check_result_events!(single_result)
51
+ results += single_result
52
+ end
53
+
54
+ @expect_contexts.map do |ec|
55
+ ec.execute(results)
56
+ end.reduce({:passed => 0, :failed => 0, :errored => 0}) do |acc,res|
57
+ acc[res] += 1
58
+ acc
59
+ end
60
+ end
61
+
62
+ def expect(name, &block)
63
+ @expect_contexts << ExpectContext.new(self, name, block)
64
+ end
65
+ end
@@ -0,0 +1,2 @@
1
+ class LogStash::Filters::Ruby::ScriptError < StandardError
2
+ end
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
 
3
3
  s.name = 'logstash-filter-ruby'
4
- s.version = '3.0.4'
4
+ s.version = '3.1.0'
5
5
  s.licenses = ['Apache License (2.0)']
6
6
  s.summary = "Filter for executing ruby code on the event"
7
7
  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"
@@ -1,121 +1,160 @@
1
1
  # encoding: utf-8
2
-
3
- require "logstash/devutils/rspec/spec_helper"
2
+ require_relative '../spec_helper'
4
3
  require "logstash/filters/ruby"
5
4
  require "logstash/filters/date"
6
5
 
7
6
  describe LogStash::Filters::Ruby do
7
+ context "when using inline script" do
8
+ describe "generate pretty json on event.to_hash" do
9
+ # this obviously tests the Ruby filter but also makes sure
10
+ # the fix for issue #1771 is correct and that to_json is
11
+ # compatible with the json gem convention.
8
12
 
9
- describe "generate pretty json on event.to_hash" do
10
- # this obviously tests the Ruby filter but also makes sure
11
- # the fix for issue #1771 is correct and that to_json is
12
- # compatible with the json gem convention.
13
-
14
- config <<-CONFIG
15
- filter {
16
- date {
17
- match => [ "mydate", "ISO8601" ]
18
- locale => "en"
19
- timezone => "UTC"
20
- }
21
- ruby {
22
- init => "require 'json'"
23
- code => "event.set('pretty', JSON.pretty_generate(event.to_hash))"
13
+ config <<-CONFIG
14
+ filter {
15
+ date {
16
+ match => [ "mydate", "ISO8601" ]
17
+ locale => "en"
18
+ timezone => "UTC"
19
+ }
20
+ ruby {
21
+ init => "require 'json'"
22
+ code => "event.set('pretty', JSON.pretty_generate(event.to_hash))"
23
+ }
24
24
  }
25
- }
26
- CONFIG
27
-
28
- sample("message" => "hello world", "mydate" => "2014-09-23T00:00:00-0800") do
29
- # json is rendered in pretty json since the JSON.pretty_generate created json from the event hash
30
- # pretty json contains \n
31
- insist { subject.get("pretty").count("\n") } == 5
32
- # usage of JSON.parse here is to avoid parser-specific order assertions
33
- insist { JSON.parse(subject.get("pretty")) } == JSON.parse("{\n \"message\": \"hello world\",\n \"mydate\": \"2014-09-23T00:00:00-0800\",\n \"@version\": \"1\",\n \"@timestamp\": \"2014-09-23T08:00:00.000Z\"\n}")
25
+ CONFIG
26
+
27
+ sample("message" => "hello world", "mydate" => "2014-09-23T00:00:00-0800") do
28
+ # json is rendered in pretty json since the JSON.pretty_generate created json from the event hash
29
+ # pretty json contains \n
30
+ insist { subject.get("pretty").count("\n") } == 5
31
+ # usage of JSON.parse here is to avoid parser-specific order assertions
32
+ insist { JSON.parse(subject.get("pretty")) } == JSON.parse("{\n \"message\": \"hello world\",\n \"mydate\": \"2014-09-23T00:00:00-0800\",\n \"@version\": \"1\",\n \"@timestamp\": \"2014-09-23T08:00:00.000Z\"\n}")
33
+ end
34
34
  end
35
- end
36
35
 
37
- describe "generate pretty json on event.to_hash" do
38
- # this obviously tests the Ruby filter but asses that using the json gem directly
39
- # on even will correctly call the to_json method but will use the logstash json
40
- # generation and thus will not work with pretty_generate.
41
- config <<-CONFIG
42
- filter {
43
- date {
44
- match => [ "mydate", "ISO8601" ]
45
- locale => "en"
46
- timezone => "UTC"
47
- }
48
- ruby {
49
- init => "require 'json'"
50
- code => "event.set('pretty', JSON.pretty_generate(event))"
36
+ describe "generate pretty json on event.to_hash" do
37
+ # this obviously tests the Ruby filter but asses that using the json gem directly
38
+ # on even will correctly call the to_json method but will use the logstash json
39
+ # generation and thus will not work with pretty_generate.
40
+ config <<-CONFIG
41
+ filter {
42
+ date {
43
+ match => [ "mydate", "ISO8601" ]
44
+ locale => "en"
45
+ timezone => "UTC"
46
+ }
47
+ ruby {
48
+ init => "require 'json'"
49
+ code => "event.set('pretty', JSON.pretty_generate(event))"
50
+ }
51
51
  }
52
- }
53
- CONFIG
54
-
55
- sample("message" => "hello world", "mydate" => "2014-09-23T00:00:00-0800") do
56
- # if this eventually breaks because we removed the custom to_json and/or added pretty support to JrJackson then all is good :)
57
- # non-pretty json does not contain \n
58
- insist { subject.get("pretty").count("\n") } == 0
59
- # usage of JSON.parse here is to avoid parser-specific order assertions
60
- insist { JSON.parse(subject.get("pretty")) } == JSON.parse("{\"message\":\"hello world\",\"mydate\":\"2014-09-23T00:00:00-0800\",\"@version\":\"1\",\"@timestamp\":\"2014-09-23T08:00:00.000Z\"}")
52
+ CONFIG
53
+
54
+ sample("message" => "hello world", "mydate" => "2014-09-23T00:00:00-0800") do
55
+ # if this eventually breaks because we removed the custom to_json and/or added pretty support to JrJackson then all is good :)
56
+ # non-pretty json does not contain \n
57
+ insist { subject.get("pretty").count("\n") } == 0
58
+ # usage of JSON.parse here is to avoid parser-specific order assertions
59
+ insist { JSON.parse(subject.get("pretty")) } == JSON.parse("{\"message\":\"hello world\",\"mydate\":\"2014-09-23T00:00:00-0800\",\"@version\":\"1\",\"@timestamp\":\"2014-09-23T08:00:00.000Z\"}")
60
+ end
61
61
  end
62
- end
63
62
 
64
- describe "catch all exceptions and don't let them ruin your day buy stopping the entire worker" do
65
- # If exception is raised, it stops entine processing pipeline, and never resumes
66
- # Code section should always be wrapped with begin/rescue
67
- config <<-CONFIG
68
- filter {
69
- date {
70
- match => [ "mydate", "ISO8601" ]
71
- locale => "en"
72
- timezone => "UTC"
63
+ describe "catch all exceptions and don't let them ruin your day buy stopping the entire worker" do
64
+ # If exception is raised, it stops entine processing pipeline, and never resumes
65
+ # Code section should always be wrapped with begin/rescue
66
+ config <<-CONFIG
67
+ filter {
68
+ date {
69
+ match => [ "mydate", "ISO8601" ]
70
+ locale => "en"
71
+ timezone => "UTC"
72
+ }
73
+ ruby {
74
+ init => "require 'json'"
75
+ code => "raise 'You shall not pass'"
76
+ add_tag => ["ok"]
77
+ }
73
78
  }
74
- ruby {
75
- init => "require 'json'"
76
- code => "raise 'You shall not pass'"
77
- add_tag => ["ok"]
79
+ CONFIG
80
+
81
+ sample("message" => "hello world", "mydate" => "2014-09-23T00:00:00-0800") do
82
+ insist { subject.get("mydate") } == "2014-09-23T00:00:00-0800"
83
+ insist { subject.get("tags") } == ["_rubyexception"]
84
+ end
85
+ end
86
+
87
+ describe "allow to create new event inside the ruby filter" do
88
+ config <<-CONFIG
89
+ filter {
90
+ ruby {
91
+ code => "new_event_block.call(event.clone)"
92
+ }
78
93
  }
79
- }
80
- CONFIG
94
+ CONFIG
81
95
 
82
- sample("message" => "hello world", "mydate" => "2014-09-23T00:00:00-0800") do
83
- insist { subject.get("mydate") } == "2014-09-23T00:00:00-0800"
84
- insist { subject.get("tags") } == ["_rubyexception"]
96
+ sample("message" => "hello world", "mydate" => "2014-09-23T00:00:00-0800") do
97
+ expect(subject).to be_a Array
98
+ expect(subject[0]).not_to eq(subject[1])
99
+ expect(subject[0].to_hash).to eq(subject[1].to_hash)
100
+ end
85
101
  end
86
- end
87
102
 
88
- describe "allow to create new event inside the ruby filter" do
89
- config <<-CONFIG
90
- filter {
91
- ruby {
92
- code => "new_event_block.call(event.clone)"
103
+ describe "allow to replace event by another one" do
104
+ config <<-CONFIG
105
+ filter {
106
+ ruby {
107
+ code => "new_event_block.call(event.clone);
108
+ event.cancel;"
109
+ add_tag => ["ok"]
110
+ }
93
111
  }
94
- }
95
- CONFIG
112
+ CONFIG
96
113
 
97
- sample("message" => "hello world", "mydate" => "2014-09-23T00:00:00-0800") do
98
- expect(subject).to be_a Array
99
- expect(subject[0]).not_to eq(subject[1])
100
- expect(subject[0].to_hash).to eq(subject[1].to_hash)
114
+ sample("message" => "hello world", "mydate" => "2014-09-23T00:00:00-0800") do
115
+ expect(subject.get("message")).to eq("hello world");
116
+ expect(subject.get("mydate")).to eq("2014-09-23T00:00:00-0800");
117
+ end
101
118
  end
102
119
  end
103
120
 
104
- describe "allow to replace event by another one" do
105
- config <<-CONFIG
106
- filter {
107
- ruby {
108
- code => "new_event_block.call(event.clone);
109
- event.cancel;"
110
- add_tag => ["ok"]
111
- }
112
- }
113
- CONFIG
121
+ context "when using file based script" do
122
+ let(:fixtures_path) { File.join(File.dirname(__FILE__), '../fixtures/') }
123
+ let(:script_filename) { 'field_multiplier.rb' }
124
+ let(:script_path) { File.join(fixtures_path, script_filename)}
125
+ let(:script_params) { { 'field' => 'foo', 'multiplier' => 2 } }
126
+ let(:filter_params) { { 'path' => script_path, 'script_params' => script_params} }
127
+ let(:incoming_event) { ::LogStash::Event.new('foo' => 42) }
128
+
129
+ subject(:filter) { ::LogStash::Filters::Ruby.new(filter_params) }
130
+
131
+ describe "basics" do
132
+ it "should register cleanly" do
133
+ expect do
134
+ filter.register
135
+ end.not_to raise_error
136
+ end
137
+
138
+ describe "filtering" do
139
+ before(:each) do
140
+ filter.register
141
+ filter.filter(incoming_event)
142
+ end
114
143
 
115
- sample("message" => "hello world", "mydate" => "2014-09-23T00:00:00-0800") do
116
- expect(subject.get("message")).to eq("hello world");
117
- expect(subject.get("mydate")).to eq("2014-09-23T00:00:00-0800");
144
+ it "should filter data as expected" do
145
+ expect(incoming_event.get('foo')).to eq(84)
146
+ end
147
+ end
148
+ end
149
+
150
+ describe "scripts with failing test suites" do
151
+ let(:script_filename) { 'broken.rb' }
152
+
153
+ it "should error out during register" do
154
+ expect do
155
+ filter.register
156
+ end.to raise_error(LogStash::Filters::Ruby::ScriptError)
157
+ end
118
158
  end
119
159
  end
120
160
  end
121
-
@@ -0,0 +1,13 @@
1
+ def filter(event)
2
+ event.set('foo', 'bar')
3
+ [event]
4
+ end
5
+
6
+ test "setting the field" do
7
+ in_event { { "myfield" => 123 } }
8
+
9
+ # This should fail!
10
+ expect("foo to equal baz") do |events|
11
+ events.first.get('foo') == 'baz'
12
+ end
13
+ end
@@ -0,0 +1,34 @@
1
+ # Disables mutex around the `filter` function
2
+ # Only use this if you know your code is threadsafe!
3
+ def concurrency
4
+ :shared
5
+ end
6
+
7
+ def register(params)
8
+ @field = params['field']
9
+ @multiplier = params['multiplier']
10
+ end
11
+
12
+ def filter(event)
13
+ event.set(@field, event.get(@field) * @multiplier)
14
+ # Filter blocks must return any events that are to be passed on
15
+ # return a nil or [] here if all events are to be cancelled
16
+ # You can even return one or more brand new events here!
17
+ [event]
18
+ end
19
+
20
+ test "standard flow" do
21
+ parameters do
22
+ { "field" => "myfield", "multiplier" => 3 }
23
+ end
24
+
25
+ in_event { { "myfield" => 123 } }
26
+
27
+ expect("there to be only one result event") do |events|
28
+ events.size == 1
29
+ end
30
+
31
+ expect("result to be equal to 123*3(369)") do |events|
32
+ events.first.get("myfield") == 369
33
+ end
34
+ end
@@ -0,0 +1,2 @@
1
+ # encoding: utf-8
2
+ require "logstash/devutils/rspec/spec_helper"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-filter-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.4
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elastic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-08-15 00:00:00.000000000 Z
11
+ date: 2017-11-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -72,8 +72,17 @@ files:
72
72
  - README.md
73
73
  - docs/index.asciidoc
74
74
  - lib/logstash/filters/ruby.rb
75
+ - lib/logstash/filters/ruby/script.rb
76
+ - lib/logstash/filters/ruby/script/context.rb
77
+ - lib/logstash/filters/ruby/script/execution_context.rb
78
+ - lib/logstash/filters/ruby/script/expect_context.rb
79
+ - lib/logstash/filters/ruby/script/test_context.rb
80
+ - lib/logstash/filters/ruby/script_error.rb
75
81
  - logstash-filter-ruby.gemspec
76
82
  - spec/filters/ruby_spec.rb
83
+ - spec/fixtures/broken.rb
84
+ - spec/fixtures/field_multiplier.rb
85
+ - spec/spec_helper.rb
77
86
  homepage: http://www.elastic.co/guide/en/logstash/current/index.html
78
87
  licenses:
79
88
  - Apache License (2.0)
@@ -102,3 +111,6 @@ specification_version: 4
102
111
  summary: Filter for executing ruby code on the event
103
112
  test_files:
104
113
  - spec/filters/ruby_spec.rb
114
+ - spec/fixtures/broken.rb
115
+ - spec/fixtures/field_multiplier.rb
116
+ - spec/spec_helper.rb