logstash-filter-grok 4.2.0 → 4.3.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
  SHA256:
3
- metadata.gz: 9a139ac82c3147d778c2f4510cf6ab077c6c4e73adeae87a0b4058623c4a0619
4
- data.tar.gz: 02300cea3c6a17e10947cf63a7dae596c1ef1fc1c5114f3834ac784536a2941a
3
+ metadata.gz: a9d8be42f7b846a0684d25d18ec624a81574b52e394df863afa004d458ecd915
4
+ data.tar.gz: 605d0cea73b747d9900bebfc2a99f59d2950300af7ef52ec5b9cb3f556a7d5fd
5
5
  SHA512:
6
- metadata.gz: e822b6f1aca31141d7c3d553167a27ea1c11ba41e39dd9553fdbecfa3472780ccca75054b2247985261634efb3263a62fb7ca87f5762e418f744ef45bf994889
7
- data.tar.gz: 990df0541f9a5cdb09d6fea2060c1c68e901e7cdf1cdb0fd45cfcf32dfa0710da3eeddd7fb3400441489ccc1741f0380daf5934b15e0fbba9ccc2d4a6b370c37
6
+ metadata.gz: '096791bcc8691300c8e59aa8c6741d499c515b6fc70b3f7e4deea0c23440bce1875d98d7aaad0d6f8d8ab12dc526051e46e2a61b590422d646d7939221ec3fc7'
7
+ data.tar.gz: efe3dcc313d22e67986828351b365b9383c5c91d0a4caf8da78bd8c34126e7088d81ce9c672a6dc0758a9c3c4d1e4a2cae53e2df2c79bcd510848b69ecac9945
@@ -1,3 +1,6 @@
1
+ ## 4.3.0
2
+ - Added: added target support [#156](https://github.com/logstash-plugins/logstash-filter-grok/pull/156)
3
+
1
4
  ## 4.2.0
2
5
  - Added: support for timeout_scope [#153](https://github.com/logstash-plugins/logstash-filter-grok/pull/153)
3
6
 
data/Gemfile CHANGED
@@ -9,3 +9,9 @@ if Dir.exist?(logstash_path) && use_logstash_source
9
9
  gem 'logstash-core', :path => "#{logstash_path}/logstash-core"
10
10
  gem 'logstash-core-plugin-api', :path => "#{logstash_path}/logstash-core-plugin-api"
11
11
  end
12
+
13
+ group :test do
14
+ gem 'rspec-benchmark', :require => false if RUBY_VERSION >= '2.3'
15
+ gem 'logstash-input-generator', :require => false
16
+ gem 'logstash-output-null', :require => false
17
+ end
@@ -345,6 +345,14 @@ successful match
345
345
 
346
346
  Tag to apply if a grok regexp times out.
347
347
 
348
+ [id="plugins-{type}s-{plugin}-target"]
349
+ ===== `target`
350
+
351
+ * Value type is <<string,string>>
352
+ * There is no default value for this setting
353
+
354
+ Define target namespace for placing matches.
355
+
348
356
  [id="plugins-{type}s-{plugin}-timeout_millis"]
349
357
  ===== `timeout_millis`
350
358
 
@@ -204,6 +204,10 @@
204
204
  # If `true`, keep empty captures as event fields.
205
205
  config :keep_empty_captures, :validate => :boolean, :default => false
206
206
 
207
+ # Define the target field for placing the matched captures.
208
+ # If this setting is omitted, data gets stored at the root (top level) of the event.
209
+ config :target, :validate => :string
210
+
207
211
  # Append values to the `tags` field when there has been no
208
212
  # successful match
209
213
  config :tag_on_failure, :validate => :array, :default => ["_grokparsefailure"]
@@ -288,6 +292,8 @@
288
292
  @match_counter = metric.counter(:matches)
289
293
  @failure_counter = metric.counter(:failures)
290
294
 
295
+ @target = "[#{@target.strip}]" if @target && @target !~ /\[.*?\]/
296
+
291
297
  @timeout = @timeout_millis > 0.0 ? RubyTimeout.new(@timeout_millis) : NoopTimeout::INSTANCE
292
298
  @matcher = ( @timeout_scope.eql?('event') ? EventTimeoutMatcher : PatternTimeoutMatcher ).new(self)
293
299
  end # def register
@@ -398,22 +404,26 @@
398
404
  def handle(field, value, event)
399
405
  return if (value.nil? || (value.is_a?(String) && value.empty?)) unless @keep_empty_captures
400
406
 
407
+ target_field = @target ? "#{@target}[#{field}]" : field
408
+
401
409
  if @overwrite.include?(field)
402
- event.set(field, value)
410
+ event.set(target_field, value)
403
411
  else
404
- v = event.get(field)
412
+ v = event.get(target_field)
405
413
  if v.nil?
406
- event.set(field, value)
414
+ event.set(target_field, value)
407
415
  elsif v.is_a?(Array)
408
416
  # do not replace the code below with:
409
417
  # event[field] << value
410
418
  # this assumes implementation specific feature of returning a mutable object
411
419
  # from a field ref which should not be assumed and will change in the future.
412
420
  v << value
413
- event.set(field, v)
421
+ event.set(target_field, v)
414
422
  elsif v.is_a?(String)
415
423
  # Promote to array since we aren't overwriting.
416
- event.set(field, [v, value])
424
+ event.set(target_field, [v, value])
425
+ else
426
+ @logger.debug("Not adding matched value - found existing (#{v.class})", :field => target_field, :value => value)
417
427
  end
418
428
  end
419
429
  end
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
 
3
3
  s.name = 'logstash-filter-grok'
4
- s.version = '4.2.0'
4
+ s.version = '4.3.0'
5
5
  s.licenses = ['Apache License (2.0)']
6
6
  s.summary = "Parses unstructured event data into fields"
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"
@@ -0,0 +1,150 @@
1
+ # encoding: utf-8
2
+ require_relative "../spec_helper"
3
+
4
+ begin
5
+ require "rspec-benchmark"
6
+ rescue LoadError # due testing against LS 5.x
7
+ end
8
+ RSpec.configure do |config|
9
+ config.include RSpec::Benchmark::Matchers if defined? RSpec::Benchmark::Matchers
10
+ end
11
+
12
+ require "logstash/filters/grok"
13
+
14
+ describe LogStash::Filters::Grok do
15
+
16
+ subject do
17
+ described_class.new(config).tap { |filter| filter.register }
18
+ end
19
+
20
+ EVENT_COUNT = 300_000
21
+
22
+ describe "base-line performance", :performance => true do
23
+
24
+ EXPECTED_MIN_RATE = 15_000 # per second
25
+ # NOTE: based on Travis CI (docker) numbers :
26
+ # logstash_1_d010d1d29244 | LogStash::Filters::Grok
27
+ # logstash_1_d010d1d29244 | base-line performance
28
+ # logstash_1_d010d1d29244 | filters/grok parse rate: 14464/sec, elapsed: 20.740866999999998s
29
+ # logstash_1_d010d1d29244 | filters/grok parse rate: 29957/sec, elapsed: 10.014199s
30
+ # logstash_1_d010d1d29244 | filters/grok parse rate: 32932/sec, elapsed: 9.109601999999999s
31
+
32
+ let(:config) do
33
+ { 'match' => { "message" => "%{SYSLOGLINE}" }, 'overwrite' => [ "message" ] }
34
+ end
35
+
36
+ it "matches at least #{EXPECTED_MIN_RATE} events/second" do
37
+ max_duration = EVENT_COUNT / EXPECTED_MIN_RATE
38
+ message = "Mar 16 00:01:25 evita postfix/smtpd[1713]: connect from camomile.cloud9.net[168.100.1.3]"
39
+ expect do
40
+ duration = measure do
41
+ EVENT_COUNT.times { subject.filter(LogStash::Event.new("message" => message)) }
42
+ end
43
+ puts "filters/grok parse rate: #{"%02.0f/sec" % (EVENT_COUNT / duration)}, elapsed: #{duration}s"
44
+ end.to perform_under(max_duration).warmup(1).sample(2).times
45
+ end
46
+
47
+ end
48
+
49
+ describe "timeout", :performance => true do
50
+
51
+ ACCEPTED_TIMEOUT_DEGRADATION = 100 # in % (compared to timeout-less run)
52
+ # TODO: with more real-world (pipeline) setup this usually gets bellow 10% on average
53
+
54
+ MATCH_PATTERNS = {
55
+ "message" => [
56
+ "foo0: %{NUMBER:bar}", "foo1: %{NUMBER:bar}", "foo2: %{NUMBER:bar}", "foo3: %{NUMBER:bar}", "foo4: %{NUMBER:bar}",
57
+ "foo5: %{NUMBER:bar}", "foo6: %{NUMBER:bar}", "foo7: %{NUMBER:bar}", "foo8: %{NUMBER:bar}", "foo9: %{NUMBER:bar}",
58
+ "%{SYSLOGLINE}"
59
+ ]
60
+ }
61
+
62
+ SAMPLE_MESSAGE = "Mar 16 00:01:25 evita postfix/smtpd[1713]: connect from aaaaaaaa.aaaaaa.net[111.111.11.1]".freeze
63
+
64
+ TIMEOUT_MILLIS = 5_000
65
+
66
+ let(:config_wout_timeout) do
67
+ {
68
+ 'match' => MATCH_PATTERNS,
69
+ 'timeout_scope' => "event",
70
+ 'timeout_millis' => 0 # 0 - disabled timeout
71
+ }
72
+ end
73
+
74
+ let(:config_with_timeout) do
75
+ {
76
+ 'match' => MATCH_PATTERNS,
77
+ 'timeout_scope' => "event",
78
+ 'timeout_millis' => TIMEOUT_MILLIS
79
+ }
80
+ end
81
+
82
+ SAMPLE_COUNT = 2
83
+
84
+ it "has less than #{ACCEPTED_TIMEOUT_DEGRADATION}% overhead" do
85
+ filter_wout_timeout = LogStash::Filters::Grok.new(config_wout_timeout).tap(&:register)
86
+ wout_timeout_duration = do_sample_filter(filter_wout_timeout) # warmup
87
+ puts "filters/grok(timeout => 0) warmed up in #{wout_timeout_duration}"
88
+ before_sample!
89
+ no_timeout_durations = Array.new(SAMPLE_COUNT).map do
90
+ duration = do_sample_filter(filter_wout_timeout)
91
+ puts "filters/grok(timeout => 0) took #{duration}"
92
+ duration
93
+ end
94
+
95
+ expected_duration = avg(no_timeout_durations)
96
+ expected_duration += (expected_duration / 100) * ACCEPTED_TIMEOUT_DEGRADATION
97
+ puts "expected_duration #{expected_duration}"
98
+
99
+ filter_with_timeout = LogStash::Filters::Grok.new(config_with_timeout).tap(&:register)
100
+ with_timeout_duration = do_sample_filter(filter_with_timeout) # warmup
101
+ puts "filters/grok(timeout_scope => event) warmed up in #{with_timeout_duration}"
102
+
103
+ before_sample!
104
+ expect do
105
+ duration = do_sample_filter(filter_with_timeout)
106
+ puts "filters/grok(timeout_scope => event) took #{duration}"
107
+ duration
108
+ end.to perform_under(expected_duration).sample(SAMPLE_COUNT).times
109
+ end
110
+
111
+ @private
112
+
113
+ def do_sample_filter(filter)
114
+ sample_event = { "message" => SAMPLE_MESSAGE }
115
+ measure do
116
+ for _ in (1..EVENT_COUNT) do # EVENT_COUNT.times without the block cost
117
+ filter.filter(LogStash::Event.new(sample_event))
118
+ end
119
+ end
120
+ end
121
+
122
+ def sample_event(hash)
123
+ LogStash::Event.new("message" => SAMPLE_MESSAGE)
124
+ end
125
+
126
+ end
127
+
128
+ @private
129
+
130
+ def measure
131
+ start = Time.now
132
+ yield
133
+ Time.now - start
134
+ end
135
+
136
+ def avg(ary)
137
+ ary.inject(0) { |m, i| m + i } / ary.size.to_f
138
+ end
139
+
140
+ def before_sample!
141
+ 2.times { JRuby.gc }
142
+ sleep TIMEOUT_MILLIS / 1000
143
+ end
144
+
145
+ def sleep(seconds)
146
+ puts "sleeping for #{seconds} seconds (redundant - potential timeout propagation)"
147
+ Kernel.sleep(seconds)
148
+ end
149
+
150
+ end
@@ -1,392 +1,357 @@
1
1
  # encoding: utf-8
2
- require "logstash/devutils/rspec/spec_helper"
3
- require "stud/temporary"
2
+ require_relative "../spec_helper"
4
3
 
5
- module LogStash::Environment
6
- # running the grok code outside a logstash package means
7
- # LOGSTASH_HOME will not be defined, so let's set it here
8
- # before requiring the grok filter
9
- unless self.const_defined?(:LOGSTASH_HOME)
10
- LOGSTASH_HOME = File.expand_path("../../../", __FILE__)
4
+ require "logstash/filters/grok"
5
+
6
+ describe LogStash::Filters::Grok do
7
+ subject { described_class.new(config) }
8
+ let(:config) { {} }
9
+ let(:event) { LogStash::Event.new(data) }
10
+ let(:data) { { "message" => message } }
11
+
12
+ before(:each) do
13
+ subject.register
14
+ subject.filter(event)
11
15
  end
12
16
 
13
- # also :pattern_path method must exist so we define it too
14
- unless self.method_defined?(:pattern_path)
15
- def pattern_path(path)
16
- ::File.join(LOGSTASH_HOME, "patterns", path)
17
+ def self.sample(message, &block)
18
+ # mod = RSpec::Core::MemoizedHelpers.module_for(self)
19
+ # mod.attr_reader :message
20
+ # # mod.__send__(:define_method, :message) { message }
21
+ # it("matches: #{message}") { @message = message; block.call }
22
+ describe message do
23
+ let(:message) { message }
24
+ it("groks", &block)
17
25
  end
18
26
  end
19
- end
20
-
21
- require "logstash/filters/grok"
22
-
23
- describe LogStash::Filters::Grok do
24
27
 
25
28
  describe "simple syslog line" do
26
- # The logstash config goes here.
27
- # At this time, only filters are supported.
28
- config <<-CONFIG
29
- filter {
30
- grok {
31
- match => { "message" => "%{SYSLOGLINE}" }
32
- overwrite => [ "message" ]
33
- }
34
- }
35
- CONFIG
29
+ let(:config) { { "match" => { "message" => "%{SYSLOGLINE}" }, "overwrite" => [ "message" ] } }
30
+ let(:message) { 'Mar 16 00:01:25 evita postfix/smtpd[1713]: connect from camomile.cloud9.net[168.100.1.3]' }
31
+
32
+ it "matches pattern" do
33
+ expect( event.get("tags") ).to be nil
34
+ expect( event.get("logsource") ).to eql "evita"
35
+ expect( event.get("timestamp") ).to eql "Mar 16 00:01:25"
36
+ expect( event.get("message") ).to eql "connect from camomile.cloud9.net[168.100.1.3]"
37
+ expect( event.get("program") ).to eql "postfix/smtpd"
38
+ expect( event.get("pid") ).to eql "1713"
39
+ end
40
+
41
+ context 'with target' do
42
+ let(:config) { { "match" => { "message" => "%{SYSLOGLINE}" }, "target" => "grok" } }
43
+
44
+ it "matches pattern" do
45
+ expect( event.get("message") ).to eql message
46
+ expect( event.get("tags") ).to be nil
47
+ expect( event.get("grok") ).to_not be nil
48
+ expect( event.get("[grok][timestamp]") ).to eql "Mar 16 00:01:25"
49
+ expect( event.get("[grok][message]") ).to eql "connect from camomile.cloud9.net[168.100.1.3]"
50
+ expect( event.get("[grok][pid]") ).to eql "1713"
51
+ end
52
+ end
53
+
54
+ context 'with [deep] target' do
55
+ let(:config) { { "match" => { "message" => "%{SYSLOGLINE}" }, "target" => "[@metadata][grok]" } }
36
56
 
37
- sample "Mar 16 00:01:25 evita postfix/smtpd[1713]: connect from camomile.cloud9.net[168.100.1.3]" do
38
- insist { subject.get("tags") }.nil?
39
- insist { subject.get("logsource") } == "evita"
40
- insist { subject.get("timestamp") } == "Mar 16 00:01:25"
41
- insist { subject.get("message") } == "connect from camomile.cloud9.net[168.100.1.3]"
42
- insist { subject.get("program") } == "postfix/smtpd"
43
- insist { subject.get("pid") } == "1713"
57
+ it "matches pattern" do
58
+ expect( event.get("message") ).to eql message
59
+ expect( event.get("tags") ).to be nil
60
+ expect( event.get("grok") ).to be nil
61
+ expect( event.get("[@metadata][grok][logsource]") ).to eql "evita"
62
+ expect( event.get("[@metadata][grok][message]") ).to eql "connect from camomile.cloud9.net[168.100.1.3]"
63
+ end
44
64
  end
45
65
  end
46
66
 
47
67
  describe "ietf 5424 syslog line" do
48
- # The logstash config goes here.
49
- # At this time, only filters are supported.
50
- config <<-CONFIG
51
- filter {
52
- grok {
53
- match => { "message" => "%{SYSLOG5424LINE}" }
54
- }
55
- }
56
- CONFIG
68
+ let(:config) { { "match" => { "message" => "%{SYSLOG5424LINE}" } } }
57
69
 
58
70
  sample "<191>1 2009-06-30T18:30:00+02:00 paxton.local grokdebug 4123 - [id1 foo=\"bar\"][id2 baz=\"something\"] Hello, syslog." do
59
- insist { subject.get("tags") }.nil?
60
- insist { subject.get("syslog5424_pri") } == "191"
61
- insist { subject.get("syslog5424_ver") } == "1"
62
- insist { subject.get("syslog5424_ts") } == "2009-06-30T18:30:00+02:00"
63
- insist { subject.get("syslog5424_host") } == "paxton.local"
64
- insist { subject.get("syslog5424_app") } == "grokdebug"
65
- insist { subject.get("syslog5424_proc") } == "4123"
66
- insist { subject.get("syslog5424_msgid") } == nil
67
- insist { subject.get("syslog5424_sd") } == "[id1 foo=\"bar\"][id2 baz=\"something\"]"
68
- insist { subject.get("syslog5424_msg") } == "Hello, syslog."
71
+ expect( event.get("tags") ).to be nil
72
+ expect( event.get("syslog5424_pri") ).to eql "191"
73
+ expect( event.get("syslog5424_ver") ).to eql "1"
74
+ expect( event.get("syslog5424_ts") ).to eql "2009-06-30T18:30:00+02:00"
75
+ expect( event.get("syslog5424_host") ).to eql "paxton.local"
76
+ expect( event.get("syslog5424_app") ).to eql "grokdebug"
77
+ expect( event.get("syslog5424_proc") ).to eql "4123"
78
+ expect( event.get("syslog5424_msgid") ).to be nil
79
+ expect( event.get("syslog5424_sd") ).to eql "[id1 foo=\"bar\"][id2 baz=\"something\"]"
80
+ expect( event.get("syslog5424_msg") ).to eql "Hello, syslog."
69
81
  end
70
82
 
71
83
  sample "<191>1 2009-06-30T18:30:00+02:00 paxton.local grokdebug - - [id1 foo=\"bar\"] No process ID." do
72
- insist { subject.get("tags") }.nil?
73
- insist { subject.get("syslog5424_pri") } == "191"
74
- insist { subject.get("syslog5424_ver") } == "1"
75
- insist { subject.get("syslog5424_ts") } == "2009-06-30T18:30:00+02:00"
76
- insist { subject.get("syslog5424_host") } == "paxton.local"
77
- insist { subject.get("syslog5424_app") } == "grokdebug"
78
- insist { subject.get("syslog5424_proc") } == nil
79
- insist { subject.get("syslog5424_msgid") } == nil
80
- insist { subject.get("syslog5424_sd") } == "[id1 foo=\"bar\"]"
81
- insist { subject.get("syslog5424_msg") } == "No process ID."
84
+ expect( event.get("tags") ).to be nil
85
+ expect( event.get("syslog5424_pri") ).to eql "191"
86
+ expect( event.get("syslog5424_ver") ).to eql "1"
87
+ expect( event.get("syslog5424_ts") ).to eql "2009-06-30T18:30:00+02:00"
88
+ expect( event.get("syslog5424_host") ).to eql "paxton.local"
89
+ expect( event.get("syslog5424_app") ).to eql "grokdebug"
90
+ expect( event.get("syslog5424_proc") ).to be nil
91
+ expect( event.get("syslog5424_msgid") ).to be nil
92
+ expect( event.get("syslog5424_sd") ).to eql "[id1 foo=\"bar\"]"
93
+ expect( event.get("syslog5424_msg") ).to eql "No process ID."
82
94
  end
83
95
 
84
96
  sample "<191>1 2009-06-30T18:30:00+02:00 paxton.local grokdebug 4123 - - No structured data." do
85
- insist { subject.get("tags") }.nil?
86
- insist { subject.get("syslog5424_pri") } == "191"
87
- insist { subject.get("syslog5424_ver") } == "1"
88
- insist { subject.get("syslog5424_ts") } == "2009-06-30T18:30:00+02:00"
89
- insist { subject.get("syslog5424_host") } == "paxton.local"
90
- insist { subject.get("syslog5424_app") } == "grokdebug"
91
- insist { subject.get("syslog5424_proc") } == "4123"
92
- insist { subject.get("syslog5424_msgid") } == nil
93
- insist { subject.get("syslog5424_sd") } == nil
94
- insist { subject.get("syslog5424_msg") } == "No structured data."
97
+ expect( event.get("tags") ).to be nil
98
+ expect( event.get("syslog5424_pri") ).to eql "191"
99
+ expect( event.get("syslog5424_ver") ).to eql "1"
100
+ expect( event.get("syslog5424_ts") ).to eql "2009-06-30T18:30:00+02:00"
101
+ expect( event.get("syslog5424_host") ).to eql "paxton.local"
102
+ expect( event.get("syslog5424_app") ).to eql "grokdebug"
103
+ expect( event.get("syslog5424_proc") ).to eql '4123'
104
+ expect( event.get("syslog5424_msgid") ).to be nil
105
+ expect( event.get("syslog5424_sd") ).to be nil
106
+ expect( event.get("syslog5424_msg") ).to eql "No structured data."
95
107
  end
96
108
 
97
109
  sample "<191>1 2009-06-30T18:30:00+02:00 paxton.local grokdebug - - - No PID or SD." do
98
- insist { subject.get("tags") }.nil?
99
- insist { subject.get("syslog5424_pri") } == "191"
100
- insist { subject.get("syslog5424_ver") } == "1"
101
- insist { subject.get("syslog5424_ts") } == "2009-06-30T18:30:00+02:00"
102
- insist { subject.get("syslog5424_host") } == "paxton.local"
103
- insist { subject.get("syslog5424_app") } == "grokdebug"
104
- insist { subject.get("syslog5424_proc") } == nil
105
- insist { subject.get("syslog5424_msgid") } == nil
106
- insist { subject.get("syslog5424_sd") } == nil
107
- insist { subject.get("syslog5424_msg") } == "No PID or SD."
110
+ expect( event.get("tags") ).to be nil
111
+ expect( event.get("syslog5424_pri") ).to eql "191"
112
+ expect( event.get("syslog5424_ver") ).to eql "1"
113
+ expect( event.get("syslog5424_ts") ).to eql "2009-06-30T18:30:00+02:00"
114
+ expect( event.get("syslog5424_host") ).to eql "paxton.local"
115
+ expect( event.get("syslog5424_app") ).to eql "grokdebug"
116
+ expect( event.get("syslog5424_proc") ).to be nil
117
+ expect( event.get("syslog5424_msgid") ).to be nil
118
+ expect( event.get("syslog5424_sd") ).to be nil
119
+ expect( event.get("syslog5424_msg") ).to eql "No PID or SD."
108
120
  end
109
121
 
110
122
  sample "<191>1 2009-06-30T18:30:00+02:00 paxton.local grokdebug 4123 - Missing structured data." do
111
- insist { subject.get("tags") }.nil?
112
- insist { subject.get("syslog5424_pri") } == "191"
113
- insist { subject.get("syslog5424_ver") } == "1"
114
- insist { subject.get("syslog5424_ts") } == "2009-06-30T18:30:00+02:00"
115
- insist { subject.get("syslog5424_host") } == "paxton.local"
116
- insist { subject.get("syslog5424_app") } == "grokdebug"
117
- insist { subject.get("syslog5424_proc") } == "4123"
118
- insist { subject.get("syslog5424_msgid") } == nil
119
- insist { subject.get("syslog5424_sd") } == nil
120
- insist { subject.get("syslog5424_msg") } == "Missing structured data."
123
+ expect( event.get("tags") ).to be nil
124
+
125
+ expect( event.get("syslog5424_proc") ).to eql '4123'
126
+ expect( event.get("syslog5424_msgid") ).to be nil
127
+ expect( event.get("syslog5424_sd") ).to be nil
128
+ expect( event.get("syslog5424_msg") ).to eql "Missing structured data."
121
129
  end
122
130
 
123
131
  sample "<191>1 2009-06-30T18:30:00+02:00 paxton.local grokdebug 4123 - - Additional spaces." do
124
- insist { subject.get("tags") }.nil?
125
- insist { subject.get("syslog5424_pri") } == "191"
126
- insist { subject.get("syslog5424_ver") } == "1"
127
- insist { subject.get("syslog5424_ts") } == "2009-06-30T18:30:00+02:00"
128
- insist { subject.get("syslog5424_host") } == "paxton.local"
129
- insist { subject.get("syslog5424_app") } == "grokdebug"
130
- insist { subject.get("syslog5424_proc") } == "4123"
131
- insist { subject.get("syslog5424_msgid") } == nil
132
- insist { subject.get("syslog5424_sd") } == nil
133
- insist { subject.get("syslog5424_msg") } == "Additional spaces."
132
+ expect( event.get("tags") ).to be nil
133
+
134
+ expect( event.get("syslog5424_app") ).to eql "grokdebug"
135
+ expect( event.get("syslog5424_proc") ).to eql '4123'
136
+ expect( event.get("syslog5424_msgid") ).to be nil
137
+ expect( event.get("syslog5424_sd") ).to be nil
138
+ expect( event.get("syslog5424_msg") ).to eql "Additional spaces."
134
139
  end
135
140
 
136
141
  sample "<191>1 2009-06-30T18:30:00+02:00 paxton.local grokdebug 4123 - Additional spaces and missing SD." do
137
- insist { subject.get("tags") }.nil?
138
- insist { subject.get("syslog5424_pri") } == "191"
139
- insist { subject.get("syslog5424_ver") } == "1"
140
- insist { subject.get("syslog5424_ts") } == "2009-06-30T18:30:00+02:00"
141
- insist { subject.get("syslog5424_host") } == "paxton.local"
142
- insist { subject.get("syslog5424_app") } == "grokdebug"
143
- insist { subject.get("syslog5424_proc") } == "4123"
144
- insist { subject.get("syslog5424_msgid") } == nil
145
- insist { subject.get("syslog5424_sd") } == nil
146
- insist { subject.get("syslog5424_msg") } == "Additional spaces and missing SD."
142
+ expect( event.get("tags") ).to be nil
143
+
144
+ expect( event.get("syslog5424_app") ).to eql "grokdebug"
145
+ expect( event.get("syslog5424_proc") ).to eql '4123'
146
+ expect( event.get("syslog5424_msgid") ).to be nil
147
+ expect( event.get("syslog5424_sd") ).to be nil
148
+ expect( event.get("syslog5424_msg") ).to eql "Additional spaces and missing SD."
147
149
  end
148
150
 
149
151
  sample "<30>1 2014-04-04T16:44:07+02:00 osctrl01 dnsmasq-dhcp 8048 - - Appname contains a dash" do
150
- insist { subject.get("tags") }.nil?
151
- insist { subject.get("syslog5424_pri") } == "30"
152
- insist { subject.get("syslog5424_ver") } == "1"
153
- insist { subject.get("syslog5424_ts") } == "2014-04-04T16:44:07+02:00"
154
- insist { subject.get("syslog5424_host") } == "osctrl01"
155
- insist { subject.get("syslog5424_app") } == "dnsmasq-dhcp"
156
- insist { subject.get("syslog5424_proc") } == "8048"
157
- insist { subject.get("syslog5424_msgid") } == nil
158
- insist { subject.get("syslog5424_sd") } == nil
159
- insist { subject.get("syslog5424_msg") } == "Appname contains a dash"
152
+ expect( event.get("tags") ).to be nil
153
+ expect( event.get("syslog5424_pri") ).to eql "30"
154
+ expect( event.get("syslog5424_ver") ).to eql "1"
155
+ expect( event.get("syslog5424_ts") ).to eql "2014-04-04T16:44:07+02:00"
156
+ expect( event.get("syslog5424_host") ).to eql "osctrl01"
157
+ expect( event.get("syslog5424_app") ).to eql "dnsmasq-dhcp"
158
+ expect( event.get("syslog5424_proc") ).to eql "8048"
159
+ expect( event.get("syslog5424_msgid") ).to be nil
160
+ expect( event.get("syslog5424_sd") ).to be nil
161
+ expect( event.get("syslog5424_msg") ).to eql "Appname contains a dash"
160
162
  end
161
163
 
162
164
  sample "<30>1 2014-04-04T16:44:07+02:00 osctrl01 - 8048 - - Appname is nil" do
163
- insist { subject.get("tags") }.nil?
164
- insist { subject.get("syslog5424_pri") } == "30"
165
- insist { subject.get("syslog5424_ver") } == "1"
166
- insist { subject.get("syslog5424_ts") } == "2014-04-04T16:44:07+02:00"
167
- insist { subject.get("syslog5424_host") } == "osctrl01"
168
- insist { subject.get("syslog5424_app") } == nil
169
- insist { subject.get("syslog5424_proc") } == "8048"
170
- insist { subject.get("syslog5424_msgid") } == nil
171
- insist { subject.get("syslog5424_sd") } == nil
172
- insist { subject.get("syslog5424_msg") } == "Appname is nil"
165
+ expect( event.get("tags") ).to be nil
166
+ expect( event.get("syslog5424_pri") ).to eql "30"
167
+ expect( event.get("syslog5424_ver") ).to eql "1"
168
+ expect( event.get("syslog5424_ts") ).to eql "2014-04-04T16:44:07+02:00"
169
+ expect( event.get("syslog5424_host") ).to eql "osctrl01"
170
+ expect( event.get("syslog5424_app") ).to be nil
171
+ expect( event.get("syslog5424_proc") ).to eql "8048"
172
+ expect( event.get("syslog5424_msgid") ).to be nil
173
+ expect( event.get("syslog5424_sd") ).to be nil
174
+ expect( event.get("syslog5424_msg") ).to eql "Appname is nil"
173
175
  end
174
176
  end
175
177
 
176
- describe "parsing an event with multiple messages (array of strings)", :if => false do
177
- config <<-CONFIG
178
- filter {
179
- grok {
180
- match => { "message" => "(?:hello|world) %{NUMBER}" }
181
- named_captures_only => false
182
- }
183
- }
184
- CONFIG
178
+ describe "parsing an event with multiple messages (array of strings)", if: false do
179
+ let(:config) { { "message" => "(?:hello|world) %{NUMBER}" } }
180
+ let(:message) { [ "hello 12345", "world 23456" ] }
185
181
 
186
- sample("message" => [ "hello 12345", "world 23456" ]) do
187
- insist { subject.get("NUMBER") } == [ "12345", "23456" ]
182
+ it "matches them all" do
183
+ expect( event.get("NUMBER") ).to eql [ "12345", "23456" ]
188
184
  end
189
185
  end
190
186
 
191
187
  describe "coercing matched values" do
192
- config <<-CONFIG
193
- filter {
194
- grok {
195
- match => { "message" => "%{NUMBER:foo:int} %{NUMBER:bar:float}" }
196
- }
197
- }
198
- CONFIG
188
+ let(:config) { { "match" => { "message" => "%{NUMBER:foo:int} %{NUMBER:bar:float}" } } }
189
+ let(:message) { '400 454.33' }
199
190
 
200
- sample "400 454.33" do
201
- insist { subject.get("foo") } == 400
202
- insist { subject.get("foo") }.is_a?(Integer)
203
- insist { subject.get("bar") } == 454.33
204
- insist { subject.get("bar") }.is_a?(Float)
191
+ it "coerces matched values" do
192
+ expect( event.get("foo") ).to be_a Integer
193
+ expect( event.get("foo") ).to eql 400
194
+ expect( event.get("bar") ).to be_a Float
195
+ expect( event.get("bar") ).to eql 454.33
205
196
  end
206
197
  end
207
198
 
208
199
  describe "in-line pattern definitions" do
209
- config <<-CONFIG
210
- filter {
211
- grok {
212
- match => { "message" => "%{FIZZLE=\\d+}" }
213
- named_captures_only => false
214
- }
215
- }
216
- CONFIG
200
+ let(:config) { { "match" => { "message" => "%{FIZZLE=\\d+}" }, "named_captures_only" => false } }
217
201
 
218
202
  sample "hello 1234" do
219
- insist { subject.get("FIZZLE") } == "1234"
203
+ expect( event.get("FIZZLE") ).to eql '1234'
220
204
  end
221
205
  end
222
206
 
223
207
  describe "processing selected fields" do
224
- config <<-CONFIG
225
- filter {
226
- grok {
227
- match => { "message" => "%{WORD:word}" }
228
- match => { "examplefield" => "%{NUMBER:num}" }
229
- break_on_match => false
230
- }
208
+ let(:config) {
209
+ {
210
+ 'match' => { "message" => "%{WORD:word}", "examplefield" => "%{NUMBER:num}" },
211
+ 'break_on_match' => false
231
212
  }
232
- CONFIG
213
+ }
214
+ let(:data) { { "message" => "hello world", "examplefield" => "12345" } }
233
215
 
234
- sample("message" => "hello world", "examplefield" => "12345") do
235
- insist { subject.get("examplefield") } == "12345"
236
- insist { subject.get("word") } == "hello"
216
+ it "processes declared matches" do
217
+ expect( event.get("word") ).to eql 'hello'
218
+ expect( event.get("examplefield") ).to eql '12345'
237
219
  end
238
220
  end
239
221
 
240
222
  describe "adding fields on match" do
241
- config <<-CONFIG
242
- filter {
243
- grok {
244
- match => { "message" => "matchme %{NUMBER:fancy}" }
245
- add_field => [ "new_field", "%{fancy}" ]
246
- }
223
+ let(:config) {
224
+ {
225
+ 'match' => { "message" => "matchme %{NUMBER:fancy}" },
226
+ 'add_field' => [ "new_field", "%{fancy}" ]
247
227
  }
248
- CONFIG
228
+ }
249
229
 
250
230
  sample "matchme 1234" do
251
- insist { subject.get("tags") }.nil?
252
- insist { subject.get("new_field") } == "1234"
231
+ expect( event.get("tags") ).to be nil
232
+ expect( event.get("new_field") ).to eql "1234"
253
233
  end
254
234
 
255
235
  sample "this will not be matched" do
256
- insist { subject.get("tags") }.include?("_grokparsefailure")
257
- reject { subject }.include?("new_field")
236
+ expect( event.get("tags") ).to include("_grokparsefailure")
237
+ expect( event ).not_to include 'new_field'
258
238
  end
259
239
  end
260
240
 
261
241
  context "empty fields" do
262
242
  describe "drop by default" do
263
- config <<-CONFIG
264
- filter {
265
- grok {
266
- match => { "message" => "1=%{WORD:foo1} *(2=%{WORD:foo2})?" }
267
- }
243
+ let(:config) {
244
+ {
245
+ 'match' => { "message" => "1=%{WORD:foo1} *(2=%{WORD:foo2})?" }
268
246
  }
269
- CONFIG
247
+ }
270
248
 
271
249
  sample "1=test" do
272
- insist { subject.get("tags") }.nil?
273
- insist { subject }.include?("foo1")
250
+ expect( event.get("tags") ).to be nil
251
+ expect( event ).to include 'foo1'
274
252
 
275
253
  # Since 'foo2' was not captured, it must not be present in the event.
276
- reject { subject }.include?("foo2")
254
+ expect( event ).not_to include 'foo2'
277
255
  end
278
256
  end
279
257
 
280
258
  describe "keep if keep_empty_captures is true" do
281
- config <<-CONFIG
282
- filter {
283
- grok {
284
- match => { "message" => "1=%{WORD:foo1} *(2=%{WORD:foo2})?" }
285
- keep_empty_captures => true
286
- }
259
+ let(:config) {
260
+ {
261
+ 'match' => { "message" => "1=%{WORD:foo1} *(2=%{WORD:foo2})?" },
262
+ 'keep_empty_captures' => true
287
263
  }
288
- CONFIG
264
+ }
289
265
 
290
266
  sample "1=test" do
291
- insist { subject.get("tags") }.nil?
267
+ expect( event.get("tags") ).to be nil
292
268
  # use .to_hash for this test, for now, because right now
293
269
  # the Event.include? returns false for missing fields as well
294
270
  # as for fields with nil values.
295
- insist { subject.to_hash }.include?("foo2")
296
- insist { subject.to_hash }.include?("foo2")
271
+ expect( event.to_hash ).to include 'foo1'
272
+ expect( event.to_hash ).to include 'foo2'
297
273
  end
298
274
  end
299
275
  end
300
276
 
301
277
  describe "when named_captures_only == false" do
302
- config <<-CONFIG
303
- filter {
304
- grok {
305
- match => { "message" => "Hello %{WORD}. %{WORD:foo}" }
306
- named_captures_only => false
307
- }
278
+ let(:config) {
279
+ {
280
+ 'match' => { "message" => "Hello %{WORD}. %{WORD:foo}" },
281
+ 'named_captures_only' => false
308
282
  }
309
- CONFIG
283
+ }
310
284
 
311
285
  sample "Hello World, yo!" do
312
- insist { subject }.include?("WORD")
313
- insist { subject.get("WORD") } == "World"
314
- insist { subject }.include?("foo")
315
- insist { subject.get("foo") } == "yo"
286
+ expect( event ).to include 'WORD'
287
+ expect( event.get("WORD") ).to eql "World"
288
+ expect( event ).to include 'foo'
289
+ expect( event.get("foo") ).to eql "yo"
316
290
  end
317
291
  end
318
292
 
319
293
  describe "using oniguruma named captures (?<name>regex)" do
320
294
  context "plain regexp" do
321
- config <<-'CONFIG'
322
- filter {
323
- grok {
324
- match => { "message" => "(?<foo>\w+)" }
325
- }
295
+ let(:config) {
296
+ {
297
+ 'match' => { "message" => "(?<foo>\\w+)" }
326
298
  }
327
- CONFIG
299
+ }
300
+
328
301
  sample "hello world" do
329
- insist { subject.get("tags") }.nil?
330
- insist { subject.get("foo") } == "hello"
302
+ expect( event.get("tags") ).to be nil
303
+ expect( event.get("foo") ).to eql "hello"
331
304
  end
332
305
  end
333
306
 
334
307
  context "grok patterns" do
335
- config <<-'CONFIG'
336
- filter {
337
- grok {
338
- match => { "message" => "(?<timestamp>%{DATE_EU} %{TIME})" }
339
- }
308
+ let(:config) {
309
+ {
310
+ 'match' => { "message" => "(?<timestamp>%{DATE_EU} %{TIME})" }
340
311
  }
341
- CONFIG
312
+ }
342
313
 
343
314
  sample "fancy 12-12-12 12:12:12" do
344
- insist { subject.get("tags") }.nil?
345
- insist { subject.get("timestamp") } == "12-12-12 12:12:12"
315
+ expect( event.get("tags") ).to be nil
316
+ expect( event.get("timestamp") ).to eql "12-12-12 12:12:12"
346
317
  end
347
318
  end
348
319
  end
349
320
 
350
321
  describe "grok on integer types" do
351
- config <<-'CONFIG'
352
- filter {
353
- grok {
354
- match => { "status" => "^403$" }
355
- add_tag => "four_oh_three"
356
- }
322
+ let(:config) {
323
+ {
324
+ 'match' => { "status" => "^403$" }, 'add_tag' => "four_oh_three"
357
325
  }
358
- CONFIG
326
+ }
327
+ let(:data) { Hash({ "status" => 403 }) }
359
328
 
360
- sample("status" => 403) do
361
- reject { subject.get("tags") }.include?("_grokparsefailure")
362
- insist { subject.get("tags") }.include?("four_oh_three")
329
+ it "parses" do
330
+ expect( event.get("tags") ).not_to include "_grokparsefailure"
331
+ expect( event.get("tags") ).to include "four_oh_three"
363
332
  end
364
333
  end
365
334
 
366
335
  describe "grok on float types" do
367
- config <<-'CONFIG'
368
- filter {
369
- grok {
370
- match => { "version" => "^1.0$" }
371
- add_tag => "one_point_oh"
372
- }
336
+ let(:config) {
337
+ {
338
+ 'match' => { "version" => "^1.0$" }, 'add_tag' => "one_point_oh"
373
339
  }
374
- CONFIG
340
+ }
341
+ let(:data) { Hash({ "version" => 1.0 }) }
375
342
 
376
- sample("version" => 1.0) do
377
- insist { subject.get("tags") }.include?("one_point_oh")
378
- insist { subject.get("tags") }.include?("one_point_oh")
343
+ it "parses" do
344
+ expect( event.get("tags") ).not_to include "_grokparsefailure"
345
+ expect( event.get("tags") ).to include "one_point_oh"
379
346
  end
380
347
  end
381
348
 
382
349
  describe "grok on %{LOGLEVEL}" do
383
- config <<-'CONFIG'
384
- filter {
385
- grok {
386
- match => { "message" => "%{LOGLEVEL:level}: error!" }
387
- }
350
+ let(:config) {
351
+ {
352
+ 'match' => { "message" => "%{LOGLEVEL:level}: error!" }
388
353
  }
389
- CONFIG
354
+ }
390
355
 
391
356
  log_level_names = %w(
392
357
  trace Trace TRACE
@@ -402,591 +367,528 @@ describe LogStash::Filters::Grok do
402
367
  )
403
368
  log_level_names.each do |level_name|
404
369
  sample "#{level_name}: error!" do
405
- insist { subject.get("level") } == level_name
370
+ expect( event.get("level") ).to eql level_name
406
371
  end
407
372
  end
408
373
  end
409
374
 
410
375
  describe "timeout on failure" do
411
- config <<-CONFIG
412
- filter {
413
- grok {
414
- match => {
415
- "message" => "(.*a){30}"
416
- }
417
- timeout_millis => 100
418
- }
376
+ let(:config) {
377
+ {
378
+ 'match' => { "message" => "(.*a){30}" },
379
+ 'timeout_millis' => 100
419
380
  }
420
- CONFIG
381
+ }
421
382
 
422
383
  sample "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" do
423
- expect(subject.get("tags")).to include("_groktimeout")
424
- expect(subject.get("tags")).not_to include("_grokparsefailure")
384
+ expect( event.get("tags") ).to include("_groktimeout")
385
+ expect( event.get("tags") ).not_to include("_grokparsefailure")
425
386
  end
426
387
  end
427
388
 
428
389
  describe "no timeout on failure with multiple patterns (when timeout not grouped)" do
429
- config <<-CONFIG
430
- filter {
431
- grok {
432
- match => {
433
- "message" => [
434
- "(.*f){20}", "(.*e){20}", "(.*d){20}", "(.*c){20}", "(.*b){20}",
435
- "(.*a){25}", "(.*a){24}", "(.*a){23}", "(.*a){22}", "(.*a){21}",
436
- "(.*a){20}"
437
- ]
438
- }
439
- timeout_millis => 500
440
- timeout_scope => 'pattern'
441
- }
442
- }
443
- CONFIG
390
+ let(:config) {
391
+ {
392
+ 'match' => {
393
+ "message" => [
394
+ "(.*f){20}", "(.*e){20}", "(.*d){20}", "(.*c){20}", "(.*b){20}",
395
+ "(.*a){25}", "(.*a){24}", "(.*a){23}", "(.*a){22}", "(.*a){21}",
396
+ "(.*a){20}"
397
+ ]
398
+ },
399
+ 'timeout_millis' => 500,
400
+ 'timeout_scope' => 'pattern'
401
+ }
402
+ }
444
403
 
445
404
  sample( 'b' * 10 + 'c' * 10 + 'd' * 10 + 'e' * 10 + ' ' + 'a' * 20 ) do
446
- insist { subject.get("tags") }.nil?
405
+ expect( event.get("tags") ).to be nil
447
406
  end
448
407
  end
449
408
 
450
409
  describe "timeout on grouped (multi-pattern) failure" do
451
- config <<-CONFIG
452
- filter {
453
- grok {
454
- match => {
455
- "message" => [
456
- "(.*f){20}", "(.*e){20}", "(.*d){20}", "(.*c){20}", "(.*b){20}",
457
- "(.*a){25}", "(.*a){24}", "(.*a){23}", "(.*a){22}", "(.*a){21}",
458
- "(.*a){20}"
459
- ]
460
- }
461
- timeout_millis => 500
462
- timeout_scope => 'event'
463
- }
464
- }
465
- CONFIG
410
+ let(:config) {
411
+ {
412
+ 'match' => {
413
+ "message" => [
414
+ "(.*f){20}", "(.*e){20}", "(.*d){20}", "(.*c){20}", "(.*b){20}",
415
+ "(.*a){25}", "(.*a){24}", "(.*a){23}", "(.*a){22}", "(.*a){21}",
416
+ "(.*a){20}"
417
+ ]
418
+ },
419
+ 'timeout_millis' => 500,
420
+ 'timeout_scope' => 'event'
421
+ }
422
+ }
466
423
 
467
424
  sample( 'b' * 10 + 'c' * 10 + 'd' * 10 + 'e' * 10 + ' ' + 'a' * 20 ) do
468
- expect(subject.get("tags")).to include("_groktimeout")
469
- expect(subject.get("tags")).not_to include("_grokparsefailure")
425
+ expect( event.get("tags") ).to include("_groktimeout")
426
+ expect( event.get("tags") ).not_to include("_grokparsefailure")
470
427
  end
471
428
  end
472
429
 
473
430
  describe "tagging on failure" do
474
- config <<-CONFIG
475
- filter {
476
- grok {
477
- match => { "message" => "matchme %{NUMBER:fancy}" }
478
- tag_on_failure => not_a_match
479
- }
431
+ let(:config) {
432
+ {
433
+ 'match' => { "message" => "matchme %{NUMBER:fancy}" },
434
+ 'tag_on_failure' => 'not_a_match'
480
435
  }
481
- CONFIG
436
+ }
482
437
 
483
438
  sample "matchme 1234" do
484
- insist { subject.get("tags") }.nil?
439
+ expect( event.get("tags") ).to be nil
485
440
  end
486
441
 
487
442
  sample "this will not be matched" do
488
- insist { subject.get("tags") }.include?("not_a_match")
443
+ expect( event.get("tags") ).to include("not_a_match")
489
444
  end
490
445
  end
491
446
 
492
447
  describe "captures named fields even if the whole text matches" do
493
- config <<-CONFIG
494
- filter {
495
- grok {
496
- match => { "message" => "%{DATE_EU:stimestamp}" }
497
- }
448
+ let(:config) {
449
+ {
450
+ 'match' => { "message" => "%{DATE_EU:stimestamp}" }
498
451
  }
499
- CONFIG
452
+ }
500
453
 
501
454
  sample "11/01/01" do
502
- insist { subject.get("stimestamp") } == "11/01/01"
455
+ expect( event.get("stimestamp") ).to eql "11/01/01"
503
456
  end
504
457
  end
505
458
 
506
459
  describe "allow dashes in capture names" do
507
- config <<-CONFIG
508
- filter {
509
- grok {
510
- match => { "message" => "%{WORD:foo-bar}" }
511
- }
460
+ let(:config) {
461
+ {
462
+ 'match' => { "message" => "%{WORD:foo-bar}" }
512
463
  }
513
- CONFIG
464
+ }
514
465
 
515
466
  sample "hello world" do
516
- insist { subject.get("foo-bar") } == "hello"
517
- end
518
- end
519
-
520
- describe "performance test", :performance => true do
521
- event_count = 100000
522
- min_rate = 2000
523
-
524
- max_duration = event_count / min_rate
525
- input = "Nov 24 01:29:01 -0800"
526
- config <<-CONFIG
527
- input {
528
- generator {
529
- count => #{event_count}
530
- message => "Mar 16 00:01:25 evita postfix/smtpd[1713]: connect from camomile.cloud9.net[168.100.1.3]"
531
- }
532
- }
533
- filter {
534
- grok {
535
- match => { "message" => "%{SYSLOGLINE}" }
536
- overwrite => [ "message" ]
537
- }
538
- }
539
- output { null { } }
540
- CONFIG
541
-
542
- 2.times do
543
- start = Time.now
544
- agent do
545
- duration = (Time.now - start)
546
- puts "filters/grok parse rate: #{"%02.0f/sec" % (event_count / duration)}, elapsed: #{duration}s"
547
- insist { duration } < max_duration
548
- end
467
+ expect( event.get("foo-bar") ).to eql "hello"
549
468
  end
550
469
  end
551
470
 
552
471
  describe "single value match with duplicate-named fields in pattern" do
553
- config <<-CONFIG
554
- filter {
555
- grok {
556
- match => { "message" => "%{INT:foo}|%{WORD:foo}" }
557
- }
472
+ let(:config) {
473
+ {
474
+ 'match' => { "message" => "%{INT:foo}|%{WORD:foo}" }
558
475
  }
559
- CONFIG
476
+ }
560
477
 
561
478
  sample "hello world" do
562
- insist { subject.get("foo") }.is_a?(String)
479
+ expect( event.get("foo") ).to be_a(String)
563
480
  end
564
481
 
565
482
  sample "123 world" do
566
- insist { subject.get("foo") }.is_a?(String)
483
+ expect( event.get("foo") ).to be_a(String)
567
484
  end
568
485
  end
569
486
 
570
- describe "break_on_match default should be true and first match should exit filter" do
571
- config <<-CONFIG
572
- filter {
573
- grok {
574
- match => { "message" => "%{INT:foo}"
575
- "somefield" => "%{INT:bar}"}
576
- }
577
- }
578
- CONFIG
579
487
 
580
- sample("message" => "hello world 123", "somefield" => "testme abc 999") do
581
- insist { subject.get("foo") } == "123"
582
- insist { subject.get("bar") }.nil?
583
- end
584
- end
585
-
586
- describe "break_on_match when set to false should try all patterns" do
587
- config <<-CONFIG
588
- filter {
589
- grok {
590
- match => { "message" => "%{INT:foo}"
591
- "somefield" => "%{INT:bar}"}
592
- break_on_match => false
593
- }
488
+ describe "break_on_match default should be true" do
489
+ let(:config) {
490
+ {
491
+ 'match' => { "message" => "%{INT:foo}", "somefield" => "%{INT:bar}" }
594
492
  }
595
- CONFIG
493
+ }
494
+ let(:data) { Hash("message" => "hello world 123", "somefield" => "testme abc 999") }
596
495
 
597
- sample("message" => "hello world 123", "somefield" => "testme abc 999") do
598
- insist { subject.get("foo") } == "123"
599
- insist { subject.get("bar") } == "999"
496
+ it 'exits filter after first match' do
497
+ expect( event.get("foo") ).to eql '123'
498
+ expect( event.get("bar") ).to be nil
600
499
  end
601
500
  end
602
501
 
603
- describe "LOGSTASH-1547 - break_on_match should work on fields with multiple patterns" do
604
- config <<-CONFIG
605
- filter {
606
- grok {
607
- match => { "message" => ["%{GREEDYDATA:name1}beard", "tree%{GREEDYDATA:name2}"] }
608
- break_on_match => false
609
- }
502
+ describe "break_on_match when set to false" do
503
+ let(:config) {
504
+ {
505
+ 'match' => { "message" => "%{INT:foo}", "somefield" => "%{INT:bar}" },
506
+ 'break_on_match' => false
610
507
  }
611
- CONFIG
508
+ }
509
+ let(:data) { Hash("message" => "hello world 123", "somefield" => "testme abc 999") }
612
510
 
613
- sample "treebranch" do
614
- insist { subject.get("name2") } == "branch"
615
- end
616
-
617
- sample "bushbeard" do
618
- insist { subject.get("name1") } == "bush"
619
- end
620
-
621
- sample "treebeard" do
622
- insist { subject.get("name1") } == "tree"
623
- insist { subject.get("name2") } == "beard"
511
+ it 'should try all patterns' do
512
+ expect( event.get("foo") ).to eql '123'
513
+ expect( event.get("bar") ).to eql '999'
624
514
  end
625
515
  end
626
516
 
627
- describe "break_on_match default for array input with single grok pattern" do
628
- config <<-CONFIG
629
- filter {
630
- grok {
631
- match => { "message" => "%{INT:foo}"}
632
- }
517
+ context "break_on_match default for array input with single grok pattern" do
518
+ let(:config) {
519
+ {
520
+ 'match' => { "message" => "%{INT:foo}" },
521
+ 'break_on_match' => false
633
522
  }
634
- CONFIG
523
+ }
635
524
 
636
- # array input --
637
- sample("message" => ["hello world 123", "line 23"]) do
638
- insist { subject.get("foo") } == ["123", "23"]
639
- insist { subject.get("tags") }.nil?
525
+ describe 'fully matching input' do
526
+ let(:data) { Hash("message" => ["hello world 123", "line 23"]) } # array input --
527
+ it 'matches' do
528
+ expect( event.get("foo") ).to eql ["123", "23"]
529
+ expect( event.get("tags") ).to be nil
530
+ end
640
531
  end
641
532
 
642
- # array input, one of them matches
643
- sample("message" => ["hello world 123", "abc"]) do
644
- insist { subject.get("foo") } == "123"
645
- insist { subject.get("tags") }.nil?
533
+ describe 'partially matching input' do
534
+ let(:data) { Hash("message" => ["hello world 123", "abc"]) } # array input, one of them matches
535
+ it 'matches' do
536
+ expect( event.get("foo") ).to eql "123"
537
+ expect( event.get("tags") ).to be nil
538
+ end
646
539
  end
647
540
  end
648
541
 
649
542
  describe "break_on_match = true (default) for array input with multiple grok pattern" do
650
- config <<-CONFIG
651
- filter {
652
- grok {
653
- match => { "message" => ["%{INT:foo}", "%{WORD:bar}"] }
654
- }
655
- }
656
- CONFIG
657
-
658
- # array input --
659
- sample("message" => ["hello world 123", "line 23"]) do
660
- insist { subject.get("foo") } == ["123", "23"]
661
- insist { subject.get("bar") }.nil?
662
- insist { subject.get("tags") }.nil?
543
+ let(:config) {
544
+ {
545
+ 'match' => { "message" => ["%{INT:foo}", "%{WORD:bar}"] }
546
+ }
547
+ }
548
+
549
+ describe 'matching input' do
550
+ let(:data) { Hash("message" => ["hello world 123", "line 23"]) } # array input --
551
+ it 'matches' do
552
+ expect( event.get("foo") ).to eql ["123", "23"]
553
+ expect( event.get("bar") ).to be nil
554
+ expect( event.get("tags") ).to be nil
555
+ end
663
556
  end
664
557
 
665
- # array input, one of them matches
666
- sample("message" => ["hello world", "line 23"]) do
667
- insist { subject.get("bar") } == "hello"
668
- insist { subject.get("foo") } == "23"
669
- insist { subject.get("tags") }.nil?
558
+ describe 'partially matching input' do
559
+ let(:data) { Hash("message" => ["hello world", "line 23"]) } # array input, one of them matches
560
+ it 'matches' do
561
+ expect( event.get("bar") ).to eql 'hello'
562
+ expect( event.get("foo") ).to eql "23"
563
+ expect( event.get("tags") ).to be nil
564
+ end
670
565
  end
671
566
  end
672
567
 
673
568
  describe "break_on_match = false for array input with multiple grok pattern" do
674
- config <<-CONFIG
675
- filter {
676
- grok {
677
- match => { "message" => ["%{INT:foo}", "%{WORD:bar}"] }
678
- break_on_match => false
679
- }
680
- }
681
- CONFIG
682
-
683
- # array input --
684
- sample("message" => ["hello world 123", "line 23"]) do
685
- insist { subject.get("foo") } == ["123", "23"]
686
- insist { subject.get("bar") } == ["hello", "line"]
687
- insist { subject.get("tags") }.nil?
569
+ let(:config) {
570
+ {
571
+ 'match' => { "message" => ["%{INT:foo}", "%{WORD:bar}"] },
572
+ 'break_on_match' => false
573
+ }
574
+ }
575
+
576
+ describe 'fully matching input' do
577
+ let(:data) { Hash("message" => ["hello world 123", "line 23"]) } # array input --
578
+ it 'matches' do
579
+ expect( event.get("foo") ).to eql ["123", "23"]
580
+ expect( event.get("bar") ).to eql ["hello", "line"]
581
+ expect( event.get("tags") ).to be nil
582
+ end
688
583
  end
689
584
 
690
- # array input, one of them matches
691
- sample("message" => ["hello world", "line 23"]) do
692
- insist { subject.get("bar") } == ["hello", "line"]
693
- insist { subject.get("foo") } == "23"
694
- insist { subject.get("tags") }.nil?
585
+ describe 'partially matching input' do
586
+ let(:data) { Hash("message" => ["hello world", "line 23"]) } # array input, one of them matches
587
+ it 'matches' do
588
+ expect( event.get("bar") ).to eql ["hello", "line"]
589
+ expect( event.get("foo") ).to eql "23"
590
+ expect( event.get("tags") ).to be nil
591
+ end
695
592
  end
696
593
  end
697
594
 
698
595
  describe "grok with unicode" do
699
- config <<-CONFIG
700
- filter {
701
- grok {
702
- #match => { "message" => "<%{POSINT:syslog_pri}>%{SYSLOGTIMESTAMP:syslog_timestamp} %{SYSLOGHOST:syslog_hostname} %{PROG:syslog_program}(?:\[%{POSINT:syslog_pid}\])?: %{GREEDYDATA:syslog_message}" }
703
- match => { "message" => "<%{POSINT:syslog_pri}>%{SPACE}%{SYSLOGTIMESTAMP:syslog_timestamp} %{SYSLOGHOST:syslog_hostname} %{PROG:syslog_program}(:?)(?:\\[%{GREEDYDATA:syslog_pid}\\])?(:?) %{GREEDYDATA:syslog_message}" }
704
- }
596
+ let(:config) {
597
+ {
598
+ #'match' => { "message" => "<%{POSINT:syslog_pri}>%{SYSLOGTIMESTAMP:syslog_timestamp} %{SYSLOGHOST:syslog_hostname} %{PROG:syslog_program}(?:\[%{POSINT:syslog_pid}\])?: %{GREEDYDATA:syslog_message}" }
599
+ 'match' => { "message" => "<%{POSINT:syslog_pri}>%{SPACE}%{SYSLOGTIMESTAMP:syslog_timestamp} %{SYSLOGHOST:syslog_hostname} %{PROG:syslog_program}(:?)(?:\\[%{GREEDYDATA:syslog_pid}\\])?(:?) %{GREEDYDATA:syslog_message}" }
705
600
  }
706
- CONFIG
601
+ }
707
602
 
708
603
  sample "<22>Jan 4 07:50:46 mailmaster postfix/policy-spf[9454]: : SPF permerror (Junk encountered in record 'v=spf1 mx a:mail.domain.no ip4:192.168.0.4 �all'): Envelope-from: email@domain.no" do
709
- insist { subject.get("tags") }.nil?
710
- insist { subject.get("syslog_pri") } == "22"
711
- insist { subject.get("syslog_program") } == "postfix/policy-spf"
712
- end
713
- end
714
-
715
- describe "patterns in the 'patterns/' dir override core patterns" do
716
-
717
- let(:pattern_dir) { File.join(LogStash::Environment::LOGSTASH_HOME, "patterns") }
718
- let(:has_pattern_dir?) { Dir.exist?(pattern_dir) }
719
-
720
- before do
721
- FileUtils.mkdir(pattern_dir) unless has_pattern_dir?
722
- @file = File.new(File.join(pattern_dir, 'grok.pattern'), 'w+')
723
- @file.write('WORD \b[2-5]\b')
724
- @file.close
725
- end
726
-
727
- let(:config) do
728
- 'filter { grok { match => { "message" => "%{WORD:word}" } } }'
729
- end
730
-
731
- sample("message" => 'hello') do
732
- insist { subject.get("tags") } == ["_grokparsefailure"]
733
- end
734
-
735
- after do
736
- File.unlink @file
737
- FileUtils.rm_rf(pattern_dir) if has_pattern_dir?
738
- end
739
- end
740
-
741
- describe "patterns in custom dir override those in 'patterns/' dir" do
742
-
743
- let(:tmpdir) { Stud::Temporary.directory }
744
- let(:pattern_dir) { File.join(LogStash::Environment::LOGSTASH_HOME, "patterns") }
745
- let(:has_pattern_dir?) { Dir.exist?(pattern_dir) }
746
-
747
- before do
748
- FileUtils.mkdir(pattern_dir) unless has_pattern_dir?
749
- @file1 = File.new(File.join(pattern_dir, 'grok.pattern'), 'w+')
750
- @file1.write('WORD \b[2-5]\b')
751
- @file1.close
752
- @file2 = File.new(File.join(tmpdir, 'grok.pattern'), 'w+')
753
- @file2.write('WORD \b[0-1]\b')
754
- @file2.close
755
- end
756
-
757
- let(:config) do
758
- "filter { grok { patterns_dir => \"#{tmpdir}\" match => { \"message\" => \"%{WORD:word}\" } } }"
759
- end
760
-
761
- sample("message" => '0') do
762
- insist { subject.get("tags") } == nil
763
- end
764
-
765
- after do
766
- File.unlink @file1
767
- File.unlink @file2
768
- FileUtils.remove_entry tmpdir
769
- FileUtils.rm_rf(pattern_dir) unless has_pattern_dir?
770
- end
771
- end
772
-
773
- describe "patterns with file glob" do
774
-
775
- let(:tmpdir) { Stud::Temporary.directory }
776
-
777
- before do
778
- @file3 = File.new(File.join(tmpdir, 'grok.pattern'), 'w+')
779
- @file3.write('WORD \b[0-1]\b')
780
- @file3.close
781
- @file4 = File.new(File.join(tmpdir, 'grok.pattern.old'), 'w+')
782
- @file4.write('WORD \b[2-5]\b')
783
- @file4.close
784
- end
785
-
786
- let(:config) do
787
- "filter { grok { patterns_dir => \"#{tmpdir}\" patterns_files_glob => \"*.pattern\" match => { \"message\" => \"%{WORD:word}\" } } }"
788
- end
789
-
790
- sample("message" => '0') do
791
- insist { subject.get("tags") } == nil
792
- end
793
-
794
- after do
795
- File.unlink @file3
796
- File.unlink @file4
797
- FileUtils.remove_entry tmpdir
798
- end
799
- end
800
-
801
- describe "patterns with file glob on directory that contains subdirectories" do
802
-
803
- let(:tmpdir) { Stud::Temporary.directory }
804
-
805
- before do
806
- @file3 = File.new(File.join(tmpdir, 'grok.pattern'), 'w+')
807
- @file3.write('WORD \b[0-1]\b')
808
- @file3.close
809
- Dir.mkdir(File.join(tmpdir, "subdir"))
810
- end
811
-
812
- let(:config) do
813
- "filter { grok { patterns_dir => \"#{tmpdir}\" patterns_files_glob => \"*\" match => { \"message\" => \"%{WORD:word}\" } } }"
814
- end
815
-
816
- sample("message" => '0') do
817
- insist { subject.get("tags") } == nil
818
- end
819
-
820
- after do
821
- File.unlink @file3
822
- FileUtils.remove_entry tmpdir
604
+ expect( event.get("tags") ).to be nil
605
+ expect( event.get("syslog_pri") ).to eql "22"
606
+ expect( event.get("syslog_program") ).to eql "postfix/policy-spf"
823
607
  end
824
608
  end
825
609
 
826
610
  describe "grok with nil coerced value" do
827
- config <<-CONFIG
828
- filter {
829
- grok {
830
- match => { "message" => "test (N/A|%{BASE10NUM:duration:float}ms)" }
831
- }
611
+ let(:config) {
612
+ {
613
+ 'match' => { "message" => "test (N/A|%{BASE10NUM:duration:float}ms)" }
832
614
  }
833
- CONFIG
615
+ }
834
616
 
835
617
  sample "test 28.4ms" do
836
- insist { subject.get("duration") } == 28.4
837
- insist { subject.get("tags") }.nil?
618
+ expect( event.get("duration") ).to eql 28.4
619
+ expect( event.get("tags") ).to be nil
838
620
  end
839
621
 
840
622
  sample "test N/A" do
841
- insist { insist { subject.to_hash }.include?("duration") }.fails
842
- insist { subject.get("tags") }.nil?
623
+ expect( event.to_hash ).not_to include("duration")
624
+ expect( event.get("tags") ).to be nil
843
625
  end
844
626
 
845
627
  sample "test abc" do
846
- insist { subject.get("duration") }.nil?
847
- insist { subject.get("tags") } == ["_grokparsefailure"]
628
+ expect( event.get("duration") ).to be nil
629
+ expect( event.get("tags") ).to eql ["_grokparsefailure"]
848
630
  end
849
631
  end
850
632
 
851
633
  describe "grok with nil coerced value and keep_empty_captures" do
852
- config <<-CONFIG
853
- filter {
854
- grok {
855
- match => { "message" => "test (N/A|%{BASE10NUM:duration:float}ms)" }
856
- keep_empty_captures => true
857
- }
634
+ let(:config) {
635
+ {
636
+ 'match' => { "message" => "test (N/A|%{BASE10NUM:duration:float}ms)" },
637
+ 'keep_empty_captures' => true
858
638
  }
859
- CONFIG
639
+ }
860
640
 
861
641
  sample "test N/A" do
862
- insist { subject.to_hash }.include?("duration")
863
- insist { subject.get("tags") }.nil?
642
+ expect( event.to_hash ).to include("duration")
643
+ expect( event.get("tags") ).to be nil
864
644
  end
865
-
866
645
  end
867
646
 
868
647
  describe "grok with no coercion" do
869
- config <<-CONFIG
870
- filter {
871
- grok {
872
- match => { "message" => "test (N/A|%{BASE10NUM:duration}ms)" }
873
- }
648
+ let(:config) {
649
+ {
650
+ 'match' => { "message" => "test (N/A|%{BASE10NUM:duration}ms)" },
874
651
  }
875
- CONFIG
652
+ }
876
653
 
877
654
  sample "test 28.4ms" do
878
- insist { subject.get("duration") } == "28.4"
879
- insist { subject.get("tags") }.nil?
655
+ expect( event.get("duration") ).to eql '28.4'
656
+ expect( event.get("tags") ).to be nil
880
657
  end
881
658
 
882
659
  sample "test N/A" do
883
- insist { subject.get("duration") }.nil?
884
- insist { subject.get("tags") }.nil?
660
+ expect( event.get("duration") ).to be nil
661
+ expect( event.get("tags") ).to be nil
885
662
  end
886
663
  end
887
664
 
888
665
  describe "opening/closing" do
889
- let(:config) { {"match" => {"message" => "A"}} }
890
- subject(:plugin) do
891
- ::LogStash::Filters::Grok.new(config)
892
- end
893
-
894
- before do
895
- plugin.register
896
- end
666
+ let(:config) { { "match" => {"message" => "A"} } }
667
+ let(:message) { 'AAA' }
897
668
 
898
669
  it "should close cleanly" do
899
- expect { plugin.do_close }.not_to raise_error
670
+ expect { subject.do_close }.not_to raise_error
900
671
  end
901
672
  end
902
673
 
903
674
  describe "after grok when the event is JSON serialised the field values are unchanged" do
904
- config <<-CONFIG
905
- filter {grok {match => ["message", "Failed password for (invalid user |)%{USERNAME:username} from %{IP:src_ip} port %{BASE10NUM:port}"] remove_field => ["message","severity"] add_tag => ["ssh_failure"]}}
906
- CONFIG
675
+ let(:config) {
676
+ {
677
+ 'match' => ["message", "Failed password for (invalid user |)%{USERNAME:username} from %{IP:src_ip} port %{BASE10NUM:port}"],
678
+ 'remove_field' => ["message","severity"],
679
+ 'add_tag' => ["ssh_failure"]
680
+ }
681
+ }
907
682
 
908
683
  sample('{"facility":"auth","message":"Failed password for testuser from 1.1.1.1 port 22"}') do
909
- insist { subject.get("username") } == "testuser"
910
- insist { subject.get("port") } == "22"
911
- insist { subject.get("src_ip") } == "1.1.1.1"
912
- insist { LogStash::Json.dump(subject.get('username')) } == "\"testuser\""
684
+ expect( event.get("username") ).to eql "testuser"
685
+ expect( event.get("port") ).to eql "22"
686
+ expect( event.get("src_ip") ).to eql "1.1.1.1"
687
+ expect( LogStash::Json.dump(event.get('username')) ).to eql "\"testuser\""
913
688
 
914
- insist { subject.to_json } =~ %r|"src_ip":"1.1.1.1"|
915
- insist { subject.to_json } =~ %r|"@timestamp":"20\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ"|
916
- insist { subject.to_json } =~ %r|"port":"22"|
917
- insist { subject.to_json } =~ %r|"@version":"1"|
918
- insist { subject.to_json } =~ %r|"username"|i
919
- insist { subject.to_json } =~ %r|"testuser"|
920
- insist { subject.to_json } =~ %r|"tags":\["ssh_failure"\]|
689
+ expect( event.to_json ).to match %r|"src_ip":"1.1.1.1"|
690
+ expect( event.to_json ).to match %r|"@timestamp":"20\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ"|
691
+ expect( event.to_json ).to match %r|"port":"22"|
692
+ expect( event.to_json ).to match %r|"@version":"1"|
693
+ expect( event.to_json ).to match %r|"username"|i
694
+ expect( event.to_json ).to match %r|"testuser"|
695
+ expect( event.to_json ).to match %r|"tags":\["ssh_failure"\]|
921
696
  end
922
697
  end
923
698
 
924
699
  describe "grok with inline pattern definition successfully extracts fields" do
925
- config <<-CONFIG
926
- filter {
927
- grok {
928
- match => { "message" => "%{APACHE_TIME:timestamp} %{LOGLEVEL:level} %{MY_PATTERN:hindsight}" }
929
- pattern_definitions => { "APACHE_TIME" => "%{DAY} %{MONTH} %{MONTHDAY} %{TIME} %{YEAR}"
930
- "MY_PATTERN" => "%{YEAR}"}
700
+ let(:config) {
701
+ {
702
+ 'match' => { "message" => "%{APACHE_TIME:timestamp} %{LOGLEVEL:level} %{MY_PATTERN:hindsight}" },
703
+ 'pattern_definitions' => {
704
+ "APACHE_TIME" => "%{DAY} %{MONTH} %{MONTHDAY} %{TIME} %{YEAR}",
705
+ "MY_PATTERN" => "%{YEAR}"
931
706
  }
932
707
  }
933
- CONFIG
708
+ }
934
709
 
935
710
  sample "Mon Dec 26 16:22:08 2016 error 2020" do
936
- insist { subject.get("timestamp") } == "Mon Dec 26 16:22:08 2016"
937
- insist { subject.get("level") } == "error"
938
- insist { subject.get("hindsight") } == "2020"
711
+ expect( event.get("timestamp") ).to eql "Mon Dec 26 16:22:08 2016"
712
+ expect( event.get("level") ).to eql "error"
713
+ expect( event.get("hindsight") ).to eql "2020"
939
714
  end
940
715
  end
941
716
 
942
717
  describe "grok with inline pattern definition overwrites existing pattern definition" do
943
- config <<-CONFIG
944
- filter {
945
- grok {
946
- match => { "message" => "%{APACHE_TIME:timestamp} %{LOGLEVEL:level}" }
947
- # loglevel was previously ([Aa]lert|ALERT|[Tt]...
948
- pattern_definitions => { "APACHE_TIME" => "%{DAY} %{MONTH} %{MONTHDAY} %{TIME} %{YEAR}"
949
- "LOGLEVEL" => "%{NUMBER}"}
718
+ let(:config) {
719
+ {
720
+ 'match' => { "message" => "%{APACHE_TIME:timestamp} %{LOGLEVEL:level}" },
721
+ # loglevel was previously ([Aa]lert|ALERT|[Tt]...
722
+ 'pattern_definitions' => {
723
+ "APACHE_TIME" => "%{DAY} %{MONTH} %{MONTHDAY} %{TIME} %{YEAR}",
724
+ "LOGLEVEL" => "%{NUMBER}"
950
725
  }
951
726
  }
952
- CONFIG
727
+ }
953
728
 
954
729
  sample "Mon Dec 26 16:22:08 2016 9999" do
955
- insist { subject.get("timestamp") } == "Mon Dec 26 16:22:08 2016"
956
- insist { subject.get("level") } == "9999"
730
+ expect( event.get("timestamp") ).to eql "Mon Dec 26 16:22:08 2016"
731
+ expect( event.get("level") ).to eql "9999"
957
732
  end
958
733
  end
959
734
 
735
+ context 'when timeouts are explicitly disabled' do
736
+ let(:config) do
737
+ {
738
+ "timeout_millis" => 0
739
+ }
740
+ end
741
+
742
+ context 'when given a pathological input', slow: true do
743
+ let(:message) { "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}
744
+ let(:config) { super().merge("match" => { "message" => "(.*a){30}" }) }
960
745
 
961
- describe "direct plugin testing" do
962
- subject do
963
- plugin = LogStash::Filters::Grok.new(options)
964
- plugin.register
965
- plugin
746
+ it 'blocks for at least 3 seconds' do
747
+ blocking_exception_class = Class.new(::Exception) # avoid RuntimeError
748
+ expect do
749
+ Timeout.timeout(3, blocking_exception_class) do
750
+ subject.filter(event)
751
+ end
752
+ end.to raise_exception(blocking_exception_class)
753
+ end
966
754
  end
755
+ end
756
+ end
757
+
758
+ describe LogStash::Filters::Grok do
759
+ describe "(LEGACY)" do
760
+ describe "patterns in the 'patterns/' dir override core patterns" do
967
761
 
968
- let(:data) { {"message" => message} }
969
- let(:event) { LogStash::Event.new(data) }
762
+ let(:pattern_dir) { File.join(LogStash::Environment::LOGSTASH_HOME, "patterns") }
763
+ let(:has_pattern_dir?) { Dir.exist?(pattern_dir) }
970
764
 
971
- context 'when timeouts are explicitly disabled' do
972
- let(:options) do
973
- {
974
- "timeout_millis" => 0
765
+ before do
766
+ FileUtils.mkdir(pattern_dir) unless has_pattern_dir?
767
+ @file = File.new(File.join(pattern_dir, 'grok.pattern'), 'w+')
768
+ @file.write('WORD \b[2-5]\b')
769
+ @file.close
770
+ end
771
+
772
+ let(:config) do
773
+ 'filter { grok { match => { "message" => "%{WORD:word}" } } }'
774
+ end
775
+
776
+ sample("message" => 'hello') do
777
+ insist { subject.get("tags") } == ["_grokparsefailure"]
778
+ end
779
+
780
+ after do
781
+ File.unlink @file
782
+ FileUtils.rm_rf(pattern_dir) if has_pattern_dir?
783
+ end
784
+ end
785
+
786
+ describe "patterns in custom dir override those in 'patterns/' dir" do
787
+
788
+ let(:tmpdir) { Stud::Temporary.directory }
789
+ let(:pattern_dir) { File.join(LogStash::Environment::LOGSTASH_HOME, "patterns") }
790
+ let(:has_pattern_dir?) { Dir.exist?(pattern_dir) }
791
+
792
+ before do
793
+ FileUtils.mkdir(pattern_dir) unless has_pattern_dir?
794
+ @file1 = File.new(File.join(pattern_dir, 'grok.pattern'), 'w+')
795
+ @file1.write('WORD \b[2-5]\b')
796
+ @file1.close
797
+ @file2 = File.new(File.join(tmpdir, 'grok.pattern'), 'w+')
798
+ @file2.write('WORD \b[0-1]\b')
799
+ @file2.close
800
+ end
801
+
802
+ let(:config) do
803
+ "filter { grok { patterns_dir => \"#{tmpdir}\" match => { \"message\" => \"%{WORD:word}\" } } }"
804
+ end
805
+
806
+ sample("message" => '0') do
807
+ insist { subject.get("tags") } == nil
808
+ end
809
+
810
+ after do
811
+ File.unlink @file1
812
+ File.unlink @file2
813
+ FileUtils.remove_entry tmpdir
814
+ FileUtils.rm_rf(pattern_dir) unless has_pattern_dir?
815
+ end
816
+ end
817
+
818
+ describe "patterns with file glob" do
819
+
820
+ let(:tmpdir) { Stud::Temporary.directory }
821
+
822
+ before do
823
+ @file3 = File.new(File.join(tmpdir, 'grok.pattern'), 'w+')
824
+ @file3.write('WORD \b[0-1]\b')
825
+ @file3.close
826
+ @file4 = File.new(File.join(tmpdir, 'grok.pattern.old'), 'w+')
827
+ @file4.write('WORD \b[2-5]\b')
828
+ @file4.close
829
+ end
830
+
831
+ let(:config) do
832
+ "filter { grok { patterns_dir => \"#{tmpdir}\" patterns_files_glob => \"*.pattern\" match => { \"message\" => \"%{WORD:word}\" } } }"
833
+ end
834
+
835
+ sample("message" => '0') do
836
+ insist { subject.get("tags") } == nil
837
+ end
838
+
839
+ after do
840
+ File.unlink @file3
841
+ File.unlink @file4
842
+ FileUtils.remove_entry tmpdir
843
+ end
844
+ end
845
+
846
+ describe "patterns with file glob on directory that contains subdirectories" do
847
+
848
+ let(:tmpdir) { Stud::Temporary.directory }
849
+
850
+ before do
851
+ @file3 = File.new(File.join(tmpdir, 'grok.pattern'), 'w+')
852
+ @file3.write('WORD \b[0-1]\b')
853
+ @file3.close
854
+ Dir.mkdir(File.join(tmpdir, "subdir"))
855
+ end
856
+
857
+ let(:config) do
858
+ "filter { grok { patterns_dir => \"#{tmpdir}\" patterns_files_glob => \"*\" match => { \"message\" => \"%{WORD:word}\" } } }"
859
+ end
860
+
861
+ sample("message" => '0') do
862
+ insist { subject.get("tags") } == nil
863
+ end
864
+
865
+ after do
866
+ File.unlink @file3
867
+ FileUtils.remove_entry tmpdir
868
+ end
869
+ end
870
+
871
+ describe "LOGSTASH-1547 - break_on_match should work on fields with multiple patterns" do
872
+ config <<-CONFIG
873
+ filter {
874
+ grok {
875
+ match => { "message" => ["%{GREEDYDATA:name1}beard", "tree%{GREEDYDATA:name2}"] }
876
+ break_on_match => false
975
877
  }
878
+ }
879
+ CONFIG
880
+
881
+ sample "treebranch" do
882
+ insist { subject.get("name2") } == "branch"
976
883
  end
977
884
 
978
- context 'when given a pathological input' do
979
- let(:message) { "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}
980
- let(:options) { super().merge("match" => { "message" => "(.*a){30}" }) }
885
+ sample "bushbeard" do
886
+ insist { subject.get("name1") } == "bush"
887
+ end
981
888
 
982
- it 'blocks for at least 3 seconds' do
983
- blocking_exception_class = Class.new(::Exception) # avoid RuntimeError
984
- expect do
985
- Timeout.timeout(3, blocking_exception_class) do
986
- subject.filter(event)
987
- end
988
- end.to raise_exception(blocking_exception_class)
989
- end
889
+ sample "treebeard" do
890
+ insist { subject.get("name1") } == "tree"
891
+ insist { subject.get("name2") } == "beard"
990
892
  end
991
893
  end
992
894
  end