fluent-plugin-record-reformer 0.7.2 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -1
- data/CHANGELOG.md +7 -0
- data/Gemfile.fluentd.0.10 +4 -0
- data/README.md +3 -7
- data/fluent-plugin-record-reformer.gemspec +1 -1
- data/lib/fluent/plugin/out_record_reformer.rb +128 -53
- data/test/helper.rb +5 -4
- data/test/test_out_record_reformer.rb +57 -2
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bf0c0a50d2b8509f3fa6917e0f700abe30e5249a
|
4
|
+
data.tar.gz: fba94b0ab7c6259354c70be688ef5ad81cc29efe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 132876d93292dd82888bbf7a34010dbc3c9e99ea746952165c919a71be3ad8118b3572009f5a8c5deb18654b714142a9b100b39c07ff2e3e40ae9d1e1c014ca9
|
7
|
+
data.tar.gz: 98e35238beb1848b8076ab6c66095a9cb15eb91301bd21f8eed18ad4f70e2eae2b932ad6c525c516f57794d0fee651a162d25a2b519c9fb19bceda90947aaad7
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -126,14 +126,10 @@ This results in same, but please note that following option parameters are reser
|
|
126
126
|
|
127
127
|
## Placeholders
|
128
128
|
|
129
|
-
|
130
|
-
|
131
|
-
* ${remove_me}
|
132
|
-
* ${not_remove_me}
|
133
|
-
* ${message}
|
134
|
-
|
135
|
-
shall be available. In addition, following placeholders are reserved:
|
129
|
+
Following placeholders are available:
|
136
130
|
|
131
|
+
* ${record["key"]} Record value of `key` such as `${record["message"]}` in the above example (available from v0.8.0).
|
132
|
+
* Originally, record placeholders were available as `${key}` such as `${message}`. This is still kept for the backward compatibility, but would be removed in the future.
|
137
133
|
* ${hostname} Hostname of the running machine
|
138
134
|
* ${tag} Input tag
|
139
135
|
* ${time} Time of the event
|
@@ -3,7 +3,7 @@ $:.push File.expand_path('../lib', __FILE__)
|
|
3
3
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
5
|
gem.name = "fluent-plugin-record-reformer"
|
6
|
-
gem.version = "0.
|
6
|
+
gem.version = "0.8.0"
|
7
7
|
gem.authors = ["Naotoshi Seo"]
|
8
8
|
gem.email = "sonots@gmail.com"
|
9
9
|
gem.homepage = "https://github.com/sonots/fluent-plugin-record-reformer"
|
@@ -10,21 +10,21 @@ module Fluent
|
|
10
10
|
end
|
11
11
|
|
12
12
|
config_param :output_tag, :string, :default => nil, # obsolete
|
13
|
-
|
13
|
+
:desc => 'The output tag name. This option is deprecated. Use `tag` option instead.'
|
14
14
|
config_param :tag, :string, :default => nil,
|
15
|
-
|
15
|
+
:desc => 'The output tag name.'
|
16
16
|
config_param :remove_keys, :string, :default => nil,
|
17
|
-
|
17
|
+
:desc => 'Specify record keys to be removed by a string separated by , (comma).'
|
18
18
|
config_param :keep_keys, :string, :default => nil,
|
19
|
-
|
19
|
+
:desc => 'Specify record keys to be kept by a string separated by , (comma).'
|
20
20
|
config_param :renew_record, :bool, :default => false,
|
21
|
-
|
21
|
+
:desc => 'Creates an output record newly without extending (merging) the input record fields.'
|
22
22
|
config_param :renew_time_key, :string, :default => nil,
|
23
|
-
|
23
|
+
:desc => 'Overwrites the time of events with a value of the record field.'
|
24
24
|
config_param :enable_ruby, :bool, :default => true, # true for lower version compatibility
|
25
|
-
|
25
|
+
:desc => 'Enable to use ruby codes in placeholders.'
|
26
26
|
config_param :auto_typecast, :bool, :default => false, # false for lower version compatibility
|
27
|
-
|
27
|
+
:desc => 'Automatically cast the field types.'
|
28
28
|
|
29
29
|
BUILTIN_CONFIGURATIONS = %W(@id @type @label type tag output_tag remove_keys renew_record keep_keys enable_ruby renew_time_key auto_typecast)
|
30
30
|
|
@@ -41,17 +41,17 @@ module Fluent
|
|
41
41
|
def configure(conf)
|
42
42
|
super
|
43
43
|
|
44
|
-
|
44
|
+
map = {}
|
45
45
|
conf.each_pair { |k, v|
|
46
46
|
next if BUILTIN_CONFIGURATIONS.include?(k)
|
47
47
|
conf.has_key?(k) # to suppress unread configuration warning
|
48
|
-
|
48
|
+
map[k] = parse_value(v)
|
49
49
|
}
|
50
50
|
# <record></record> directive
|
51
51
|
conf.elements.select { |element| element.name == 'record' }.each { |element|
|
52
52
|
element.each_pair { |k, v|
|
53
53
|
element.has_key?(k) # to suppress unread configuration warning
|
54
|
-
|
54
|
+
map[k] = parse_value(v)
|
55
55
|
}
|
56
56
|
}
|
57
57
|
|
@@ -86,6 +86,8 @@ module Fluent
|
|
86
86
|
else
|
87
87
|
PlaceholderExpander.new(placeholder_expander_params)
|
88
88
|
end
|
89
|
+
@map = @placeholder_expander.preprocess_map(map)
|
90
|
+
@tag = @placeholder_expander.preprocess_map(@tag)
|
89
91
|
|
90
92
|
@hostname = Socket.gethostname
|
91
93
|
end
|
@@ -94,18 +96,22 @@ module Fluent
|
|
94
96
|
tag_parts = tag.split('.')
|
95
97
|
tag_prefix = tag_prefix(tag_parts)
|
96
98
|
tag_suffix = tag_suffix(tag_parts)
|
97
|
-
|
98
|
-
'tag'
|
99
|
-
'tags'
|
100
|
-
'tag_parts'
|
99
|
+
placeholder_values = {
|
100
|
+
'tag' => tag,
|
101
|
+
'tags' => tag_parts, # for old version compatibility
|
102
|
+
'tag_parts' => tag_parts,
|
101
103
|
'tag_prefix' => tag_prefix,
|
102
104
|
'tag_suffix' => tag_suffix,
|
103
|
-
'hostname'
|
105
|
+
'hostname' => @hostname,
|
104
106
|
}
|
105
107
|
last_record = nil
|
106
108
|
es.each {|time, record|
|
107
109
|
last_record = record # for debug log
|
108
|
-
|
110
|
+
placeholder_values.merge!({
|
111
|
+
'time' => @placeholder_expander.time_value(time),
|
112
|
+
'record' => record,
|
113
|
+
})
|
114
|
+
new_tag, new_record = reform(@tag, record, placeholder_values)
|
109
115
|
if new_tag
|
110
116
|
if @renew_time_key && new_record.has_key?(@renew_time_key)
|
111
117
|
time = new_record[@renew_time_key].to_i
|
@@ -116,7 +122,7 @@ module Fluent
|
|
116
122
|
chain.next
|
117
123
|
rescue => e
|
118
124
|
log.warn "record_reformer: #{e.class} #{e.message} #{e.backtrace.first}"
|
119
|
-
log.debug "record_reformer: tag:#{@tag} map:#{@map} record:#{last_record}
|
125
|
+
log.debug "record_reformer: tag:#{@tag} map:#{@map} record:#{last_record} placeholder_values:#{placeholder_values}"
|
120
126
|
end
|
121
127
|
|
122
128
|
private
|
@@ -132,9 +138,10 @@ module Fluent
|
|
132
138
|
value_str # emit as string
|
133
139
|
end
|
134
140
|
|
135
|
-
def reform(tag,
|
136
|
-
@placeholder_expander.prepare_placeholders(
|
137
|
-
|
141
|
+
def reform(tag, record, placeholder_values)
|
142
|
+
@placeholder_expander.prepare_placeholders(placeholder_values)
|
143
|
+
|
144
|
+
new_tag = expand_placeholders(tag)
|
138
145
|
|
139
146
|
new_record = @renew_record ? {} : record.dup
|
140
147
|
@keep_keys.each {|k| new_record[k] = record[k]} if @keep_keys and @renew_record
|
@@ -190,17 +197,29 @@ module Fluent
|
|
190
197
|
@auto_typecast = params[:auto_typecast]
|
191
198
|
end
|
192
199
|
|
193
|
-
def
|
194
|
-
|
195
|
-
|
200
|
+
def time_value(time)
|
201
|
+
Time.at(time).to_s
|
202
|
+
end
|
203
|
+
|
204
|
+
def preprocess_map(value, force_stringify = false)
|
205
|
+
value
|
206
|
+
end
|
196
207
|
|
197
|
-
|
208
|
+
def prepare_placeholders(placeholder_values)
|
209
|
+
placeholders = {}
|
210
|
+
|
211
|
+
placeholder_values.each do |key, value|
|
198
212
|
if value.kind_of?(Array) # tag_parts, etc
|
199
213
|
size = value.size
|
200
|
-
value.each_with_index
|
214
|
+
value.each_with_index do |v, idx|
|
201
215
|
placeholders.store("${#{key}[#{idx}]}", v)
|
202
216
|
placeholders.store("${#{key}[#{idx-size}]}", v) # support [-1]
|
203
|
-
|
217
|
+
end
|
218
|
+
elsif value.kind_of?(Hash) # record, etc
|
219
|
+
value.each do |k, v|
|
220
|
+
placeholders.store("${#{k}}", v) # foo
|
221
|
+
placeholders.store(%Q[${#{key}["#{k}"]}], v) # record["foo"]
|
222
|
+
end
|
204
223
|
else # string, interger, float, and others?
|
205
224
|
placeholders.store("${#{key}}", value)
|
206
225
|
end
|
@@ -209,22 +228,27 @@ module Fluent
|
|
209
228
|
@placeholders = placeholders
|
210
229
|
end
|
211
230
|
|
212
|
-
|
231
|
+
# Expand string with placeholders
|
232
|
+
#
|
233
|
+
# @param [String] str
|
234
|
+
# @param [Boolean] force_stringify the value must be string, used for hash key
|
235
|
+
def expand(str, force_stringify = false)
|
213
236
|
if @auto_typecast and !force_stringify
|
214
237
|
single_placeholder_matched = str.match(/\A(\${[^}]+}|__[A-Z_]+__)\z/)
|
215
238
|
if single_placeholder_matched
|
216
|
-
|
239
|
+
log_if_unknown_placeholder($1)
|
217
240
|
return @placeholders[single_placeholder_matched[1]]
|
218
241
|
end
|
219
242
|
end
|
220
243
|
str.gsub(/(\${[^}]+}|__[A-Z_]+__)/) {
|
221
|
-
|
244
|
+
log_if_unknown_placeholder($1)
|
222
245
|
@placeholders[$1]
|
223
246
|
}
|
224
247
|
end
|
225
248
|
|
226
249
|
private
|
227
|
-
|
250
|
+
|
251
|
+
def log_if_unknown_placeholder(placeholder)
|
228
252
|
unless @placeholders.include?(placeholder)
|
229
253
|
log.warn "record_reformer: unknown placeholder `#{placeholder}` found"
|
230
254
|
end
|
@@ -232,45 +256,96 @@ module Fluent
|
|
232
256
|
end
|
233
257
|
|
234
258
|
class RubyPlaceholderExpander
|
235
|
-
attr_reader :
|
259
|
+
attr_reader :log
|
236
260
|
|
237
261
|
def initialize(params)
|
238
262
|
@log = params[:log]
|
239
263
|
@auto_typecast = params[:auto_typecast]
|
264
|
+
@cleanroom_expander = CleanroomExpander.new
|
240
265
|
end
|
241
266
|
|
242
|
-
|
243
|
-
|
244
|
-
# @param [Time] time the time
|
245
|
-
# @param [Hash] record the record
|
246
|
-
# @param [Hash] opts others
|
247
|
-
def prepare_placeholders(time, record, opts)
|
248
|
-
struct = UndefOpenStruct.new(record)
|
249
|
-
struct.time = Time.at(time)
|
250
|
-
opts.each {|key, value| struct.__send__("#{key}=", value) }
|
251
|
-
@placeholders = struct
|
267
|
+
def time_value(time)
|
268
|
+
Time.at(time)
|
252
269
|
end
|
253
270
|
|
254
|
-
#
|
271
|
+
# Preprocess record map to convert into ruby string expansion
|
255
272
|
#
|
256
|
-
# @param [String]
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
273
|
+
# @param [Hash|String|Array] value record map config
|
274
|
+
# @param [Boolean] force_stringify the value must be string, used for hash key
|
275
|
+
def preprocess_map(value, force_stringify = false)
|
276
|
+
new_value = nil
|
277
|
+
if value.is_a?(String)
|
278
|
+
if @auto_typecast and !force_stringify
|
279
|
+
if single_placeholder_matched = value.match(/\A\${([^}]+)}\z/) # ${..} => ..
|
280
|
+
new_value = single_placeholder_matched[1]
|
281
|
+
end
|
282
|
+
end
|
283
|
+
unless new_value
|
284
|
+
new_value = %Q{%Q[#{value.gsub(/\$\{([^}]+)\}/, '#{\1}')}]} # xx${..}xx => %Q[xx#{..}xx]
|
285
|
+
end
|
286
|
+
elsif value.is_a?(Hash)
|
287
|
+
new_value = {}
|
288
|
+
value.each_pair do |k, v|
|
289
|
+
new_value[preprocess_map(k, true)] = preprocess_map(v)
|
263
290
|
end
|
291
|
+
elsif value.is_a?(Array)
|
292
|
+
new_value = []
|
293
|
+
value.each_with_index do |v, i|
|
294
|
+
new_value[i] = preprocess_map(v)
|
295
|
+
end
|
296
|
+
else
|
297
|
+
new_value = value
|
264
298
|
end
|
265
|
-
|
266
|
-
|
299
|
+
new_value
|
300
|
+
end
|
301
|
+
|
302
|
+
def prepare_placeholders(placeholder_values)
|
303
|
+
@tag = placeholder_values['tag']
|
304
|
+
@time = placeholder_values['time']
|
305
|
+
@record = placeholder_values['record']
|
306
|
+
@tag_parts = placeholder_values['tag_parts']
|
307
|
+
@tag_prefix = placeholder_values['tag_prefix']
|
308
|
+
@tag_suffix = placeholder_values['tag_suffix']
|
309
|
+
@hostname = placeholder_values['hostname']
|
310
|
+
end
|
311
|
+
|
312
|
+
# Expand string with placeholders
|
313
|
+
#
|
314
|
+
# @param [String] str
|
315
|
+
def expand(str, force_stringify = false)
|
316
|
+
@cleanroom_expander.expand(
|
317
|
+
str,
|
318
|
+
@tag,
|
319
|
+
@time,
|
320
|
+
@record,
|
321
|
+
@tag_parts,
|
322
|
+
@tag_prefix,
|
323
|
+
@tag_suffix,
|
324
|
+
@hostname,
|
325
|
+
)
|
267
326
|
rescue => e
|
268
327
|
log.warn "record_reformer: failed to expand `#{str}`", :error_class => e.class, :error => e.message
|
269
328
|
log.warn_backtrace
|
270
329
|
nil
|
271
330
|
end
|
272
331
|
|
273
|
-
class
|
332
|
+
class CleanroomExpander
|
333
|
+
def expand(__str_to_eval__, tag, time, record, tag_parts, tag_prefix, tag_suffix, hostname)
|
334
|
+
tags = tag_parts # for old version compatibility
|
335
|
+
@record = record # for old version compatibility
|
336
|
+
instance_eval(__str_to_eval__)
|
337
|
+
end
|
338
|
+
|
339
|
+
# for old version compatibility
|
340
|
+
def method_missing(name)
|
341
|
+
key = name.to_s
|
342
|
+
if @record.has_key?(key)
|
343
|
+
@record[key]
|
344
|
+
else
|
345
|
+
raise NameError, "undefined local variable or method `#{key}'"
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
274
349
|
(Object.instance_methods).each do |m|
|
275
350
|
undef_method m unless m.to_s =~ /^__|respond_to_missing\?|object_id|public_methods|instance_eval|method_missing|define_singleton_method|respond_to\?|new_ostruct_member/
|
276
351
|
end
|
data/test/helper.rb
CHANGED
@@ -7,16 +7,17 @@ unless defined?(Test::Unit::AssertionFailedError)
|
|
7
7
|
end
|
8
8
|
end
|
9
9
|
|
10
|
-
#
|
11
|
-
# https://github.com/fluent/fluentd/blob/
|
10
|
+
# Reduce sleep period at
|
11
|
+
# https://github.com/fluent/fluentd/blob/a271b3ec76ab7cf89ebe4012aa5b3912333dbdb7/lib/fluent/test/base.rb#L81
|
12
12
|
module Fluent
|
13
13
|
module Test
|
14
14
|
class TestDriver
|
15
|
-
def run(&block)
|
15
|
+
def run(num_waits = 10, &block)
|
16
16
|
@instance.start
|
17
17
|
begin
|
18
18
|
# wait until thread starts
|
19
|
-
#
|
19
|
+
# num_waits.times { sleep 0.05 }
|
20
|
+
sleep 0.05
|
20
21
|
return yield
|
21
22
|
ensure
|
22
23
|
@instance.shutdown
|
@@ -470,6 +470,28 @@ EOC
|
|
470
470
|
end
|
471
471
|
assert_equal(expected_results, actual_results)
|
472
472
|
end
|
473
|
+
|
474
|
+
test %Q[record["key"] with enable_ruby #{enable_ruby}] do
|
475
|
+
config = %[
|
476
|
+
tag tag
|
477
|
+
enable_ruby #{enable_ruby}
|
478
|
+
auto_typecast true
|
479
|
+
<record>
|
480
|
+
_timestamp ${record["@timestamp"]}
|
481
|
+
_foo_bar ${record["foo.bar"]}
|
482
|
+
</record>
|
483
|
+
]
|
484
|
+
d = create_driver(config, use_v1)
|
485
|
+
record = {
|
486
|
+
"foo.bar" => "foo.bar",
|
487
|
+
"@timestamp" => 10,
|
488
|
+
}
|
489
|
+
es = emit(config, use_v1, [record])
|
490
|
+
es.each_with_index do |(tag, time, r), i|
|
491
|
+
assert { r['_timestamp'] == record['@timestamp'] }
|
492
|
+
assert { r['_foo_bar'] == record['foo.bar'] }
|
493
|
+
end
|
494
|
+
end
|
473
495
|
end
|
474
496
|
|
475
497
|
test 'unknown placeholder (enable_ruby no)' do
|
@@ -495,7 +517,7 @@ EOC
|
|
495
517
|
</record>
|
496
518
|
]
|
497
519
|
d = create_driver(config, use_v1)
|
498
|
-
mock(d.instance.log).warn("record_reformer: failed to expand
|
520
|
+
mock(d.instance.log).warn("record_reformer: failed to expand `%Q[\#{unknown['bar']}]`", anything)
|
499
521
|
d.run { d.emit({}, @time) }
|
500
522
|
# emit, but nil value
|
501
523
|
assert_equal 1, d.emits.size
|
@@ -510,7 +532,7 @@ EOC
|
|
510
532
|
enable_ruby yes
|
511
533
|
]
|
512
534
|
d = create_driver(config, use_v1)
|
513
|
-
mock(d.instance.log).warn("record_reformer: failed to expand
|
535
|
+
mock(d.instance.log).warn("record_reformer: failed to expand `%Q[\#{unknown['bar']}]`", anything)
|
514
536
|
d.run { d.emit({}, @time) }
|
515
537
|
# nil tag message should not be emitted
|
516
538
|
assert_equal 0, d.emits.size
|
@@ -548,5 +570,38 @@ EOC
|
|
548
570
|
end
|
549
571
|
end
|
550
572
|
end
|
573
|
+
|
574
|
+
test "compatibility test (enable_ruby yes) (use_v1 #{use_v1})" do
|
575
|
+
config = %[
|
576
|
+
tag tag
|
577
|
+
enable_ruby yes
|
578
|
+
auto_typecast yes
|
579
|
+
<record>
|
580
|
+
_message prefix-${message}-suffix
|
581
|
+
_time ${Time.at(time)}
|
582
|
+
_number ${number == '-' ? 0 : number}
|
583
|
+
_match ${/0x[0-9a-f]+/.match(hex)[0]}
|
584
|
+
_timestamp ${__send__("@timestamp")}
|
585
|
+
_foo_bar ${__send__('foo.bar')}
|
586
|
+
</record>
|
587
|
+
]
|
588
|
+
d = create_driver(config, use_v1)
|
589
|
+
record = {
|
590
|
+
"number" => "-",
|
591
|
+
"hex" => "0x10",
|
592
|
+
"foo.bar" => "foo.bar",
|
593
|
+
"@timestamp" => 10,
|
594
|
+
"message" => "10",
|
595
|
+
}
|
596
|
+
es = emit(config, use_v1, [record])
|
597
|
+
es.each_with_index do |(tag, time, r), i|
|
598
|
+
assert { r['_message'] == "prefix-#{record['message']}-suffix" }
|
599
|
+
assert { r['_time'] == Time.at(@time) }
|
600
|
+
assert { r['_number'] == 0 }
|
601
|
+
assert { r['_match'] == record['hex'] }
|
602
|
+
assert { r['_timestamp'] == record['@timestamp'] }
|
603
|
+
assert { r['_foo_bar'] == record['foo.bar'] }
|
604
|
+
end
|
605
|
+
end
|
551
606
|
end
|
552
607
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fluent-plugin-record-reformer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Naotoshi Seo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-01-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fluentd
|
@@ -119,6 +119,7 @@ files:
|
|
119
119
|
- ".travis.yml"
|
120
120
|
- CHANGELOG.md
|
121
121
|
- Gemfile
|
122
|
+
- Gemfile.fluentd.0.10
|
122
123
|
- LICENSE
|
123
124
|
- README.md
|
124
125
|
- Rakefile
|