logstash-filter-grok 4.2.0 → 4.3.0

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