logstash-filter-grok 3.1.2 → 3.2.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
  SHA1:
3
- metadata.gz: caf1f9a43c9403b8c1dfaa59d356e1600b5f1881
4
- data.tar.gz: 2404df9cf8936c1bb6f62867733e53838e87c1c9
3
+ metadata.gz: a34c685833867a04238f8544429c5938cd58a696
4
+ data.tar.gz: 096eb04af33607c064d210c9e007ffbfccb3a9fa
5
5
  SHA512:
6
- metadata.gz: a655b0603e43911b7ab64b2f59936224f656cb5de4064a28a06c378bdc9cba77c1716b3fee14531721abfb2ff09c7d8d53747035d0bc7b269c8e88d7911d1515
7
- data.tar.gz: 306b4b5a7345991750b63f49b497b3fe59773a045680b6e934791a5a9a76280d615c74b7825ca45fd533727fefe485e109f23a42f5af051ea731e0831b5bee97
6
+ metadata.gz: b7aacf53f2a102c96c65597710b529ef0acef10730358ae4c5d4fd32c9dd053e5f226a2696535921fccc75504f205c8580762287ead404d413359bac8e9758f1
7
+ data.tar.gz: 61bfb20dbb5452b957e08e82b5ad54d8b1f89d50dcdd2d0ba6bd37f7e06b2f630e2d92247ddd9e81de7fcefba720f12a3a32b5a21edb99eafb9d6a57331b7b54
data/CHANGELOG.md CHANGED
@@ -1,3 +1,6 @@
1
+ ## 3.2.0
2
+ - Add new timeout options to cancel grok execution if a threshold time is exceeded
3
+
1
4
  ## 3.1.2
2
5
  - Relax constraint on logstash-core-plugin-api to >= 1.60 <= 2.99
3
6
 
@@ -0,0 +1,77 @@
1
+ class LogStash::Filters::Grok::TimeoutEnforcer
2
+ def initialize(logger, timeout_nanos)
3
+ @logger = logger
4
+ @running = true
5
+ @timeout_nanos = timeout_nanos
6
+
7
+ # Stores running matches with their start time, this is used to cancel long running matches
8
+ # Is a map of Thread => start_time
9
+ @timer_mutex = Mutex.new
10
+ @threads_to_start_time = {}
11
+ end
12
+
13
+ def grok_till_timeout(event, grok, field, value)
14
+ begin
15
+ thread = Thread.current
16
+ start_thread_groking(thread)
17
+ yield
18
+ rescue ::LogStash::Filters::Grok::TimeoutException => e
19
+ # These fields aren't present at the time the exception was raised
20
+ # so we add them here.
21
+ # We could store this metadata in the @threads_to_start_time hash
22
+ # but that'd come at a perf cost and this works just as well.
23
+ e.grok = grok
24
+ e.field = field
25
+ e.value = value
26
+ raise e
27
+ ensure
28
+ stop_thread_groking(thread)
29
+ end
30
+ end
31
+
32
+ def start_thread_groking(thread)
33
+ @timer_mutex.synchronize do
34
+ @threads_to_start_time[thread] = java.lang.System.nanoTime()
35
+ end
36
+ end
37
+
38
+ def stop_thread_groking(thread)
39
+ @timer_mutex.synchronize do
40
+ @threads_to_start_time.delete(thread)
41
+ end
42
+ end
43
+
44
+ def cancel_timed_out!
45
+ @threads_to_start_time.each do |thread,start_time|
46
+ now = java.lang.System.nanoTime # save ourselves some nanotime calls
47
+ elapsed = java.lang.System.nanoTime - start_time
48
+ if elapsed > @timeout_nanos
49
+ elapsed_millis = elapsed / 1000
50
+ thread.raise(::LogStash::Filters::Grok::TimeoutException.new(elapsed_millis))
51
+ end
52
+ end
53
+ end
54
+
55
+ def start!
56
+ @timer_thread = Thread.new do
57
+ while @running
58
+ begin
59
+ cancel_timed_out!
60
+ rescue Exception => e
61
+ @logger.error("Error while attempting to check/cancel excessively long grok patterns",
62
+ :message => e.message,
63
+ :class => e.class.name,
64
+ :backtrace => e.backtrace
65
+ )
66
+ end
67
+ sleep 0.25
68
+ end
69
+ end
70
+ end
71
+
72
+ def stop!
73
+ @running = false
74
+ # Check for the thread mostly for a fast start/shutdown scenario
75
+ @timer_thread.join if @timer_thread
76
+ end
77
+ end
@@ -0,0 +1,23 @@
1
+ class LogStash::Filters::Grok::TimeoutException < Exception
2
+ attr_accessor :elapsed_millis, :grok, :field, :value
3
+
4
+ def initialize(elapsed_millis, grok=nil, field=nil, value=nil)
5
+ @elapsed_millis = elapsed_millis
6
+ @field = field
7
+ @value = value
8
+ @grok = grok
9
+ end
10
+
11
+ def message
12
+ "Timeout executing grok '#{@grok.pattern}' against field '#{field}' with value '#{trunc_value}'!"
13
+ end
14
+
15
+ def trunc_value
16
+ if value.size <= 255 # If no more than 255 chars
17
+ value
18
+ else
19
+ "Value too large to output (#{value.bytesize} bytes)! First 255 chars are: #{value[0..255]}"
20
+ end
21
+ end
22
+ end
23
+
@@ -138,6 +138,8 @@
138
138
  # `SYSLOGBASE` pattern which itself is defined by other patterns.
139
139
  class LogStash::Filters::Grok < LogStash::Filters::Base
140
140
  config_name "grok"
141
+ require "logstash/filters/grok/timeout_enforcer"
142
+ require "logstash/filters/grok/timeout_exception"
141
143
 
142
144
  # A hash of matches of field => value
143
145
  #
@@ -159,9 +161,9 @@
159
161
  #
160
162
  # Logstash ships by default with a bunch of patterns, so you don't
161
163
  # necessarily need to define this yourself unless you are adding additional
162
- # patterns. You can point to multiple pattern directories using this setting
164
+ # patterns. You can point to multiple pattern directories using this setting.
163
165
  # Note that Grok will read all files in the directory matching the patterns_files_glob
164
- # and assume its a pattern file (including any tilde backup files)
166
+ # and assume it's a pattern file (including any tilde backup files)
165
167
  # [source,ruby]
166
168
  # patterns_dir => ["/opt/logstash/patterns", "/opt/logstash/extra_patterns"]
167
169
  #
@@ -193,6 +195,15 @@
193
195
  # successful match
194
196
  config :tag_on_failure, :validate => :array, :default => ["_grokparsefailure"]
195
197
 
198
+ # Attempt to terminate regexps after this amount of time.
199
+ # This applies per pattern if multiple patterns are applied
200
+ # This will never timeout early, but may take a little longer to timeout.
201
+ # Actual timeout is approximate based on a 250ms quantization.
202
+ config :timeout_millis, :validate => :number, :default => 2000
203
+
204
+ # Tag to apply if a grok regexp times out.
205
+ config :tag_on_timeout, :validate => :string, :default => '_groktimeout'
206
+
196
207
  # The fields to overwrite.
197
208
  #
198
209
  # This allows you to overwrite a value in a field that already exists.
@@ -223,6 +234,9 @@
223
234
  super(params)
224
235
  # a cache of capture name handler methods.
225
236
  @handlers = {}
237
+
238
+ @timeout_enforcer = TimeoutEnforcer.new(@logger, @timeout_millis * 1000000)
239
+ @timeout_enforcer.start!
226
240
  end
227
241
 
228
242
  public
@@ -264,8 +278,11 @@
264
278
  done = false
265
279
 
266
280
  @logger.debug? and @logger.debug("Running grok filter", :event => event);
281
+
267
282
  @patterns.each do |field, groks|
268
- if match(groks, field, event)
283
+ success = match(groks, field, event)
284
+
285
+ if success
269
286
  matched = true
270
287
  break if @break_on_match
271
288
  end
@@ -277,10 +294,14 @@
277
294
  filter_matched(event)
278
295
  else
279
296
  metric.increment(:failures)
280
- @tag_on_failure.each{|tag| event.tag(tag)}
297
+ @tag_on_failure.each {|tag| event.tag(tag)}
281
298
  end
282
299
 
283
300
  @logger.debug? and @logger.debug("Event now: ", :event => event)
301
+ rescue ::LogStash::Filters::Grok::TimeoutException => e
302
+ @logger.warn(e.message)
303
+ metric.increment(:timeouts)
304
+ event.tag(@tag_on_timeout)
284
305
  end # def filter
285
306
 
286
307
  private
@@ -289,28 +310,32 @@
289
310
  if input.is_a?(Array)
290
311
  success = false
291
312
  input.each do |input|
292
- success |= match_against_groks(groks, input, event)
313
+ success |= match_against_groks(groks, field, input, event)
293
314
  end
294
315
  return success
295
316
  else
296
- return match_against_groks(groks, input, event)
317
+ match_against_groks(groks, field, input, event)
297
318
  end
298
319
  rescue StandardError => e
299
- @logger.warn("Grok regexp threw exception", :exception => e.message)
320
+ @logger.warn("Grok regexp threw exception", :exception => e.message, :backtrace => e.backtrace, :class => e.class.name)
321
+ return false
300
322
  end
301
-
323
+
302
324
  private
303
- def match_against_groks(groks, input, event)
325
+ def match_against_groks(groks, field, input, event)
326
+ input = input.to_s
304
327
  matched = false
305
328
  groks.each do |grok|
306
329
  # Convert anything else to string (number, hash, etc)
307
- matched = grok.match_and_capture(input.to_s) do |field, value|
308
- matched = true
309
- handle(field, value, event)
330
+
331
+ matched = @timeout_enforcer.grok_till_timeout(event, grok, field, input) { grok.execute(input) }
332
+ if matched
333
+ grok.capture(matched) {|field, value| handle(field, value, event)}
334
+ break if @break_on_match
310
335
  end
311
- break if matched and @break_on_match
312
336
  end
313
- return matched
337
+
338
+ matched
314
339
  end
315
340
 
316
341
  private
@@ -364,4 +389,8 @@
364
389
  end
365
390
  end # def add_patterns_from_files
366
391
 
392
+ def close
393
+ @timeout_handler.stop!
394
+ end
395
+
367
396
  end # class LogStash::Filters::Grok
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
 
3
3
  s.name = 'logstash-filter-grok'
4
- s.version = '3.1.2'
4
+ s.version = '3.2.0'
5
5
  s.licenses = ['Apache License (2.0)']
6
6
  s.summary = "Parse arbitrary text and structure it."
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"
@@ -22,7 +22,7 @@ Gem::Specification.new do |s|
22
22
  # Gem dependencies
23
23
  s.add_runtime_dependency "logstash-core-plugin-api", ">= 1.60", "<= 2.99"
24
24
 
25
- s.add_runtime_dependency 'jls-grok', '~> 0.11.1'
25
+ s.add_runtime_dependency 'jls-grok', '~> 0.11.3'
26
26
  s.add_runtime_dependency 'logstash-patterns-core'
27
27
 
28
28
  s.add_development_dependency 'logstash-devutils'
@@ -407,12 +407,30 @@ describe LogStash::Filters::Grok do
407
407
  end
408
408
  end
409
409
 
410
+ describe "timeout on failure" do
411
+ config <<-CONFIG
412
+ filter {
413
+ grok {
414
+ match => {
415
+ message => "(.*a){30}"
416
+ }
417
+ timeout_millis => 100
418
+ }
419
+ }
420
+ CONFIG
421
+
422
+ sample "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" do
423
+ expect(subject.get("tags")).to include("_groktimeout")
424
+ expect(subject.get("tags")).not_to include("_grokparsefailure")
425
+ end
426
+ end
427
+
410
428
  describe "tagging on failure" do
411
429
  config <<-CONFIG
412
430
  filter {
413
431
  grok {
414
432
  match => { "message" => "matchme %{NUMBER:fancy}" }
415
- tag_on_failure => false
433
+ tag_on_failure => not_a_match
416
434
  }
417
435
  }
418
436
  CONFIG
@@ -422,7 +440,7 @@ describe LogStash::Filters::Grok do
422
440
  end
423
441
 
424
442
  sample "this will not be matched" do
425
- insist { subject.get("tags") }.include?("false")
443
+ insist { subject.get("tags") }.include?("not_a_match")
426
444
  end
427
445
  end
428
446
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-filter-grok
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.2
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elastic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-14 00:00:00.000000000 Z
11
+ date: 2016-08-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -35,7 +35,7 @@ dependencies:
35
35
  requirements:
36
36
  - - "~>"
37
37
  - !ruby/object:Gem::Version
38
- version: 0.11.1
38
+ version: 0.11.3
39
39
  name: jls-grok
40
40
  prerelease: false
41
41
  type: :runtime
@@ -43,7 +43,7 @@ dependencies:
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: 0.11.1
46
+ version: 0.11.3
47
47
  - !ruby/object:Gem::Dependency
48
48
  requirement: !ruby/object:Gem::Requirement
49
49
  requirements:
@@ -85,6 +85,8 @@ files:
85
85
  - NOTICE.TXT
86
86
  - README.md
87
87
  - lib/logstash/filters/grok.rb
88
+ - lib/logstash/filters/grok/timeout_enforcer.rb
89
+ - lib/logstash/filters/grok/timeout_exception.rb
88
90
  - logstash-filter-grok.gemspec
89
91
  - spec/filters/grok_spec.rb
90
92
  homepage: http://www.elastic.co/guide/en/logstash/current/index.html
@@ -109,7 +111,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
111
  version: '0'
110
112
  requirements: []
111
113
  rubyforge_project:
112
- rubygems_version: 2.6.3
114
+ rubygems_version: 2.4.8
113
115
  signing_key:
114
116
  specification_version: 4
115
117
  summary: Parse arbitrary text and structure it.