logstash-filter-kv 2.0.2 → 2.0.3
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 +5 -1
- data/README.md +3 -0
- data/lib/logstash/filters/kv.rb +51 -53
- data/logstash-filter-kv.gemspec +1 -1
- data/spec/filters/kv_spec.rb +107 -18
- metadata +12 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7823f827ecc2a7359e187f76eeef30ace026483d
|
4
|
+
data.tar.gz: 15efba89f825cfe3431ad6fc66f1fa8eb06ff1d9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b5c3322e32a0c28e133ec2a7b7a347019e208c70d7ec84afca3ec086c1d0389f3b20effce8392a8646b333e378dfa4a917d00f7b92731da3c2953ce6291d8604
|
7
|
+
data.tar.gz: f0fd946fba942d8e8c5d0c678e85619b522b32e12ba6b2bce49e2bf6166cc6744b5ed9838e94ebc267565b1336304778b8551d431e01978d3b11336b041d63a4
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
|
+
## 2.0.3
|
2
|
+
- fixed fixed short circuit expressions, some optimizations, added specs, PR #20
|
3
|
+
- fixed event field assignment, PR #21
|
4
|
+
|
1
5
|
## 2.0.0
|
2
|
-
- Plugins were updated to follow the new shutdown semantic, this mainly allows Logstash to instruct input plugins to terminate gracefully,
|
6
|
+
- Plugins were updated to follow the new shutdown semantic, this mainly allows Logstash to instruct input plugins to terminate gracefully,
|
3
7
|
instead of using Thread.raise on the plugins' threads. Ref: https://github.com/elastic/logstash/pull/3895
|
4
8
|
- Dependency on logstash-core update to 2.0
|
5
9
|
|
data/README.md
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# Logstash Plugin
|
2
2
|
|
3
|
+
[](http://build-eu-00.elastic.co/view/LS%20Plugins/view/LS%20Filters/job/logstash-plugin-filter-kv-unit/)
|
5
|
+
|
3
6
|
This is a plugin for [Logstash](https://github.com/elastic/logstash).
|
4
7
|
|
5
8
|
It is fully free and fully open source. The license is Apache 2.0, meaning you are pretty much free to use it however you want in whatever way.
|
data/lib/logstash/filters/kv.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
|
2
3
|
require "logstash/filters/base"
|
3
4
|
require "logstash/namespace"
|
4
5
|
|
@@ -124,7 +125,7 @@ class LogStash::Filters::KV < LogStash::Filters::Base
|
|
124
125
|
# An array specifying the parsed keys which should be added to the event.
|
125
126
|
# By default all keys will be added.
|
126
127
|
#
|
127
|
-
# For example, consider a source like `Hey, from=<abc>, to=def foo=bar`.
|
128
|
+
# For example, consider a source like `Hey, from=<abc>, to=def foo=bar`.
|
128
129
|
# To include `from` and `to`, but exclude the `foo` key, you could use this configuration:
|
129
130
|
# [source,ruby]
|
130
131
|
# filter {
|
@@ -137,7 +138,7 @@ class LogStash::Filters::KV < LogStash::Filters::Base
|
|
137
138
|
# An array specifying the parsed keys which should not be added to the event.
|
138
139
|
# By default no keys will be excluded.
|
139
140
|
#
|
140
|
-
# For example, consider a source like `Hey, from=<abc>, to=def foo=bar`.
|
141
|
+
# For example, consider a source like `Hey, from=<abc>, to=def foo=bar`.
|
141
142
|
# To exclude `from` and `to`, but retain the `foo` key, you could use this configuration:
|
142
143
|
# [source,ruby]
|
143
144
|
# filter {
|
@@ -158,10 +159,10 @@ class LogStash::Filters::KV < LogStash::Filters::Base
|
|
158
159
|
# }
|
159
160
|
config :default_keys, :validate => :hash, :default => {}
|
160
161
|
|
161
|
-
# A bool option for removing duplicate key/value pairs. When set to false, only
|
162
|
+
# A bool option for removing duplicate key/value pairs. When set to false, only
|
162
163
|
# one unique key/value pair will be preserved.
|
163
164
|
#
|
164
|
-
# For example, consider a source like `from=me from=me`. `[from]` will map to
|
165
|
+
# For example, consider a source like `from=me from=me`. `[from]` will map to
|
165
166
|
# an Array with two elements: `["me", "me"]`. to only keep unique key/value pairs,
|
166
167
|
# you could use this configuration:
|
167
168
|
# [source,ruby]
|
@@ -193,7 +194,7 @@ class LogStash::Filters::KV < LogStash::Filters::Base
|
|
193
194
|
# * bracketstwo: [hello
|
194
195
|
config :include_brackets, :validate => :boolean, :default => true
|
195
196
|
|
196
|
-
# A boolean specifying whether to drill down into values
|
197
|
+
# A boolean specifying whether to drill down into values
|
197
198
|
# and recursively get more key-value pairs from it.
|
198
199
|
# The extra key-value pairs will be stored as subkeys of the root key.
|
199
200
|
#
|
@@ -208,90 +209,86 @@ class LogStash::Filters::KV < LogStash::Filters::Base
|
|
208
209
|
config :recursive, :validate => :boolean, :default => false
|
209
210
|
|
210
211
|
def register
|
211
|
-
@trim_re = Regexp.new("[#{@trim}]") if
|
212
|
-
@trimkey_re = Regexp.new("[#{@trimkey}]") if
|
212
|
+
@trim_re = Regexp.new("[#{@trim}]") if @trim
|
213
|
+
@trimkey_re = Regexp.new("[#{@trimkey}]") if @trimkey
|
213
214
|
|
214
215
|
valueRxString = "(?:\"([^\"]+)\"|'([^']+)'"
|
215
216
|
valueRxString += "|\\(([^\\)]+)\\)|\\[([^\\]]+)\\]" if @include_brackets
|
216
|
-
valueRxString += "|((?:\\\\ |[^"
|
217
|
-
@scan_re = Regexp.new("((?:\\\\ |[^"
|
218
|
-
end # def register
|
217
|
+
valueRxString += "|((?:\\\\ |[^" + @field_split + "])+))"
|
218
|
+
@scan_re = Regexp.new("((?:\\\\ |[^" + @field_split + @value_split + "])+)\\s*[" + @value_split + "]\\s*" + valueRxString)
|
219
219
|
|
220
|
-
|
221
|
-
|
220
|
+
@value_split_re = /[#{@value_split}]/
|
221
|
+
end
|
222
222
|
|
223
|
+
def filter(event)
|
223
224
|
kv = Hash.new
|
224
|
-
|
225
225
|
value = event[@source]
|
226
226
|
|
227
227
|
case value
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
228
|
+
when nil
|
229
|
+
# Nothing to do
|
230
|
+
when String
|
231
|
+
kv = parse(value, event, kv)
|
232
|
+
when Array
|
233
|
+
value.each { |v| kv = parse(v, event, kv) }
|
234
|
+
else
|
235
|
+
@logger.warn("kv filter has no support for this type of data", :type => value.class, :value => value)
|
236
|
+
end
|
235
237
|
|
236
238
|
# Add default key-values for missing keys
|
237
239
|
kv = @default_keys.merge(kv)
|
238
240
|
|
239
|
-
|
240
|
-
if kv.length > 0
|
241
|
-
if @target.nil?
|
242
|
-
# Default is to write to the root of the event.
|
243
|
-
dest = event.to_hash
|
244
|
-
else
|
245
|
-
if !event[@target].is_a?(Hash)
|
246
|
-
@logger.debug("Overwriting existing target field", :target => @target)
|
247
|
-
dest = event[@target] = {}
|
248
|
-
else
|
249
|
-
dest = event[@target]
|
250
|
-
end
|
251
|
-
end
|
241
|
+
return if kv.empty?
|
252
242
|
|
253
|
-
|
254
|
-
|
243
|
+
if @target
|
244
|
+
@logger.debug? && @logger.debug("Overwriting existing target field", :target => @target)
|
245
|
+
event[@target] = kv
|
246
|
+
else
|
247
|
+
kv.each{|k, v| event[k] = v}
|
255
248
|
end
|
256
|
-
|
249
|
+
|
250
|
+
filter_matched(event)
|
251
|
+
end
|
257
252
|
|
258
253
|
private
|
254
|
+
|
255
|
+
def has_value_splitter?(s)
|
256
|
+
s =~ @value_split_re
|
257
|
+
end
|
258
|
+
|
259
259
|
def parse(text, event, kv_keys)
|
260
|
-
if
|
261
|
-
|
262
|
-
|
263
|
-
|
260
|
+
# short circuit parsing if the text does not contain the @value_split
|
261
|
+
return kv_keys unless has_value_splitter?(text)
|
262
|
+
|
264
263
|
# Interpret dynamic keys for @include_keys and @exclude_keys
|
265
264
|
include_keys = @include_keys.map{|key| event.sprintf(key)}
|
266
265
|
exclude_keys = @exclude_keys.map{|key| event.sprintf(key)}
|
267
|
-
|
266
|
+
|
268
267
|
text.scan(@scan_re) do |key, v1, v2, v3, v4, v5|
|
269
268
|
value = v1 || v2 || v3 || v4 || v5
|
270
|
-
key = @trimkey
|
271
|
-
|
269
|
+
key = @trimkey ? key.gsub(@trimkey_re, "") : key
|
270
|
+
|
272
271
|
# Bail out as per the values of include_keys and exclude_keys
|
273
272
|
next if not include_keys.empty? and not include_keys.include?(key)
|
273
|
+
# next unless include_keys.include?(key)
|
274
274
|
next if exclude_keys.include?(key)
|
275
275
|
|
276
276
|
key = event.sprintf(@prefix) + key
|
277
277
|
|
278
|
-
value = @trim
|
278
|
+
value = @trim ? value.gsub(@trim_re, "") : value
|
279
279
|
|
280
|
-
# Bail out if inserting duplicate value in key mapping when unique_values
|
280
|
+
# Bail out if inserting duplicate value in key mapping when unique_values
|
281
281
|
# option is set to true.
|
282
282
|
next if not @allow_duplicate_values and kv_keys.has_key?(key) and kv_keys[key].include?(value)
|
283
283
|
|
284
284
|
# recursively get more kv pairs from the value
|
285
|
-
if @recursive
|
286
|
-
innerKv =
|
287
|
-
|
288
|
-
if innerKv.length > 0
|
289
|
-
value = innerKv
|
290
|
-
end
|
285
|
+
if @recursive
|
286
|
+
innerKv = parse(value, event, {})
|
287
|
+
value = innerKv unless innerKv.empty?
|
291
288
|
end
|
292
289
|
|
293
290
|
if kv_keys.has_key?(key)
|
294
|
-
if kv_keys[key].is_a?
|
291
|
+
if kv_keys[key].is_a?(Array)
|
295
292
|
kv_keys[key].push(value)
|
296
293
|
else
|
297
294
|
kv_keys[key] = [kv_keys[key], value]
|
@@ -300,6 +297,7 @@ class LogStash::Filters::KV < LogStash::Filters::Base
|
|
300
297
|
kv_keys[key] = value
|
301
298
|
end
|
302
299
|
end
|
300
|
+
|
303
301
|
return kv_keys
|
304
302
|
end
|
305
|
-
end
|
303
|
+
end
|
data/logstash-filter-kv.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
|
3
3
|
s.name = 'logstash-filter-kv'
|
4
|
-
s.version = '2.0.
|
4
|
+
s.version = '2.0.3'
|
5
5
|
s.licenses = ['Apache License (2.0)']
|
6
6
|
s.summary = "This filter helps automatically parse messages (or specific event fields) which are of the 'foo=bar' variety."
|
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/plugin install gemname. This gem is not a stand-alone program"
|
data/spec/filters/kv_spec.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
require "logstash/devutils/rspec/spec_helper"
|
2
4
|
require "logstash/filters/kv"
|
3
5
|
|
@@ -54,21 +56,108 @@ describe LogStash::Filters::KV do
|
|
54
56
|
end
|
55
57
|
|
56
58
|
describe "test value_split" do
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
59
|
+
context "using an alternate splitter" do
|
60
|
+
config <<-CONFIG
|
61
|
+
filter {
|
62
|
+
kv { value_split => ':' }
|
63
|
+
}
|
64
|
+
CONFIG
|
62
65
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
66
|
+
sample "hello:=world foo:bar baz=:fizz doublequoted:\"hello world\" singlequoted:'hello world' brackets:(hello world)" do
|
67
|
+
insist { subject["hello"] } == "=world"
|
68
|
+
insist { subject["foo"] } == "bar"
|
69
|
+
insist { subject["baz="] } == "fizz"
|
70
|
+
insist { subject["doublequoted"] } == "hello world"
|
71
|
+
insist { subject["singlequoted"] } == "hello world"
|
72
|
+
insist { subject["brackets"] } == "hello world"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# these specs are quite implementation specific by testing on the private method
|
78
|
+
# has_value_splitter? - this is what I figured would help fixing the short circuit
|
79
|
+
# broken code that was previously in place
|
80
|
+
describe "short circuit" do
|
81
|
+
subject do
|
82
|
+
plugin = LogStash::Filters::KV.new(options)
|
83
|
+
plugin.register
|
84
|
+
plugin
|
85
|
+
end
|
86
|
+
let(:data) { {"message" => message} }
|
87
|
+
let(:event) { LogStash::Event.new(data) }
|
88
|
+
|
89
|
+
context "plain message" do
|
90
|
+
let(:options) { {} }
|
91
|
+
|
92
|
+
context "without splitter" do
|
93
|
+
let(:message) { "foo:bar" }
|
94
|
+
it "should short circuit" do
|
95
|
+
expect(subject.send(:has_value_splitter?, message)).to be_falsey
|
96
|
+
expect(subject).to receive(:has_value_splitter?).with(message).once.and_return(false)
|
97
|
+
subject.filter(event)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context "with splitter" do
|
102
|
+
let(:message) { "foo=bar" }
|
103
|
+
it "should not short circuit" do
|
104
|
+
expect(subject.send(:has_value_splitter?, message)).to be_truthy
|
105
|
+
expect(subject).to receive(:has_value_splitter?).with(message).once.and_return(true)
|
106
|
+
subject.filter(event)
|
107
|
+
end
|
108
|
+
end
|
70
109
|
end
|
71
110
|
|
111
|
+
context "recursive message" do
|
112
|
+
context "without inner splitter" do
|
113
|
+
let(:inner) { "bar" }
|
114
|
+
let(:message) { "foo=#{inner}" }
|
115
|
+
let(:options) { {"recursive" => "true"} }
|
116
|
+
|
117
|
+
it "should extract kv" do
|
118
|
+
subject.filter(event)
|
119
|
+
expect(event["foo"]).to eq(inner)
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should short circuit" do
|
123
|
+
expect(subject.send(:has_value_splitter?, message)).to be_truthy
|
124
|
+
expect(subject.send(:has_value_splitter?, inner)).to be_falsey
|
125
|
+
expect(subject).to receive(:has_value_splitter?).with(message).once.and_return(true)
|
126
|
+
expect(subject).to receive(:has_value_splitter?).with(inner).once.and_return(false)
|
127
|
+
subject.filter(event)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
context "with inner splitter" do
|
132
|
+
let(:foo_val) { "1" }
|
133
|
+
let(:baz_val) { "2" }
|
134
|
+
let(:inner) { "baz=#{baz_val}" }
|
135
|
+
let(:message) { "foo=#{foo_val} bar=(#{inner})" } # foo=1 bar=(baz=2)
|
136
|
+
let(:options) { {"recursive" => "true"} }
|
137
|
+
|
138
|
+
it "should extract kv" do
|
139
|
+
subject.filter(event)
|
140
|
+
expect(event["foo"]).to eq(foo_val)
|
141
|
+
expect(event["[bar][baz]"]).to eq(baz_val)
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should short circuit" do
|
145
|
+
expect(subject.send(:has_value_splitter?, message)).to be_truthy
|
146
|
+
expect(subject.send(:has_value_splitter?, foo_val)).to be_falsey
|
147
|
+
|
148
|
+
expect(subject.send(:has_value_splitter?, inner)).to be_truthy
|
149
|
+
expect(subject.send(:has_value_splitter?, baz_val)).to be_falsey
|
150
|
+
|
151
|
+
expect(subject).to receive(:has_value_splitter?).with(message).once.and_return(true)
|
152
|
+
expect(subject).to receive(:has_value_splitter?).with(foo_val).once.and_return(false)
|
153
|
+
|
154
|
+
expect(subject).to receive(:has_value_splitter?).with(inner).once.and_return(true)
|
155
|
+
expect(subject).to receive(:has_value_splitter?).with(baz_val).once.and_return(false)
|
156
|
+
|
157
|
+
subject.filter(event)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
72
161
|
end
|
73
162
|
|
74
163
|
describe "test field_split" do
|
@@ -104,12 +193,12 @@ describe LogStash::Filters::KV do
|
|
104
193
|
describe "test recursive" do
|
105
194
|
config <<-CONFIG
|
106
195
|
filter {
|
107
|
-
kv {
|
196
|
+
kv {
|
108
197
|
recursive => 'true'
|
109
198
|
}
|
110
199
|
}
|
111
200
|
CONFIG
|
112
|
-
|
201
|
+
|
113
202
|
sample 'IKE="Quick Mode completion" IKE\ IDs = (subnet= x.x.x.x mask= 255.255.255.254 and host=y.y.y.y)' do
|
114
203
|
insist { subject["IKE"] } == 'Quick Mode completion'
|
115
204
|
insist { subject['IKE\ IDs']['subnet'] } == 'x.x.x.x'
|
@@ -393,7 +482,7 @@ describe LogStash::Filters::KV do
|
|
393
482
|
insist { subject["__doublequoted"] } == "hello world"
|
394
483
|
end
|
395
484
|
end
|
396
|
-
|
485
|
+
|
397
486
|
describe "test include_keys with dynamic key" do
|
398
487
|
config <<-CONFIG
|
399
488
|
filter {
|
@@ -403,13 +492,13 @@ describe LogStash::Filters::KV do
|
|
403
492
|
}
|
404
493
|
}
|
405
494
|
CONFIG
|
406
|
-
|
495
|
+
|
407
496
|
sample({"data" => "foo=bar baz=fizz", "key" => "foo"}) do
|
408
497
|
insist { subject["foo"] } == "bar"
|
409
498
|
insist { subject["baz"] } == nil
|
410
499
|
end
|
411
500
|
end
|
412
|
-
|
501
|
+
|
413
502
|
describe "test exclude_keys with dynamic key" do
|
414
503
|
config <<-CONFIG
|
415
504
|
filter {
|
@@ -419,7 +508,7 @@ describe LogStash::Filters::KV do
|
|
419
508
|
}
|
420
509
|
}
|
421
510
|
CONFIG
|
422
|
-
|
511
|
+
|
423
512
|
sample({"data" => "foo=bar baz=fizz", "key" => "foo"}) do
|
424
513
|
insist { subject["foo"] } == nil
|
425
514
|
insist { subject["baz"] } == "fizz"
|
metadata
CHANGED
@@ -1,17 +1,18 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: logstash-filter-kv
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Elastic
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-12-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
|
14
|
+
name: logstash-core
|
15
|
+
version_requirements: !ruby/object:Gem::Requirement
|
15
16
|
requirements:
|
16
17
|
- - '>='
|
17
18
|
- !ruby/object:Gem::Version
|
@@ -19,10 +20,7 @@ dependencies:
|
|
19
20
|
- - <
|
20
21
|
- !ruby/object:Gem::Version
|
21
22
|
version: 3.0.0
|
22
|
-
|
23
|
-
prerelease: false
|
24
|
-
type: :runtime
|
25
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirement: !ruby/object:Gem::Requirement
|
26
24
|
requirements:
|
27
25
|
- - '>='
|
28
26
|
- !ruby/object:Gem::Version
|
@@ -30,20 +28,22 @@ dependencies:
|
|
30
28
|
- - <
|
31
29
|
- !ruby/object:Gem::Version
|
32
30
|
version: 3.0.0
|
31
|
+
prerelease: false
|
32
|
+
type: :runtime
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
|
-
|
34
|
+
name: logstash-devutils
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
35
36
|
requirements:
|
36
37
|
- - '>='
|
37
38
|
- !ruby/object:Gem::Version
|
38
39
|
version: '0'
|
39
|
-
|
40
|
-
prerelease: false
|
41
|
-
type: :development
|
42
|
-
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirement: !ruby/object:Gem::Requirement
|
43
41
|
requirements:
|
44
42
|
- - '>='
|
45
43
|
- !ruby/object:Gem::Version
|
46
44
|
version: '0'
|
45
|
+
prerelease: false
|
46
|
+
type: :development
|
47
47
|
description: This gem is a logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/plugin install gemname. This gem is not a stand-alone program
|
48
48
|
email: info@elastic.co
|
49
49
|
executables: []
|