logstash-filter-range 0.1.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 +15 -0
- data/.gitignore +4 -0
- data/Gemfile +3 -0
- data/Rakefile +6 -0
- data/lib/logstash/filters/range.rb +143 -0
- data/logstash-filter-range.gemspec +26 -0
- data/rakelib/publish.rake +9 -0
- data/rakelib/vendor.rake +169 -0
- data/spec/filters/range_spec.rb +169 -0
- metadata +76 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
Nzk5Y2ZkNTcwNTczZDA1YjEyYjY0ZDZjMjZkNGE2MGI2NmE3M2E4Yg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
M2YwYjc5Mjc5ZGY1NjVlY2VlMWZkNWJhOThhMjNkYzdiMGJkMjI0Ng==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
ODk3ODk4NjU3MzdmM2M0MTk3ZGViN2ZiOTVjMjYxYjZkZWM1NThmYTAxMzc3
|
10
|
+
M2JlZmRjYTQwY2M0YmU1MzEwNDRmMTA0MjU0ZTM3ZDM3MjFmZTA3MTk4ZjMx
|
11
|
+
YWQxMGNiNGM2NjAyNjA3Yzg3YjNmN2ViNDZkYzZmNWJlOWU0MGQ=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
YjQ0ZGE0ZTZhMDc3NGIyNWE2MTUwNDNkNDA1NTE4NGI3NzIzNzBkMmM0ODdl
|
14
|
+
ZTAyMTkyZmJiMmMwMWUwZTc5YjNiNjVhNGMyYjQwMzlkOWM4ZGIzZWMzNGVk
|
15
|
+
NTc3MjhlZTM0ZjM5M2MwNDY0OWU2MjU1M2VhZThjNmMzOGY1MmU=
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/filters/base"
|
3
|
+
require "logstash/namespace"
|
4
|
+
|
5
|
+
|
6
|
+
# This filter is used to check that certain fields are within expected size/length ranges.
|
7
|
+
# Supported types are numbers and strings.
|
8
|
+
# Numbers are checked to be within numeric value range.
|
9
|
+
# Strings are checked to be within string length range.
|
10
|
+
# More than one range can be specified for same fieldname, actions will be applied incrementally.
|
11
|
+
# When field value is within a specified range an action will be taken.
|
12
|
+
# Supported actions are drop event, add tag, or add field with specified value.
|
13
|
+
#
|
14
|
+
# Example use cases are for histogram-like tagging of events
|
15
|
+
# or for finding anomaly values in fields or too big events that should be dropped.
|
16
|
+
|
17
|
+
class LogStash::Filters::Range < LogStash::Filters::Base
|
18
|
+
config_name "range"
|
19
|
+
milestone 1
|
20
|
+
|
21
|
+
# An array of field, min, max, action tuples.
|
22
|
+
# Example:
|
23
|
+
#
|
24
|
+
# filter {
|
25
|
+
# %PLUGIN% {
|
26
|
+
# ranges => [ "message", 0, 10, "tag:short",
|
27
|
+
# "message", 11, 100, "tag:medium",
|
28
|
+
# "message", 101, 1000, "tag:long",
|
29
|
+
# "message", 1001, 1e1000, "drop",
|
30
|
+
# "duration", 0, 100, "field:latency:fast",
|
31
|
+
# "duration", 101, 200, "field:latency:normal",
|
32
|
+
# "duration", 201, 1000, "field:latency:slow",
|
33
|
+
# "duration", 1001, 1e1000, "field:latency:outlier",
|
34
|
+
# "requests", 0, 10, "tag:too_few_%{host}_requests" ]
|
35
|
+
# }
|
36
|
+
# }
|
37
|
+
#
|
38
|
+
# Supported actions are drop tag or field with specified value.
|
39
|
+
# Added tag names and field names and field values can have %{dynamic} values.
|
40
|
+
#
|
41
|
+
# TODO(piavlo): The action syntax is ugly at the moment due to logstash grammar limitations - arrays grammar should support
|
42
|
+
# TODO(piavlo): simple not nested hashses as values in addition to numaric and string values to prettify the syntax.
|
43
|
+
config :ranges, :validate => :array, :default => []
|
44
|
+
|
45
|
+
# Negate the range match logic, events should be outsize of the specified range to match.
|
46
|
+
config :negate, :validate => :boolean, :default => false
|
47
|
+
|
48
|
+
public
|
49
|
+
def register
|
50
|
+
if @ranges.length % 4 != 0
|
51
|
+
raise "#{self.class.name}: ranges array should consist of 4 field tuples (field,min,max,action)"
|
52
|
+
end
|
53
|
+
|
54
|
+
@range_tuples = {}
|
55
|
+
|
56
|
+
while !@ranges.empty?
|
57
|
+
fieldname, min, max, action = @ranges.shift(4)
|
58
|
+
|
59
|
+
raise "#{self.class.name}: range field name value should be a string" if !fieldname.is_a?(String)
|
60
|
+
raise "#{self.class.name}: range min value should be a number" if !min.is_a?(Integer) and !min.is_a?(Float)
|
61
|
+
raise "#{self.class.name}: range max value should be a number" if !max.is_a?(Integer) and !max.is_a?(Float)
|
62
|
+
raise "#{self.class.name}: range action value should be a string" if !action.is_a?(String)
|
63
|
+
|
64
|
+
action = action.split(':')
|
65
|
+
|
66
|
+
case action.first
|
67
|
+
when "drop"
|
68
|
+
raise "#{self.class.name}: drop action does not accept any parameters" unless action.length == 1
|
69
|
+
action = { :name => :drop }
|
70
|
+
when "tag"
|
71
|
+
raise "#{self.class.name}: tag action accepts exactly one arg which is a tag name" unless action.length == 2
|
72
|
+
action = { :name => :add_tag, :tag => action.last }
|
73
|
+
when "field"
|
74
|
+
raise "#{self.class.name}: field action accepts exactly 2 args which are a field name and field value" unless action.length == 3
|
75
|
+
if action.last == action.last.to_i.to_s
|
76
|
+
value = action.last.to_i
|
77
|
+
elsif action.last == action.last.to_f.to_s
|
78
|
+
value = action.last.to_f
|
79
|
+
else
|
80
|
+
value = action.last
|
81
|
+
end
|
82
|
+
action = { :name => :add_field, :field => action[1], :value => value }
|
83
|
+
else
|
84
|
+
raise "#{self.class.name}: unsupported action #{action}"
|
85
|
+
end
|
86
|
+
|
87
|
+
@range_tuples[fieldname] ||= []
|
88
|
+
@range_tuples[fieldname] << { :min => min, :max => max, :action => action }
|
89
|
+
end
|
90
|
+
end # def register
|
91
|
+
|
92
|
+
|
93
|
+
public
|
94
|
+
def filter(event)
|
95
|
+
return unless filter?(event)
|
96
|
+
|
97
|
+
@range_tuples.each_key do |fieldname|
|
98
|
+
if event.include?(fieldname)
|
99
|
+
@range_tuples[fieldname].each do |range|
|
100
|
+
matched = false
|
101
|
+
|
102
|
+
field = event[fieldname]
|
103
|
+
case field
|
104
|
+
when Integer
|
105
|
+
matched = field.between?(range[:min], range[:max])
|
106
|
+
when Float
|
107
|
+
matched = field.between?(range[:min], range[:max])
|
108
|
+
when String
|
109
|
+
matched = field.length.between?(range[:min], range[:max])
|
110
|
+
else
|
111
|
+
@logger.warn("#{self.class.name}: action field value has unsupported type")
|
112
|
+
end
|
113
|
+
|
114
|
+
matched = !matched if @negate
|
115
|
+
next unless matched
|
116
|
+
|
117
|
+
case range[:action][:name]
|
118
|
+
when :drop
|
119
|
+
@logger.debug? and @logger.debug("#{self.class.name}: dropping event due to range match", :event => event)
|
120
|
+
event.cancel
|
121
|
+
return
|
122
|
+
when :add_tag
|
123
|
+
@logger.debug? and @logger.debug("#{self.class.name}: adding tag due to range match",
|
124
|
+
:event => event, :tag => range[:action][:tag] )
|
125
|
+
event.tag(event.sprintf(range[:action][:tag]))
|
126
|
+
when :add_field
|
127
|
+
@logger.debug? and @logger.debug("#{self.class.name}: adding field due to range match",
|
128
|
+
:event => event, :field => range[:action][:field], :value => range[:action][:value])
|
129
|
+
new_field = event.sprintf(range[:action][:field])
|
130
|
+
if event[new_field]
|
131
|
+
event[new_field] = [event[new_field]] if !event[new_field].is_a?(Array)
|
132
|
+
event[new_field] << event.sprintf(range[:action][:value])
|
133
|
+
else
|
134
|
+
event[new_field] = range[:action][:value].is_a?(String) ? event.sprintf(range[:action][:value]) : range[:action][:value]
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
filter_matched(event)
|
142
|
+
end # def filter
|
143
|
+
end # class LogStash::Filters::Range
|
@@ -0,0 +1,26 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
|
3
|
+
s.name = 'logstash-filter-range'
|
4
|
+
s.version = '0.1.0'
|
5
|
+
s.licenses = ['Apache License (2.0)']
|
6
|
+
s.summary = "This filter is used to check that certain fields are within expected size/length ranges."
|
7
|
+
s.description = "This filter is used to check that certain fields are within expected size/length ranges."
|
8
|
+
s.authors = ["Elasticsearch"]
|
9
|
+
s.email = 'richard.pijnenburg@elasticsearch.com'
|
10
|
+
s.homepage = "http://logstash.net/"
|
11
|
+
s.require_paths = ["lib"]
|
12
|
+
|
13
|
+
# Files
|
14
|
+
s.files = `git ls-files`.split($\)
|
15
|
+
|
16
|
+
# Tests
|
17
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
18
|
+
|
19
|
+
# Special flag to let us know this is actually a logstash plugin
|
20
|
+
s.metadata = { "logstash_plugin" => "true", "group" => "filter" }
|
21
|
+
|
22
|
+
# Gem dependencies
|
23
|
+
s.add_runtime_dependency 'logstash', '>= 1.4.0', '< 2.0.0'
|
24
|
+
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require "gem_publisher"
|
2
|
+
|
3
|
+
desc "Publish gem to RubyGems.org"
|
4
|
+
task :publish_gem do |t|
|
5
|
+
gem_file = Dir.glob(File.expand_path('../*.gemspec',File.dirname(__FILE__))).first
|
6
|
+
gem = GemPublisher.publish_if_updated(gem_file, :rubygems)
|
7
|
+
puts "Published #{gem}" if gem
|
8
|
+
end
|
9
|
+
|
data/rakelib/vendor.rake
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
require "net/http"
|
2
|
+
require "uri"
|
3
|
+
require "digest/sha1"
|
4
|
+
|
5
|
+
def vendor(*args)
|
6
|
+
return File.join("vendor", *args)
|
7
|
+
end
|
8
|
+
|
9
|
+
directory "vendor/" => ["vendor"] do |task, args|
|
10
|
+
mkdir task.name
|
11
|
+
end
|
12
|
+
|
13
|
+
def fetch(url, sha1, output)
|
14
|
+
|
15
|
+
puts "Downloading #{url}"
|
16
|
+
actual_sha1 = download(url, output)
|
17
|
+
|
18
|
+
if actual_sha1 != sha1
|
19
|
+
fail "SHA1 does not match (expected '#{sha1}' but got '#{actual_sha1}')"
|
20
|
+
end
|
21
|
+
end # def fetch
|
22
|
+
|
23
|
+
def file_fetch(url, sha1)
|
24
|
+
filename = File.basename( URI(url).path )
|
25
|
+
output = "vendor/#{filename}"
|
26
|
+
task output => [ "vendor/" ] do
|
27
|
+
begin
|
28
|
+
actual_sha1 = file_sha1(output)
|
29
|
+
if actual_sha1 != sha1
|
30
|
+
fetch(url, sha1, output)
|
31
|
+
end
|
32
|
+
rescue Errno::ENOENT
|
33
|
+
fetch(url, sha1, output)
|
34
|
+
end
|
35
|
+
end.invoke
|
36
|
+
|
37
|
+
return output
|
38
|
+
end
|
39
|
+
|
40
|
+
def file_sha1(path)
|
41
|
+
digest = Digest::SHA1.new
|
42
|
+
fd = File.new(path, "r")
|
43
|
+
while true
|
44
|
+
begin
|
45
|
+
digest << fd.sysread(16384)
|
46
|
+
rescue EOFError
|
47
|
+
break
|
48
|
+
end
|
49
|
+
end
|
50
|
+
return digest.hexdigest
|
51
|
+
ensure
|
52
|
+
fd.close if fd
|
53
|
+
end
|
54
|
+
|
55
|
+
def download(url, output)
|
56
|
+
uri = URI(url)
|
57
|
+
digest = Digest::SHA1.new
|
58
|
+
tmp = "#{output}.tmp"
|
59
|
+
Net::HTTP.start(uri.host, uri.port, :use_ssl => (uri.scheme == "https")) do |http|
|
60
|
+
request = Net::HTTP::Get.new(uri.path)
|
61
|
+
http.request(request) do |response|
|
62
|
+
fail "HTTP fetch failed for #{url}. #{response}" if [200, 301].include?(response.code)
|
63
|
+
size = (response["content-length"].to_i || -1).to_f
|
64
|
+
count = 0
|
65
|
+
File.open(tmp, "w") do |fd|
|
66
|
+
response.read_body do |chunk|
|
67
|
+
fd.write(chunk)
|
68
|
+
digest << chunk
|
69
|
+
if size > 0 && $stdout.tty?
|
70
|
+
count += chunk.bytesize
|
71
|
+
$stdout.write(sprintf("\r%0.2f%%", count/size * 100))
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
$stdout.write("\r \r") if $stdout.tty?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
File.rename(tmp, output)
|
80
|
+
|
81
|
+
return digest.hexdigest
|
82
|
+
rescue SocketError => e
|
83
|
+
puts "Failure while downloading #{url}: #{e}"
|
84
|
+
raise
|
85
|
+
ensure
|
86
|
+
File.unlink(tmp) if File.exist?(tmp)
|
87
|
+
end # def download
|
88
|
+
|
89
|
+
def untar(tarball, &block)
|
90
|
+
require "archive/tar/minitar"
|
91
|
+
tgz = Zlib::GzipReader.new(File.open(tarball))
|
92
|
+
# Pull out typesdb
|
93
|
+
tar = Archive::Tar::Minitar::Input.open(tgz)
|
94
|
+
tar.each do |entry|
|
95
|
+
path = block.call(entry)
|
96
|
+
next if path.nil?
|
97
|
+
parent = File.dirname(path)
|
98
|
+
|
99
|
+
mkdir_p parent unless File.directory?(parent)
|
100
|
+
|
101
|
+
# Skip this file if the output file is the same size
|
102
|
+
if entry.directory?
|
103
|
+
mkdir path unless File.directory?(path)
|
104
|
+
else
|
105
|
+
entry_mode = entry.instance_eval { @mode } & 0777
|
106
|
+
if File.exists?(path)
|
107
|
+
stat = File.stat(path)
|
108
|
+
# TODO(sissel): Submit a patch to archive-tar-minitar upstream to
|
109
|
+
# expose headers in the entry.
|
110
|
+
entry_size = entry.instance_eval { @size }
|
111
|
+
# If file sizes are same, skip writing.
|
112
|
+
next if stat.size == entry_size && (stat.mode & 0777) == entry_mode
|
113
|
+
end
|
114
|
+
puts "Extracting #{entry.full_name} from #{tarball} #{entry_mode.to_s(8)}"
|
115
|
+
File.open(path, "w") do |fd|
|
116
|
+
# eof? check lets us skip empty files. Necessary because the API provided by
|
117
|
+
# Archive::Tar::Minitar::Reader::EntryStream only mostly acts like an
|
118
|
+
# IO object. Something about empty files in this EntryStream causes
|
119
|
+
# IO.copy_stream to throw "can't convert nil into String" on JRuby
|
120
|
+
# TODO(sissel): File a bug about this.
|
121
|
+
while !entry.eof?
|
122
|
+
chunk = entry.read(16384)
|
123
|
+
fd.write(chunk)
|
124
|
+
end
|
125
|
+
#IO.copy_stream(entry, fd)
|
126
|
+
end
|
127
|
+
File.chmod(entry_mode, path)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
tar.close
|
131
|
+
File.unlink(tarball) if File.file?(tarball)
|
132
|
+
end # def untar
|
133
|
+
|
134
|
+
def ungz(file)
|
135
|
+
|
136
|
+
outpath = file.gsub('.gz', '')
|
137
|
+
tgz = Zlib::GzipReader.new(File.open(file))
|
138
|
+
begin
|
139
|
+
File.open(outpath, "w") do |out|
|
140
|
+
IO::copy_stream(tgz, out)
|
141
|
+
end
|
142
|
+
File.unlink(file)
|
143
|
+
rescue
|
144
|
+
File.unlink(outpath) if File.file?(outpath)
|
145
|
+
raise
|
146
|
+
end
|
147
|
+
tgz.close
|
148
|
+
end
|
149
|
+
|
150
|
+
desc "Process any vendor files required for this plugin"
|
151
|
+
task "vendor" do |task, args|
|
152
|
+
|
153
|
+
@files.each do |file|
|
154
|
+
download = file_fetch(file['url'], file['sha1'])
|
155
|
+
if download =~ /.tar.gz/
|
156
|
+
prefix = download.gsub('.tar.gz', '').gsub('vendor/', '')
|
157
|
+
untar(download) do |entry|
|
158
|
+
if !file['files'].nil?
|
159
|
+
next unless file['files'].include?(entry.full_name.gsub(prefix, ''))
|
160
|
+
out = entry.full_name.split("/").last
|
161
|
+
end
|
162
|
+
File.join('vendor', out)
|
163
|
+
end
|
164
|
+
elsif download =~ /.gz/
|
165
|
+
ungz(download)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "logstash/filters/range"
|
3
|
+
|
4
|
+
describe LogStash::Filters::Range do
|
5
|
+
|
6
|
+
|
7
|
+
describe "range match integer field on tag action" do
|
8
|
+
config <<-CONFIG
|
9
|
+
filter {
|
10
|
+
range {
|
11
|
+
ranges => [ "duration", 10, 100, "tag:cool",
|
12
|
+
"duration", 1, 1, "tag:boring" ]
|
13
|
+
}
|
14
|
+
}
|
15
|
+
CONFIG
|
16
|
+
|
17
|
+
sample("duration" => 50) do
|
18
|
+
insist { subject["tags"] }.include?("cool")
|
19
|
+
reject { subject["tags"] }.include?("boring")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "range match float field on tag action" do
|
24
|
+
config <<-CONFIG
|
25
|
+
filter {
|
26
|
+
range {
|
27
|
+
ranges => [ "duration", 0, 100, "tag:cool",
|
28
|
+
"duration", 0, 1, "tag:boring" ]
|
29
|
+
}
|
30
|
+
}
|
31
|
+
CONFIG
|
32
|
+
|
33
|
+
sample("duration" => 50.0) do
|
34
|
+
insist { subject["tags"] }.include?("cool")
|
35
|
+
reject { subject["tags"] }.include?("boring")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "range match string field on tag action" do
|
40
|
+
config <<-CONFIG
|
41
|
+
filter {
|
42
|
+
range {
|
43
|
+
ranges => [ "length", 0, 10, "tag:cool",
|
44
|
+
"length", 0, 1, "tag:boring" ]
|
45
|
+
}
|
46
|
+
}
|
47
|
+
CONFIG
|
48
|
+
|
49
|
+
sample("length" => "123456789") do
|
50
|
+
insist { subject["tags"] }.include?("cool")
|
51
|
+
reject { subject["tags"] }.include?("boring")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "range match with negation" do
|
56
|
+
config <<-CONFIG
|
57
|
+
filter {
|
58
|
+
range {
|
59
|
+
ranges => [ "length", 0, 10, "tag:cool",
|
60
|
+
"length", 0, 1, "tag:boring" ]
|
61
|
+
negate => true
|
62
|
+
}
|
63
|
+
}
|
64
|
+
CONFIG
|
65
|
+
|
66
|
+
sample("length" => "123456789") do
|
67
|
+
reject { subject["tags"] }.include?("cool")
|
68
|
+
insist { subject["tags"] }.include?("boring")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "range match on drop action" do
|
73
|
+
config <<-CONFIG
|
74
|
+
filter {
|
75
|
+
range {
|
76
|
+
ranges => [ "length", 0, 10, "drop" ]
|
77
|
+
}
|
78
|
+
}
|
79
|
+
CONFIG
|
80
|
+
|
81
|
+
sample("length" => "123456789") do
|
82
|
+
insist { subject }.nil?
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "range match on field action with string value" do
|
87
|
+
config <<-CONFIG
|
88
|
+
filter {
|
89
|
+
range {
|
90
|
+
ranges => [ "duration", 10, 100, "field:cool:foo",
|
91
|
+
"duration", 1, 1, "field:boring:foo" ]
|
92
|
+
}
|
93
|
+
}
|
94
|
+
CONFIG
|
95
|
+
|
96
|
+
sample("duration" => 50) do
|
97
|
+
insist { subject }.include?("cool")
|
98
|
+
insist { subject["cool"] } == "foo"
|
99
|
+
reject { subject }.include?("boring")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe "range match on field action with integer value" do
|
104
|
+
config <<-CONFIG
|
105
|
+
filter {
|
106
|
+
range {
|
107
|
+
ranges => [ "duration", 10, 100, "field:cool:666",
|
108
|
+
"duration", 1, 1, "field:boring:666" ]
|
109
|
+
}
|
110
|
+
}
|
111
|
+
CONFIG
|
112
|
+
|
113
|
+
sample("duration" => 50) do
|
114
|
+
insist { subject }.include?("cool")
|
115
|
+
insist { subject["cool"] } == 666
|
116
|
+
reject { subject }.include?("boring")
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe "range match on field action with float value" do
|
121
|
+
config <<-CONFIG
|
122
|
+
filter {
|
123
|
+
range {
|
124
|
+
ranges => [ "duration", 10, 100, "field:cool:3.14",
|
125
|
+
"duration", 1, 1, "field:boring:3.14" ]
|
126
|
+
}
|
127
|
+
}
|
128
|
+
CONFIG
|
129
|
+
|
130
|
+
sample("duration" => 50) do
|
131
|
+
insist { subject }.include?("cool")
|
132
|
+
insist { subject["cool"] } == 3.14
|
133
|
+
reject { subject }.include?("boring")
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
describe "range match on tag action with dynamic string value" do
|
138
|
+
config <<-CONFIG
|
139
|
+
filter {
|
140
|
+
range {
|
141
|
+
ranges => [ "duration", 10, 100, "tag:cool_%{dynamic}_dynamic",
|
142
|
+
"duration", 1, 1, "tag:boring_%{dynamic}_dynamic" ]
|
143
|
+
}
|
144
|
+
}
|
145
|
+
CONFIG
|
146
|
+
|
147
|
+
sample("duration" => 50, "dynamic" => "and") do
|
148
|
+
insist { subject["tags"] }.include?("cool_and_dynamic")
|
149
|
+
reject { subject["tags"] }.include?("boring_and_dynamic")
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
describe "range match on field action with dynamic string field and value" do
|
154
|
+
config <<-CONFIG
|
155
|
+
filter {
|
156
|
+
range {
|
157
|
+
ranges => [ "duration", 10, 100, "field:cool_%{dynamic}_dynamic:foo_%{dynamic}_bar",
|
158
|
+
"duration", 1, 1, "field:boring_%{dynamic}_dynamic:foo_%{dynamic}_bar" ]
|
159
|
+
}
|
160
|
+
}
|
161
|
+
CONFIG
|
162
|
+
|
163
|
+
sample("duration" => 50, "dynamic" => "and") do
|
164
|
+
insist { subject }.include?("cool_and_dynamic")
|
165
|
+
insist { subject["cool_and_dynamic"] } == "foo_and_bar"
|
166
|
+
reject { subject }.include?("boring_and_dynamic")
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: logstash-filter-range
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Elasticsearch
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-11-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: logstash
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.4.0
|
20
|
+
- - <
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 2.0.0
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.4.0
|
30
|
+
- - <
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.0.0
|
33
|
+
description: This filter is used to check that certain fields are within expected
|
34
|
+
size/length ranges.
|
35
|
+
email: richard.pijnenburg@elasticsearch.com
|
36
|
+
executables: []
|
37
|
+
extensions: []
|
38
|
+
extra_rdoc_files: []
|
39
|
+
files:
|
40
|
+
- .gitignore
|
41
|
+
- Gemfile
|
42
|
+
- Rakefile
|
43
|
+
- lib/logstash/filters/range.rb
|
44
|
+
- logstash-filter-range.gemspec
|
45
|
+
- rakelib/publish.rake
|
46
|
+
- rakelib/vendor.rake
|
47
|
+
- spec/filters/range_spec.rb
|
48
|
+
homepage: http://logstash.net/
|
49
|
+
licenses:
|
50
|
+
- Apache License (2.0)
|
51
|
+
metadata:
|
52
|
+
logstash_plugin: 'true'
|
53
|
+
group: filter
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
requirements: []
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 2.4.1
|
71
|
+
signing_key:
|
72
|
+
specification_version: 4
|
73
|
+
summary: This filter is used to check that certain fields are within expected size/length
|
74
|
+
ranges.
|
75
|
+
test_files:
|
76
|
+
- spec/filters/range_spec.rb
|