logstash-filter-aggregate 2.5.0 → 2.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +1 -1
- data/lib/logstash/filters/aggregate.rb +86 -32
- data/logstash-filter-aggregate.gemspec +1 -1
- data/spec/filters/aggregate_spec.rb +36 -4
- data/spec/filters/aggregate_spec_helper.rb +18 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a9d474bc096fd4b164adb655607abdc0d52b7f15
|
4
|
+
data.tar.gz: 9e7b57528e3201bea76ed198e080be776b0ed3af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6d08a02cdf7a32904e74f235f2f3888170cd535dce21aeb5767c6ddfa302920beb2a6c4216aac1b485ccb502a0f0180d687b5276e7e18ec691e4c8ae2c5895e2
|
7
|
+
data.tar.gz: 5d509ab8cf7d26fbce5cf4f53d8255ff7c203492c9fe96aab363afa73dedcda278e2497a7be91e7f8f7ce58f08bb42cfd93f08afd94628c4d54b6cd671dfeee9
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
## 2.5.1
|
2
|
+
- enhancement: when final flush occurs (just before Logstash shutdown), add `_aggregatefinalflush` tag on generated timeout events
|
3
|
+
- bugfix: when final flush occurs (just before Logstash shutdown), push last aggregate map as event (if push_previous_map_as_event=true)
|
4
|
+
- bugfix: fix 'timeout_task_id_field' feature when push_previous_map_as_event=true
|
5
|
+
- bugfix: fix aggregate_maps_path feature (bug since v2.4.0)
|
6
|
+
- internal: add debug logging
|
7
|
+
- internal: refactor flush management static variables
|
8
|
+
|
1
9
|
## 2.5.0
|
2
10
|
- new feature: add compatibility with Logstash 5
|
3
11
|
- breaking: need Logstash 2.4 or later
|
data/README.md
CHANGED
@@ -179,7 +179,7 @@ In that case, you don't want to wait task timeout to flush aggregation map.
|
|
179
179
|
map['town_name'] ||= []
|
180
180
|
event.to_hash.each do |key,value|
|
181
181
|
map[key] = value unless map.has_key?(key)
|
182
|
-
map[key] << value if map[key].is_a?(Array)
|
182
|
+
map[key] << value if map[key].is_a?(Array) and !value.is_a?(Array)
|
183
183
|
end
|
184
184
|
"
|
185
185
|
push_previous_map_as_event => true
|
@@ -194,7 +194,7 @@ require "logstash/util/decorators"
|
|
194
194
|
# map['town_name'] ||= []
|
195
195
|
# event.to_hash.each do |key,value|
|
196
196
|
# map[key] = value unless map.has_key?(key)
|
197
|
-
# map[key] << value if map[key].is_a?(Array)
|
197
|
+
# map[key] << value if map[key].is_a?(Array) and !value.is_a?(Array)
|
198
198
|
# end
|
199
199
|
# "
|
200
200
|
# push_previous_map_as_event => true
|
@@ -313,12 +313,14 @@ class LogStash::Filters::Aggregate < LogStash::Filters::Base
|
|
313
313
|
|
314
314
|
# Defines tags to add when a timeout event is generated and yield
|
315
315
|
config :timeout_tags, :validate => :array, :required => false, :default => []
|
316
|
-
|
317
316
|
|
317
|
+
|
318
|
+
# STATIC VARIABLES
|
319
|
+
|
320
|
+
|
318
321
|
# Default timeout (in seconds) when not defined in plugin configuration
|
319
322
|
DEFAULT_TIMEOUT = 1800
|
320
323
|
|
321
|
-
|
322
324
|
# This is the state of the filter.
|
323
325
|
# For each entry, key is "task_id" and value is a map freely updatable by 'code' config
|
324
326
|
@@aggregate_maps = {}
|
@@ -329,20 +331,26 @@ class LogStash::Filters::Aggregate < LogStash::Filters::Base
|
|
329
331
|
# Default timeout for task_id patterns where timeout is not defined in logstash filter configuration
|
330
332
|
@@default_timeout = nil
|
331
333
|
|
332
|
-
# For each "task_id" pattern, defines which Aggregate instance will
|
334
|
+
# For each "task_id" pattern, defines which Aggregate instance will process flush() call, processing expired Aggregate elements (older than timeout)
|
333
335
|
# For each entry, key is "task_id pattern" and value is "aggregate instance"
|
334
|
-
@@
|
336
|
+
@@flush_instance_map = {}
|
335
337
|
|
336
|
-
# last time where
|
337
|
-
@@
|
338
|
+
# last time where timeout management in flush() method was launched, per "task_id" pattern
|
339
|
+
@@last_flush_timestamp_map = {}
|
338
340
|
|
339
341
|
# flag indicating if aggregate_maps_path option has been already set on one aggregate instance
|
340
342
|
@@aggregate_maps_path_set = false
|
341
343
|
|
344
|
+
# defines which Aggregate instance will close Aggregate static variables
|
345
|
+
@@static_close_instance = nil
|
346
|
+
|
342
347
|
|
343
348
|
# Initialize plugin
|
344
349
|
public
|
345
350
|
def register
|
351
|
+
|
352
|
+
@logger.debug("Aggregate register call", :code => @code)
|
353
|
+
|
346
354
|
# process lambda expression to call in each filter call
|
347
355
|
eval("@codeblock = lambda { |event, map| #{@code} }", binding, "(aggregate filter code)")
|
348
356
|
|
@@ -355,11 +363,11 @@ class LogStash::Filters::Aggregate < LogStash::Filters::Base
|
|
355
363
|
|
356
364
|
# timeout management : define eviction_instance for current task_id pattern
|
357
365
|
if has_timeout_options?
|
358
|
-
if @@
|
366
|
+
if @@flush_instance_map.has_key?(@task_id)
|
359
367
|
# all timeout options have to be defined in only one aggregate filter per task_id pattern
|
360
368
|
raise LogStash::ConfigurationError, "Aggregate plugin: For task_id pattern #{@task_id}, there are more than one filter which defines timeout options. All timeout options have to be defined in only one aggregate filter per task_id pattern. Timeout options are : #{display_timeout_options}"
|
361
369
|
end
|
362
|
-
@@
|
370
|
+
@@flush_instance_map[@task_id] = self
|
363
371
|
@logger.debug("Aggregate timeout for '#{@task_id}' pattern: #{@timeout} seconds")
|
364
372
|
end
|
365
373
|
|
@@ -369,13 +377,19 @@ class LogStash::Filters::Aggregate < LogStash::Filters::Base
|
|
369
377
|
@logger.debug("Aggregate default timeout: #{@timeout} seconds")
|
370
378
|
end
|
371
379
|
|
372
|
-
#
|
380
|
+
# reinit static_close_instance (if necessary)
|
381
|
+
if !@@aggregate_maps_path_set && !@@static_close_instance.nil?
|
382
|
+
@@static_close_instance = nil
|
383
|
+
end
|
384
|
+
|
385
|
+
# check if aggregate_maps_path option has already been set on another instance else set @@aggregate_maps_path_set
|
373
386
|
if !@aggregate_maps_path.nil?
|
374
387
|
if @@aggregate_maps_path_set
|
375
388
|
@@aggregate_maps_path_set = false
|
376
389
|
raise LogStash::ConfigurationError, "Aggregate plugin: Option 'aggregate_maps_path' must be set on only one aggregate filter"
|
377
390
|
else
|
378
391
|
@@aggregate_maps_path_set = true
|
392
|
+
@@static_close_instance = self
|
379
393
|
end
|
380
394
|
end
|
381
395
|
|
@@ -394,21 +408,29 @@ class LogStash::Filters::Aggregate < LogStash::Filters::Base
|
|
394
408
|
# Called when logstash stops
|
395
409
|
public
|
396
410
|
def close
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
411
|
+
|
412
|
+
@logger.debug("Aggregate close call", :code => @code)
|
413
|
+
|
414
|
+
# define static close instance if none is already defined
|
415
|
+
@@static_close_instance = self if @@static_close_instance.nil?
|
416
|
+
|
417
|
+
if @@static_close_instance == self
|
418
|
+
# store aggregate maps to file (if option defined)
|
419
|
+
@@mutex.synchronize do
|
420
|
+
@@aggregate_maps.delete_if { |key, value| value.empty? }
|
421
|
+
if !@aggregate_maps_path.nil? && !@@aggregate_maps.empty?
|
422
|
+
File.open(@aggregate_maps_path, "w"){ |to_file| Marshal.dump(@@aggregate_maps, to_file) }
|
423
|
+
@logger.info("Aggregate maps stored to : #{@aggregate_maps_path}")
|
424
|
+
end
|
425
|
+
@@aggregate_maps.clear()
|
404
426
|
end
|
405
|
-
|
427
|
+
|
428
|
+
# reinit static variables for logstash reload
|
429
|
+
@@default_timeout = nil
|
430
|
+
@@flush_instance_map = {}
|
431
|
+
@@last_flush_timestamp_map = {}
|
432
|
+
@@aggregate_maps_path_set = false
|
406
433
|
end
|
407
|
-
|
408
|
-
# Protection against logstash reload
|
409
|
-
@@aggregate_maps_path_set = false if @@aggregate_maps_path_set
|
410
|
-
@@default_timeout = nil unless @@default_timeout.nil?
|
411
|
-
@@eviction_instance_map = {} unless @@eviction_instance_map.empty?
|
412
434
|
|
413
435
|
end
|
414
436
|
|
@@ -435,8 +457,7 @@ class LogStash::Filters::Aggregate < LogStash::Filters::Base
|
|
435
457
|
return if @map_action == "update"
|
436
458
|
# create new event from previous map, if @push_previous_map_as_event is enabled
|
437
459
|
if @push_previous_map_as_event && !@@aggregate_maps[@task_id].empty?
|
438
|
-
|
439
|
-
event_to_yield = create_timeout_event(previous_map, task_id)
|
460
|
+
event_to_yield = extract_previous_map_as_event()
|
440
461
|
end
|
441
462
|
aggregate_maps_element = LogStash::Filters::Aggregate::Element.new(Time.now);
|
442
463
|
@@aggregate_maps[@task_id][task_id] = aggregate_maps_element
|
@@ -448,6 +469,7 @@ class LogStash::Filters::Aggregate < LogStash::Filters::Base
|
|
448
469
|
# execute the code to read/update map and event
|
449
470
|
begin
|
450
471
|
@codeblock.call(event, map)
|
472
|
+
@logger.debug("Aggregate successful filter code execution", :code => @code)
|
451
473
|
noError = true
|
452
474
|
rescue => exception
|
453
475
|
@logger.error("Aggregate exception occurred",
|
@@ -477,6 +499,9 @@ class LogStash::Filters::Aggregate < LogStash::Filters::Base
|
|
477
499
|
# if @timeout_code is set, it will execute the timeout code on the created timeout event
|
478
500
|
# returns the newly created event
|
479
501
|
def create_timeout_event(aggregation_map, task_id)
|
502
|
+
|
503
|
+
@logger.debug("Aggregate create_timeout_event call with task_id '#{task_id}'")
|
504
|
+
|
480
505
|
event_to_yield = LogStash::Event.new(aggregation_map)
|
481
506
|
|
482
507
|
if @timeout_task_id_field
|
@@ -501,6 +526,14 @@ class LogStash::Filters::Aggregate < LogStash::Filters::Base
|
|
501
526
|
return event_to_yield
|
502
527
|
end
|
503
528
|
|
529
|
+
# Extract the previous map in aggregate maps, and return it as a new Logstash event
|
530
|
+
def extract_previous_map_as_event
|
531
|
+
previous_entry = @@aggregate_maps[@task_id].shift()
|
532
|
+
previous_task_id = previous_entry[0]
|
533
|
+
previous_map = previous_entry[1].map
|
534
|
+
return create_timeout_event(previous_map, previous_task_id)
|
535
|
+
end
|
536
|
+
|
504
537
|
# Necessary to indicate logstash to periodically call 'flush' method
|
505
538
|
def periodic_flush
|
506
539
|
true
|
@@ -508,22 +541,41 @@ class LogStash::Filters::Aggregate < LogStash::Filters::Base
|
|
508
541
|
|
509
542
|
# This method is invoked by LogStash every 5 seconds.
|
510
543
|
def flush(options = {})
|
544
|
+
|
545
|
+
@logger.debug("Aggregate flush call with #{options}")
|
546
|
+
|
511
547
|
# Protection against no timeout defined by logstash conf : define a default eviction instance with timeout = DEFAULT_TIMEOUT seconds
|
512
548
|
if @@default_timeout.nil?
|
513
549
|
@@default_timeout = DEFAULT_TIMEOUT
|
514
550
|
end
|
515
|
-
if !@@
|
516
|
-
@@
|
551
|
+
if !@@flush_instance_map.has_key?(@task_id)
|
552
|
+
@@flush_instance_map[@task_id] = self
|
517
553
|
@timeout = @@default_timeout
|
518
|
-
elsif @@
|
519
|
-
@@
|
554
|
+
elsif @@flush_instance_map[@task_id].timeout.nil?
|
555
|
+
@@flush_instance_map[@task_id].timeout = @@default_timeout
|
520
556
|
end
|
521
557
|
|
522
|
-
# Launch
|
523
|
-
if @@
|
558
|
+
# Launch timeout management only every interval of (@timeout / 2) seconds or at Logstash shutdown
|
559
|
+
if @@flush_instance_map[@task_id] == self && (!@@last_flush_timestamp_map.has_key?(@task_id) || Time.now > @@last_flush_timestamp_map[@task_id] + @timeout / 2 || options[:final])
|
524
560
|
events_to_flush = remove_expired_maps()
|
525
|
-
|
561
|
+
|
562
|
+
# at Logstash shutdown, if push_previous_map_as_event is enabled, it's important to force flush (particularly for jdbc input plugin)
|
563
|
+
if options[:final] && @push_previous_map_as_event && !@@aggregate_maps[@task_id].empty?
|
564
|
+
events_to_flush << extract_previous_map_as_event()
|
565
|
+
end
|
566
|
+
|
567
|
+
# tag flushed events, indicating "final flush" special event
|
568
|
+
if options[:final]
|
569
|
+
events_to_flush.each { |event_to_flush| event_to_flush.tag("_aggregatefinalflush") }
|
570
|
+
end
|
571
|
+
|
572
|
+
# update last flush timestamp
|
573
|
+
@@last_flush_timestamp_map[@task_id] = Time.now
|
574
|
+
|
575
|
+
# return events to flush into Logstash pipeline
|
526
576
|
return events_to_flush
|
577
|
+
else
|
578
|
+
return []
|
527
579
|
end
|
528
580
|
|
529
581
|
end
|
@@ -537,6 +589,8 @@ class LogStash::Filters::Aggregate < LogStash::Filters::Base
|
|
537
589
|
|
538
590
|
@@mutex.synchronize do
|
539
591
|
|
592
|
+
@logger.debug("Aggregate remove_expired_maps call with '#{@task_id}' pattern and #{@@aggregate_maps[@task_id].length} maps")
|
593
|
+
|
540
594
|
@@aggregate_maps[@task_id].delete_if do |key, element|
|
541
595
|
if element.creation_timestamp < min_timestamp
|
542
596
|
if @push_previous_map_as_event || @push_map_as_event_on_timeout
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'logstash-filter-aggregate'
|
3
|
-
s.version = '2.5.
|
3
|
+
s.version = '2.5.1'
|
4
4
|
s.licenses = ['Apache License (2.0)']
|
5
5
|
s.summary = 'The aim of this filter is to aggregate information available among several events (typically log lines) belonging to a same task, and finally push aggregated information into final task event.'
|
6
6
|
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'
|
@@ -6,8 +6,7 @@ require_relative "aggregate_spec_helper"
|
|
6
6
|
describe LogStash::Filters::Aggregate do
|
7
7
|
|
8
8
|
before(:each) do
|
9
|
-
|
10
|
-
aggregate_maps.clear()
|
9
|
+
reset_static_variables()
|
11
10
|
@start_filter = setup_filter({ "map_action" => "create", "code" => "map['sql_duration'] = 0" })
|
12
11
|
@update_filter = setup_filter({ "map_action" => "update", "code" => "map['sql_duration'] += event.get('duration')" })
|
13
12
|
@end_filter = setup_filter({"timeout_task_id_field" => "my_id", "push_map_as_event_on_timeout" => true, "map_action" => "update", "code" => "event.set('sql_duration', map['sql_duration'])", "end_of_task" => true, "timeout" => 5, "timeout_code" => "event.set('test', 'testValue')", "timeout_tags" => ["tag1", "tag2"] })
|
@@ -212,6 +211,9 @@ describe LogStash::Filters::Aggregate do
|
|
212
211
|
filter = store_filter.filter(start_event)
|
213
212
|
expect(aggregate_maps["%{taskid}"].size).to eq(1)
|
214
213
|
|
214
|
+
@end_filter.close()
|
215
|
+
expect(aggregate_maps).not_to be_empty
|
216
|
+
|
215
217
|
store_filter.close()
|
216
218
|
expect(File.exist?(store_file)).to be true
|
217
219
|
expect(aggregate_maps).to be_empty
|
@@ -232,6 +234,21 @@ describe LogStash::Filters::Aggregate do
|
|
232
234
|
end
|
233
235
|
end
|
234
236
|
|
237
|
+
context "Logstash reload occurs, " do
|
238
|
+
describe "close method is called, " do
|
239
|
+
it "reinitializes static variables" do
|
240
|
+
@end_filter.close()
|
241
|
+
expect(aggregate_maps).to be_empty
|
242
|
+
expect(taskid_eviction_instance).to be_nil
|
243
|
+
expect(static_close_instance).not_to be_nil
|
244
|
+
expect(aggregate_maps_path_set).to be false
|
245
|
+
|
246
|
+
@end_filter.register()
|
247
|
+
expect(static_close_instance).to be_nil
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
235
252
|
context "push_previous_map_as_event option is defined, " do
|
236
253
|
describe "when push_previous_map_as_event option is activated on another filter with same task_id pattern" do
|
237
254
|
it "should throw a LogStash::ConfigurationError" do
|
@@ -243,9 +260,9 @@ describe LogStash::Filters::Aggregate do
|
|
243
260
|
|
244
261
|
describe "when a new task id is detected, " do
|
245
262
|
it "should push previous map as new event" do
|
246
|
-
push_filter = setup_filter({ "task_id" => "%{ppm_id}", "code" => "map['ppm_id'] = event.get('ppm_id')", "push_previous_map_as_event" => true, "timeout" => 5 })
|
263
|
+
push_filter = setup_filter({ "task_id" => "%{ppm_id}", "code" => "map['ppm_id'] = event.get('ppm_id')", "push_previous_map_as_event" => true, "timeout" => 5, "timeout_task_id_field" => "timeout_task_id_field" })
|
247
264
|
push_filter.filter(event({"ppm_id" => "1"})) { |yield_event| fail "task 1 shouldn't have yield event" }
|
248
|
-
push_filter.filter(event({"ppm_id" => "2"})) { |yield_event| expect(yield_event.get("ppm_id")).to eq("1") }
|
265
|
+
push_filter.filter(event({"ppm_id" => "2"})) { |yield_event| expect(yield_event.get("ppm_id")).to eq("1") ; expect(yield_event.get("timeout_task_id_field")).to eq("1") }
|
249
266
|
expect(aggregate_maps["%{ppm_id}"].size).to eq(1)
|
250
267
|
end
|
251
268
|
end
|
@@ -263,6 +280,21 @@ describe LogStash::Filters::Aggregate do
|
|
263
280
|
expect(aggregate_maps["%{ppm_id}"].size).to eq(0)
|
264
281
|
end
|
265
282
|
end
|
283
|
+
|
284
|
+
describe "when Logstash shutdown happens, " do
|
285
|
+
it "flush method should return last map as new event even if timeout has not occured" do
|
286
|
+
push_filter = setup_filter({ "task_id" => "%{ppm_id}", "code" => "", "push_previous_map_as_event" => true, "timeout" => 4 })
|
287
|
+
push_filter.filter(event({"ppm_id" => "1"}))
|
288
|
+
events_to_flush = push_filter.flush({:final=>false})
|
289
|
+
expect(events_to_flush).to be_empty
|
290
|
+
expect(aggregate_maps["%{ppm_id}"].size).to eq(1)
|
291
|
+
events_to_flush = push_filter.flush({:final=>true})
|
292
|
+
expect(events_to_flush).not_to be_nil
|
293
|
+
expect(events_to_flush.size).to eq(1)
|
294
|
+
expect(events_to_flush[0].get("tags")).to eq(["_aggregatefinalflush"])
|
295
|
+
expect(aggregate_maps["%{ppm_id}"].size).to eq(0)
|
296
|
+
end
|
297
|
+
end
|
266
298
|
end
|
267
299
|
|
268
300
|
|
@@ -38,10 +38,26 @@ def aggregate_maps()
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def taskid_eviction_instance()
|
41
|
-
LogStash::Filters::Aggregate.class_variable_get(:@@
|
41
|
+
LogStash::Filters::Aggregate.class_variable_get(:@@flush_instance_map)["%{taskid}"]
|
42
|
+
end
|
43
|
+
|
44
|
+
def static_close_instance()
|
45
|
+
LogStash::Filters::Aggregate.class_variable_get(:@@static_close_instance)
|
46
|
+
end
|
47
|
+
|
48
|
+
def aggregate_maps_path_set()
|
49
|
+
LogStash::Filters::Aggregate.class_variable_get(:@@aggregate_maps_path_set)
|
42
50
|
end
|
43
51
|
|
44
52
|
def reset_timeout_management()
|
45
53
|
LogStash::Filters::Aggregate.class_variable_set(:@@default_timeout, nil)
|
46
|
-
LogStash::Filters::Aggregate.class_variable_get(:@@
|
54
|
+
LogStash::Filters::Aggregate.class_variable_get(:@@flush_instance_map).clear()
|
55
|
+
LogStash::Filters::Aggregate.class_variable_get(:@@last_flush_timestamp_map).clear()
|
56
|
+
end
|
57
|
+
|
58
|
+
def reset_static_variables()
|
59
|
+
reset_timeout_management()
|
60
|
+
aggregate_maps().clear()
|
61
|
+
LogStash::Filters::Aggregate.class_variable_set(:@@static_close_instance, nil)
|
62
|
+
LogStash::Filters::Aggregate.class_variable_set(:@@aggregate_maps_path_set, false)
|
47
63
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: logstash-filter-aggregate
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.5.
|
4
|
+
version: 2.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Elastic
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2017-01-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|