fluent-plugin-record-reformer 0.7.2 → 0.8.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/.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
|