logstash-filter-translate 3.1.0 → 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 +4 -4
- data/CHANGELOG.md +8 -0
- data/docs/index.asciidoc +173 -33
- data/lib/logstash/filters/array_of_maps_value_update.rb +44 -0
- data/lib/logstash/filters/array_of_values_update.rb +37 -0
- data/lib/logstash/filters/dictionary/csv_file.rb +25 -0
- data/lib/logstash/filters/dictionary/file.rb +140 -0
- data/lib/logstash/filters/dictionary/json_file.rb +87 -0
- data/lib/logstash/filters/dictionary/memory.rb +31 -0
- data/lib/logstash/filters/dictionary/yaml_file.rb +24 -0
- data/lib/logstash/filters/dictionary/yaml_visitor.rb +42 -0
- data/lib/logstash/filters/fetch_strategy/file.rb +81 -0
- data/lib/logstash/filters/fetch_strategy/memory.rb +52 -0
- data/lib/logstash/filters/single_value_update.rb +33 -0
- data/lib/logstash/filters/translate.rb +54 -155
- data/logstash-filter-translate.gemspec +5 -1
- data/spec/filters/benchmark_rspec.rb +69 -0
- data/spec/filters/scheduling_spec.rb +200 -0
- data/spec/filters/translate_spec.rb +238 -45
- data/spec/filters/yaml_visitor_spec.rb +16 -0
- data/spec/fixtures/regex_dict.csv +4 -0
- data/spec/fixtures/regex_union_dict.csv +4 -0
- data/spec/fixtures/tag-map-dict.yml +21 -0
- data/spec/fixtures/tag-omap-dict.yml +21 -0
- data/spec/support/build_huge_dictionaries.rb +33 -0
- data/spec/support/rspec_wait_handler_helper.rb +38 -0
- metadata +87 -2
@@ -17,8 +17,8 @@ describe LogStash::Filters::Translate do
|
|
17
17
|
"300", "Redirect",
|
18
18
|
"400", "Client Error",
|
19
19
|
"500", "Server Error" ],
|
20
|
-
|
21
|
-
|
20
|
+
"exact" => true,
|
21
|
+
"regex" => false
|
22
22
|
}
|
23
23
|
end
|
24
24
|
|
@@ -33,51 +33,96 @@ describe LogStash::Filters::Translate do
|
|
33
33
|
|
34
34
|
|
35
35
|
describe "multi translation" do
|
36
|
+
context "when using an inline dictionary" do
|
37
|
+
let(:config) do
|
38
|
+
{
|
39
|
+
"field" => "status",
|
40
|
+
"destination" => "translation",
|
41
|
+
"dictionary" => [ "200", "OK",
|
42
|
+
"300", "Redirect",
|
43
|
+
"400", "Client Error",
|
44
|
+
"500", "Server Error" ],
|
45
|
+
"exact" => false,
|
46
|
+
"regex" => false
|
47
|
+
}
|
48
|
+
end
|
36
49
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
"500", "Server Error" ],
|
45
|
-
"exact" => false,
|
46
|
-
"regex" => false
|
47
|
-
}
|
50
|
+
let(:event) { LogStash::Event.new("status" => "200 & 500") }
|
51
|
+
|
52
|
+
it "return the exact translation" do
|
53
|
+
subject.register
|
54
|
+
subject.filter(event)
|
55
|
+
expect(event.get("translation")).to eq("OK & Server Error")
|
56
|
+
end
|
48
57
|
end
|
49
58
|
|
50
|
-
|
59
|
+
context "when using a file based dictionary" do
|
60
|
+
let(:dictionary_path) { File.join(File.dirname(__FILE__), "..", "fixtures", "regex_union_dict.csv") }
|
61
|
+
let(:config) do
|
62
|
+
{
|
63
|
+
"field" => "status",
|
64
|
+
"destination" => "translation",
|
65
|
+
"dictionary_path" => dictionary_path,
|
66
|
+
"refresh_interval" => 0,
|
67
|
+
"exact" => false,
|
68
|
+
"regex" => false
|
69
|
+
}
|
70
|
+
end
|
51
71
|
|
52
|
-
|
53
|
-
subject.register
|
54
|
-
subject.filter(event)
|
55
|
-
expect(event.get("translation")).to eq("OK & Server Error")
|
56
|
-
end
|
72
|
+
let(:event) { LogStash::Event.new("status" => "200 & 500") }
|
57
73
|
|
74
|
+
it "return the exact regex translation" do
|
75
|
+
subject.register
|
76
|
+
subject.filter(event)
|
77
|
+
expect(event.get("translation")).to eq("OK & Server Error")
|
78
|
+
end
|
79
|
+
end
|
58
80
|
end
|
59
81
|
|
60
82
|
describe "regex translation" do
|
83
|
+
context "when using an inline dictionary" do
|
84
|
+
let(:config) do
|
85
|
+
{
|
86
|
+
"field" => "status",
|
87
|
+
"destination" => "translation",
|
88
|
+
"dictionary" => [ "^2[0-9][0-9]$", "OK",
|
89
|
+
"^3[0-9][0-9]$", "Redirect",
|
90
|
+
"^4[0-9][0-9]$", "Client Error",
|
91
|
+
"^5[0-9][0-9]$", "Server Error" ],
|
92
|
+
"exact" => true,
|
93
|
+
"regex" => true
|
94
|
+
}
|
95
|
+
end
|
61
96
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
"^5[0-9][0-9]$", "Server Error" ],
|
70
|
-
"exact" => true,
|
71
|
-
"regex" => true
|
72
|
-
}
|
97
|
+
let(:event) { LogStash::Event.new("status" => "200") }
|
98
|
+
|
99
|
+
it "return the exact regex translation" do
|
100
|
+
subject.register
|
101
|
+
subject.filter(event)
|
102
|
+
expect(event.get("translation")).to eq("OK")
|
103
|
+
end
|
73
104
|
end
|
74
105
|
|
75
|
-
|
106
|
+
context "when using a file based dictionary" do
|
107
|
+
let(:dictionary_path) { File.join(File.dirname(__FILE__), "..", "fixtures", "regex_dict.csv") }
|
108
|
+
let(:config) do
|
109
|
+
{
|
110
|
+
"field" => "status",
|
111
|
+
"destination" => "translation",
|
112
|
+
"dictionary_path" => dictionary_path,
|
113
|
+
"refresh_interval" => 0,
|
114
|
+
"exact" => true,
|
115
|
+
"regex" => true
|
116
|
+
}
|
117
|
+
end
|
76
118
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
119
|
+
let(:event) { LogStash::Event.new("status" => "200") }
|
120
|
+
|
121
|
+
it "return the exact regex translation" do
|
122
|
+
subject.register
|
123
|
+
subject.filter(event)
|
124
|
+
expect(event.get("translation")).to eq("OK")
|
125
|
+
end
|
81
126
|
end
|
82
127
|
end
|
83
128
|
|
@@ -129,14 +174,15 @@ describe LogStash::Filters::Translate do
|
|
129
174
|
"field" => "status",
|
130
175
|
"destination" => "translation",
|
131
176
|
"dictionary_path" => dictionary_path,
|
177
|
+
"refresh_interval" => -1,
|
132
178
|
"exact" => true,
|
133
179
|
"regex" => false
|
134
180
|
}
|
135
181
|
end
|
136
182
|
|
137
183
|
it "raises exception when loading" do
|
138
|
-
error =
|
139
|
-
expect { subject.register }.to raise_error(
|
184
|
+
error = /mapping values are not allowed here at line 1 column 45 when loading dictionary file/
|
185
|
+
expect { subject.register }.to raise_error(error)
|
140
186
|
end
|
141
187
|
|
142
188
|
context "when using a yml file" do
|
@@ -150,6 +196,28 @@ describe LogStash::Filters::Translate do
|
|
150
196
|
end
|
151
197
|
end
|
152
198
|
|
199
|
+
context "when using a map tagged yml file" do
|
200
|
+
let(:dictionary_path) { File.join(File.dirname(__FILE__), "..", "fixtures", "tag-map-dict.yml") }
|
201
|
+
let(:event) { LogStash::Event.new("status" => "six") }
|
202
|
+
|
203
|
+
it "return the exact translation" do
|
204
|
+
subject.register
|
205
|
+
subject.filter(event)
|
206
|
+
expect(event.get("translation")).to eq("val-6-1|val-6-2")
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
context "when using a omap tagged yml file" do
|
211
|
+
let(:dictionary_path) { File.join(File.dirname(__FILE__), "..", "fixtures", "tag-omap-dict.yml") }
|
212
|
+
let(:event) { LogStash::Event.new("status" => "nine") }
|
213
|
+
|
214
|
+
it "return the exact translation" do
|
215
|
+
subject.register
|
216
|
+
subject.filter(event)
|
217
|
+
expect(event.get("translation")).to eq("val-9-1|val-9-2")
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
153
221
|
context "when using a json file" do
|
154
222
|
let(:dictionary_path) { File.join(File.dirname(__FILE__), "..", "fixtures", "dict.json") }
|
155
223
|
let(:event) { LogStash::Event.new("status" => "b") }
|
@@ -172,15 +240,84 @@ describe LogStash::Filters::Translate do
|
|
172
240
|
end
|
173
241
|
end
|
174
242
|
|
175
|
-
context "when using an
|
243
|
+
context "when using an unknown file" do
|
176
244
|
let(:dictionary_path) { File.join(File.dirname(__FILE__), "..", "fixtures", "dict.other") }
|
177
245
|
|
178
|
-
it "
|
179
|
-
expect { subject.register }.to raise_error(RuntimeError, /Dictionary #{dictionary_path}
|
246
|
+
it "raises error" do
|
247
|
+
expect { subject.register }.to raise_error(RuntimeError, /Dictionary #{dictionary_path} has a non valid format/)
|
180
248
|
end
|
181
249
|
end
|
182
250
|
end
|
183
251
|
|
252
|
+
describe "iterate_on functionality" do
|
253
|
+
describe "when iterate_on is the same as field, AKA array of values" do
|
254
|
+
let(:dictionary_path) { File.join(File.dirname(__FILE__), "..", "fixtures", "tag-map-dict.yml") }
|
255
|
+
let(:config) do
|
256
|
+
{
|
257
|
+
"iterate_on" => "foo",
|
258
|
+
"field" => "foo",
|
259
|
+
"destination" => "baz",
|
260
|
+
"fallback" => "nooo",
|
261
|
+
"dictionary_path" => dictionary_path,
|
262
|
+
# "override" => true,
|
263
|
+
"refresh_interval" => 0
|
264
|
+
}
|
265
|
+
end
|
266
|
+
|
267
|
+
let(:event) { LogStash::Event.new("foo" => ["nine","eight", "seven"]) }
|
268
|
+
it "adds a translation to destination array for each value in field array" do
|
269
|
+
subject.register
|
270
|
+
subject.filter(event)
|
271
|
+
expect(event.get("baz")).to eq(["val-9-1|val-9-2", "val-8-1|val-8-2", "val-7-1|val-7-2"])
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
describe "when iterate_on is not the same as field, AKA array of objects" do
|
276
|
+
let(:dictionary_path) { File.join(File.dirname(__FILE__), "..", "fixtures", "tag-map-dict.yml") }
|
277
|
+
let(:config) do
|
278
|
+
{
|
279
|
+
"iterate_on" => "foo",
|
280
|
+
"field" => "bar",
|
281
|
+
"destination" => "baz",
|
282
|
+
"fallback" => "nooo",
|
283
|
+
"dictionary_path" => dictionary_path,
|
284
|
+
# "override" => true,
|
285
|
+
"refresh_interval" => 0
|
286
|
+
}
|
287
|
+
end
|
288
|
+
|
289
|
+
let(:event) { LogStash::Event.new("foo" => [{"bar"=>"two"},{"bar"=>"one"}, {"bar"=>"six"}]) }
|
290
|
+
it "adds a translation to each map" do
|
291
|
+
subject.register
|
292
|
+
subject.filter(event)
|
293
|
+
expect(event.get("[foo][0][baz]")).to eq("val-2-1|val-2-2")
|
294
|
+
expect(event.get("[foo][1][baz]")).to eq("val-1-1|val-1-2")
|
295
|
+
expect(event.get("[foo][2][baz]")).to eq("val-6-1|val-6-2")
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
describe "field and destination are the same (needs override)" do
|
301
|
+
let(:dictionary_path) { File.join(File.dirname(__FILE__), "..", "fixtures", "tag-map-dict.yml") }
|
302
|
+
let(:config) do
|
303
|
+
{
|
304
|
+
"field" => "foo",
|
305
|
+
"destination" => "foo",
|
306
|
+
"dictionary_path" => dictionary_path,
|
307
|
+
"override" => true,
|
308
|
+
"refresh_interval" => -1
|
309
|
+
}
|
310
|
+
end
|
311
|
+
|
312
|
+
let(:event) { LogStash::Event.new("foo" => "nine") }
|
313
|
+
|
314
|
+
it "overwrites existing value" do
|
315
|
+
subject.register
|
316
|
+
subject.filter(event)
|
317
|
+
expect(event.get("foo")).to eq("val-9-1|val-9-2")
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
184
321
|
describe "general configuration" do
|
185
322
|
let(:dictionary_path) { File.join(File.dirname(__FILE__), "..", "fixtures", "dict.yml") }
|
186
323
|
let(:config) do
|
@@ -206,7 +343,7 @@ describe LogStash::Filters::Translate do
|
|
206
343
|
"field" => "status",
|
207
344
|
"destination" => "translation",
|
208
345
|
"dictionary_path" => dictionary_path,
|
209
|
-
"refresh_interval" =>
|
346
|
+
"refresh_interval" => -1, # we're controlling this manually
|
210
347
|
"exact" => true,
|
211
348
|
"regex" => false,
|
212
349
|
"fallback" => "no match",
|
@@ -229,7 +366,7 @@ describe LogStash::Filters::Translate do
|
|
229
366
|
it "overwrites existing entries" do
|
230
367
|
subject.filter(before_mod)
|
231
368
|
IO.write(dictionary_path, modified_content)
|
232
|
-
subject.
|
369
|
+
subject.lookup.load_dictionary
|
233
370
|
subject.filter(after_mod)
|
234
371
|
expect(before_mod.get("translation")).to eq(2)
|
235
372
|
expect(after_mod.get("translation")).to eq(4)
|
@@ -237,7 +374,7 @@ describe LogStash::Filters::Translate do
|
|
237
374
|
it "keeps leftover entries" do
|
238
375
|
subject.filter(before_del)
|
239
376
|
IO.write(dictionary_path, modified_content)
|
240
|
-
subject.
|
377
|
+
subject.lookup.load_dictionary
|
241
378
|
subject.filter(after_del)
|
242
379
|
expect(before_del.get("translation")).to eq(3)
|
243
380
|
expect(after_del.get("translation")).to eq(3)
|
@@ -249,7 +386,7 @@ describe LogStash::Filters::Translate do
|
|
249
386
|
it "overwrites existing entries" do
|
250
387
|
subject.filter(before_mod)
|
251
388
|
IO.write(dictionary_path, modified_content)
|
252
|
-
subject.
|
389
|
+
subject.lookup.load_dictionary
|
253
390
|
subject.filter(after_mod)
|
254
391
|
expect(before_mod.get("translation")).to eq(2)
|
255
392
|
expect(after_mod.get("translation")).to eq(4)
|
@@ -257,11 +394,67 @@ describe LogStash::Filters::Translate do
|
|
257
394
|
it "removes leftover entries" do
|
258
395
|
subject.filter(before_del)
|
259
396
|
IO.write(dictionary_path, modified_content)
|
260
|
-
subject.
|
397
|
+
subject.lookup.load_dictionary
|
261
398
|
subject.filter(after_del)
|
262
399
|
expect(before_del.get("translation")).to eq(3)
|
263
400
|
expect(after_del.get("translation")).to eq("no match")
|
264
401
|
end
|
265
402
|
end
|
266
403
|
end
|
404
|
+
|
405
|
+
describe "loading an empty dictionary" do
|
406
|
+
let(:directory) { Pathname.new(Stud::Temporary.directory) }
|
407
|
+
|
408
|
+
let(:config) do
|
409
|
+
{
|
410
|
+
"field" => "status",
|
411
|
+
"destination" => "translation",
|
412
|
+
"dictionary_path" => dictionary_path.to_path,
|
413
|
+
"refresh_interval" => -1,
|
414
|
+
"fallback" => "no match",
|
415
|
+
"exact" => true,
|
416
|
+
"regex" => false
|
417
|
+
}
|
418
|
+
end
|
419
|
+
|
420
|
+
before do
|
421
|
+
dictionary_path.open("wb") do |file|
|
422
|
+
file.write("")
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
context "when using a yml file" do
|
427
|
+
let(:dictionary_path) { directory.join("dict-e.yml") }
|
428
|
+
let(:event) { LogStash::Event.new("status" => "a") }
|
429
|
+
|
430
|
+
it "return the exact translation" do
|
431
|
+
|
432
|
+
subject.register
|
433
|
+
subject.filter(event)
|
434
|
+
expect(event.get("translation")).to eq("no match")
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
context "when using a json file" do
|
439
|
+
let(:dictionary_path) { directory.join("dict-e.json") }
|
440
|
+
let(:event) { LogStash::Event.new("status" => "b") }
|
441
|
+
|
442
|
+
it "return the exact translation" do
|
443
|
+
subject.register
|
444
|
+
subject.filter(event)
|
445
|
+
expect(event.get("translation")).to eq("no match")
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
context "when using a csv file" do
|
450
|
+
let(:dictionary_path) { directory.join("dict-e.csv") }
|
451
|
+
let(:event) { LogStash::Event.new("status" => "c") }
|
452
|
+
|
453
|
+
it "return the exact translation" do
|
454
|
+
subject.register
|
455
|
+
subject.filter(event)
|
456
|
+
expect(event.get("translation")).to eq("no match")
|
457
|
+
end
|
458
|
+
end
|
459
|
+
end
|
267
460
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "logstash/devutils/rspec/spec_helper"
|
2
|
+
|
3
|
+
require "logstash/filters/dictionary/yaml_visitor"
|
4
|
+
|
5
|
+
describe LogStash::Filters::Dictionary::YamlVisitor do
|
6
|
+
it 'works' do
|
7
|
+
yaml_string = "---\na: \n x: 3\nb: \n x: 4\n"
|
8
|
+
dictionary = {"c" => {"x" => 5}}
|
9
|
+
described_class.create.accept_with_dictionary(dictionary, Psych.parse_stream(yaml_string)).first
|
10
|
+
expect(dictionary.keys.sort).to eq(["a", "b", "c"])
|
11
|
+
values = dictionary.values
|
12
|
+
expect(values[0]).to eq({"x" => 5})
|
13
|
+
expect(values[1]).to eq({"x" => 3})
|
14
|
+
expect(values[2]).to eq({"x" => 4})
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
---
|
2
|
+
!!map {
|
3
|
+
? !!str "eight"
|
4
|
+
: !!str "val-8-1|val-8-2",
|
5
|
+
? !!str "five"
|
6
|
+
: !!str "val-5-1|val-5-2",
|
7
|
+
? !!str "four"
|
8
|
+
: !!str "val-4-1|val-4-2",
|
9
|
+
? !!str "nine"
|
10
|
+
: !!str "val-9-1|val-9-2",
|
11
|
+
? !!str "one"
|
12
|
+
: !!str "val-1-1|val-1-2",
|
13
|
+
? !!str "seven"
|
14
|
+
: !!str "val-7-1|val-7-2",
|
15
|
+
? !!str "six"
|
16
|
+
: !!str "val-6-1|val-6-2",
|
17
|
+
? !!str "three"
|
18
|
+
: !!str "val-3-1|val-3-2",
|
19
|
+
? !!str "two"
|
20
|
+
: !!str "val-2-1|val-2-2",
|
21
|
+
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
---
|
2
|
+
!!omap {
|
3
|
+
? !!str "eight"
|
4
|
+
: !!str "val-8-1|val-8-2",
|
5
|
+
? !!str "five"
|
6
|
+
: !!str "val-5-1|val-5-2",
|
7
|
+
? !!str "four"
|
8
|
+
: !!str "val-4-1|val-4-2",
|
9
|
+
? !!str "nine"
|
10
|
+
: !!str "val-9-1|val-9-2",
|
11
|
+
? !!str "one"
|
12
|
+
: !!str "val-1-1|val-1-2",
|
13
|
+
? !!str "seven"
|
14
|
+
: !!str "val-7-1|val-7-2",
|
15
|
+
? !!str "six"
|
16
|
+
: !!str "val-6-1|val-6-2",
|
17
|
+
? !!str "three"
|
18
|
+
: !!str "val-3-1|val-3-2",
|
19
|
+
? !!str "two"
|
20
|
+
: !!str "val-2-1|val-2-2",
|
21
|
+
}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "securerandom"
|
4
|
+
|
5
|
+
module LogStash module Filters module Dictionary
|
6
|
+
def self.create_huge_csv_dictionary(directory, name, size)
|
7
|
+
tmppath = directory.join("temp_big.csv")
|
8
|
+
tmppath.open("w") do |file|
|
9
|
+
file.puts("foo,#{SecureRandom.hex(4)}")
|
10
|
+
file.puts("bar,#{SecureRandom.hex(4)}")
|
11
|
+
size.times do |i|
|
12
|
+
file.puts("#{SecureRandom.hex(12)},#{1000000 + i}")
|
13
|
+
end
|
14
|
+
file.puts("baz,quux")
|
15
|
+
end
|
16
|
+
tmppath.rename(directory.join(name))
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.create_huge_json_dictionary(directory, name, size)
|
20
|
+
tmppath = directory.join("temp_big.json")
|
21
|
+
tmppath.open("w") do |file|
|
22
|
+
file.puts("{")
|
23
|
+
file.puts(' "foo":"'.concat(SecureRandom.hex(4)).concat('",'))
|
24
|
+
file.puts(' "bar":"'.concat(SecureRandom.hex(4)).concat('",'))
|
25
|
+
size.times do |i|
|
26
|
+
file.puts(' "'.concat(SecureRandom.hex(12)).concat('":"').concat("#{1000000 + i}").concat('",'))
|
27
|
+
end
|
28
|
+
file.puts(' "baz":"quux"')
|
29
|
+
file.puts("}")
|
30
|
+
end
|
31
|
+
tmppath.rename(directory.join(name))
|
32
|
+
end
|
33
|
+
end end end
|