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 +4 -4
- data/CHANGELOG.md +3 -0
- data/Gemfile +6 -0
- data/docs/index.asciidoc +8 -0
- data/lib/logstash/filters/grok.rb +15 -5
- data/logstash-filter-grok.gemspec +1 -1
- data/spec/filters/grok_performance_spec.rb +150 -0
- data/spec/filters/grok_spec.rb +585 -683
- data/spec/spec_helper.rb +19 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a9d8be42f7b846a0684d25d18ec624a81574b52e394df863afa004d458ecd915
|
4
|
+
data.tar.gz: 605d0cea73b747d9900bebfc2a99f59d2950300af7ef52ec5b9cb3f556a7d5fd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '096791bcc8691300c8e59aa8c6741d499c515b6fc70b3f7e4deea0c23440bce1875d98d7aaad0d6f8d8ab12dc526051e46e2a61b590422d646d7939221ec3fc7'
|
7
|
+
data.tar.gz: efe3dcc313d22e67986828351b365b9383c5c91d0a4caf8da78bd8c34126e7088d81ce9c672a6dc0758a9c3c4d1e4a2cae53e2df2c79bcd510848b69ecac9945
|
data/CHANGELOG.md
CHANGED
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
|
data/docs/index.asciidoc
CHANGED
@@ -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(
|
410
|
+
event.set(target_field, value)
|
403
411
|
else
|
404
|
-
v = event.get(
|
412
|
+
v = event.get(target_field)
|
405
413
|
if v.nil?
|
406
|
-
event.set(
|
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(
|
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(
|
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.
|
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
|
data/spec/filters/grok_spec.rb
CHANGED
@@ -1,392 +1,357 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
|
3
|
-
require "stud/temporary"
|
2
|
+
require_relative "../spec_helper"
|
4
3
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
-
|
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
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
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
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
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
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
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
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
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
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
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
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
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
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
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
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
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)", :
|
177
|
-
config
|
178
|
-
|
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
|
-
|
187
|
-
|
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
|
193
|
-
|
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
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
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
|
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
|
-
|
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
|
225
|
-
|
226
|
-
|
227
|
-
|
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
|
-
|
213
|
+
}
|
214
|
+
let(:data) { { "message" => "hello world", "examplefield" => "12345" } }
|
233
215
|
|
234
|
-
|
235
|
-
|
236
|
-
|
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
|
242
|
-
|
243
|
-
|
244
|
-
|
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
|
-
|
228
|
+
}
|
249
229
|
|
250
230
|
sample "matchme 1234" do
|
251
|
-
|
252
|
-
|
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
|
-
|
257
|
-
|
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
|
264
|
-
|
265
|
-
|
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
|
-
|
247
|
+
}
|
270
248
|
|
271
249
|
sample "1=test" do
|
272
|
-
|
273
|
-
|
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
|
-
|
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
|
282
|
-
|
283
|
-
|
284
|
-
|
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
|
-
|
264
|
+
}
|
289
265
|
|
290
266
|
sample "1=test" do
|
291
|
-
|
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
|
-
|
296
|
-
|
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
|
303
|
-
|
304
|
-
|
305
|
-
|
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
|
-
|
283
|
+
}
|
310
284
|
|
311
285
|
sample "Hello World, yo!" do
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
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
|
322
|
-
|
323
|
-
|
324
|
-
match => { "message" => "(?<foo>\w+)" }
|
325
|
-
}
|
295
|
+
let(:config) {
|
296
|
+
{
|
297
|
+
'match' => { "message" => "(?<foo>\\w+)" }
|
326
298
|
}
|
327
|
-
|
299
|
+
}
|
300
|
+
|
328
301
|
sample "hello world" do
|
329
|
-
|
330
|
-
|
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
|
336
|
-
|
337
|
-
|
338
|
-
match => { "message" => "(?<timestamp>%{DATE_EU} %{TIME})" }
|
339
|
-
}
|
308
|
+
let(:config) {
|
309
|
+
{
|
310
|
+
'match' => { "message" => "(?<timestamp>%{DATE_EU} %{TIME})" }
|
340
311
|
}
|
341
|
-
|
312
|
+
}
|
342
313
|
|
343
314
|
sample "fancy 12-12-12 12:12:12" do
|
344
|
-
|
345
|
-
|
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
|
352
|
-
|
353
|
-
|
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
|
-
|
326
|
+
}
|
327
|
+
let(:data) { Hash({ "status" => 403 }) }
|
359
328
|
|
360
|
-
|
361
|
-
|
362
|
-
|
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
|
368
|
-
|
369
|
-
|
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
|
-
|
340
|
+
}
|
341
|
+
let(:data) { Hash({ "version" => 1.0 }) }
|
375
342
|
|
376
|
-
|
377
|
-
|
378
|
-
|
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
|
384
|
-
|
385
|
-
|
386
|
-
match => { "message" => "%{LOGLEVEL:level}: error!" }
|
387
|
-
}
|
350
|
+
let(:config) {
|
351
|
+
{
|
352
|
+
'match' => { "message" => "%{LOGLEVEL:level}: error!" }
|
388
353
|
}
|
389
|
-
|
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
|
-
|
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
|
412
|
-
|
413
|
-
|
414
|
-
|
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
|
-
|
381
|
+
}
|
421
382
|
|
422
383
|
sample "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" do
|
423
|
-
expect(
|
424
|
-
expect(
|
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
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
"
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
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
|
-
|
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
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
"
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
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(
|
469
|
-
expect(
|
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
|
475
|
-
|
476
|
-
|
477
|
-
|
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
|
-
|
436
|
+
}
|
482
437
|
|
483
438
|
sample "matchme 1234" do
|
484
|
-
|
439
|
+
expect( event.get("tags") ).to be nil
|
485
440
|
end
|
486
441
|
|
487
442
|
sample "this will not be matched" do
|
488
|
-
|
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
|
494
|
-
|
495
|
-
|
496
|
-
match => { "message" => "%{DATE_EU:stimestamp}" }
|
497
|
-
}
|
448
|
+
let(:config) {
|
449
|
+
{
|
450
|
+
'match' => { "message" => "%{DATE_EU:stimestamp}" }
|
498
451
|
}
|
499
|
-
|
452
|
+
}
|
500
453
|
|
501
454
|
sample "11/01/01" do
|
502
|
-
|
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
|
508
|
-
|
509
|
-
|
510
|
-
match => { "message" => "%{WORD:foo-bar}" }
|
511
|
-
}
|
460
|
+
let(:config) {
|
461
|
+
{
|
462
|
+
'match' => { "message" => "%{WORD:foo-bar}" }
|
512
463
|
}
|
513
|
-
|
464
|
+
}
|
514
465
|
|
515
466
|
sample "hello world" do
|
516
|
-
|
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
|
554
|
-
|
555
|
-
|
556
|
-
match => { "message" => "%{INT:foo}|%{WORD:foo}" }
|
557
|
-
}
|
472
|
+
let(:config) {
|
473
|
+
{
|
474
|
+
'match' => { "message" => "%{INT:foo}|%{WORD:foo}" }
|
558
475
|
}
|
559
|
-
|
476
|
+
}
|
560
477
|
|
561
478
|
sample "hello world" do
|
562
|
-
|
479
|
+
expect( event.get("foo") ).to be_a(String)
|
563
480
|
end
|
564
481
|
|
565
482
|
sample "123 world" do
|
566
|
-
|
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
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
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
|
-
|
493
|
+
}
|
494
|
+
let(:data) { Hash("message" => "hello world 123", "somefield" => "testme abc 999") }
|
596
495
|
|
597
|
-
|
598
|
-
|
599
|
-
|
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 "
|
604
|
-
config
|
605
|
-
|
606
|
-
|
607
|
-
|
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
|
-
|
508
|
+
}
|
509
|
+
let(:data) { Hash("message" => "hello world 123", "somefield" => "testme abc 999") }
|
612
510
|
|
613
|
-
|
614
|
-
|
615
|
-
|
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
|
-
|
628
|
-
config
|
629
|
-
|
630
|
-
|
631
|
-
|
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
|
-
|
523
|
+
}
|
635
524
|
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
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
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
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
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
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
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
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
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
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
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
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
|
700
|
-
|
701
|
-
|
702
|
-
|
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
|
-
|
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
|
-
|
710
|
-
|
711
|
-
|
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
|
828
|
-
|
829
|
-
|
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
|
-
|
615
|
+
}
|
834
616
|
|
835
617
|
sample "test 28.4ms" do
|
836
|
-
|
837
|
-
|
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
|
-
|
842
|
-
|
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
|
-
|
847
|
-
|
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
|
853
|
-
|
854
|
-
|
855
|
-
|
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
|
-
|
639
|
+
}
|
860
640
|
|
861
641
|
sample "test N/A" do
|
862
|
-
|
863
|
-
|
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
|
870
|
-
|
871
|
-
|
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
|
-
|
652
|
+
}
|
876
653
|
|
877
654
|
sample "test 28.4ms" do
|
878
|
-
|
879
|
-
|
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
|
-
|
884
|
-
|
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
|
-
|
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 {
|
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
|
905
|
-
|
906
|
-
|
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
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
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
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
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
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
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
|
-
|
708
|
+
}
|
934
709
|
|
935
710
|
sample "Mon Dec 26 16:22:08 2016 error 2020" do
|
936
|
-
|
937
|
-
|
938
|
-
|
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
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
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
|
-
|
727
|
+
}
|
953
728
|
|
954
729
|
sample "Mon Dec 26 16:22:08 2016 9999" do
|
955
|
-
|
956
|
-
|
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
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
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
|
-
|
969
|
-
|
762
|
+
let(:pattern_dir) { File.join(LogStash::Environment::LOGSTASH_HOME, "patterns") }
|
763
|
+
let(:has_pattern_dir?) { Dir.exist?(pattern_dir) }
|
970
764
|
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
|
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
|
-
|
979
|
-
|
980
|
-
|
885
|
+
sample "bushbeard" do
|
886
|
+
insist { subject.get("name1") } == "bush"
|
887
|
+
end
|
981
888
|
|
982
|
-
|
983
|
-
|
984
|
-
|
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
|