require_relative 'helper'
require 'time'
require 'fluent/plugin/out_record_reformer'
Fluent::Test.setup
class RecordReformerOutputTest < Test::Unit::TestCase
setup do
@hostname = Socket.gethostname.chomp
@tag = 'test.tag'
@tag_parts = @tag.split('.')
@time = Time.local(1,2,3,4,5,2010,nil,nil,nil,nil)
Timecop.freeze(@time)
end
teardown do
Timecop.return
end
def create_driver(conf, use_v1)
Fluent::Test::OutputTestDriver.new(Fluent::RecordReformerOutput, @tag).configure(conf, use_v1)
end
def emit(config, use_v1, msgs = [''])
d = create_driver(config, use_v1)
d.run do
records = msgs.map do |msg|
next msg if msg.is_a?(Hash)
{ 'eventType0' => 'bar', 'message' => msg }
end
records.each do |record|
d.emit(record, @time)
end
end
@instance = d.instance
d.emits
end
CONFIG = %[
tag reformed.${tag}
hostname ${hostname}
input_tag ${tag}
time ${time.to_s}
message ${hostname} ${tag_parts.last} ${URI.escape(message)}
]
[true, false].each do |use_v1|
sub_test_case 'configure' do
test 'typical usage' do
assert_nothing_raised do
create_driver(CONFIG, use_v1)
end
end
test "tag is not specified" do
assert_raise(Fluent::ConfigError) do
create_driver('', use_v1)
end
end
test "keep_keys must be specified together with renew_record true" do
assert_raise(Fluent::ConfigError) do
create_driver(%[keep_keys a], use_v1)
end
end
end
sub_test_case "test options" do
test 'typical usage' do
msgs = ['1', '2']
emits = emit(CONFIG, use_v1, msgs)
assert_equal 2, emits.size
emits.each_with_index do |(tag, time, record), i|
assert_equal("reformed.#{@tag}", tag)
assert_equal('bar', record['eventType0'])
assert_equal(@hostname, record['hostname'])
assert_equal(@tag, record['input_tag'])
assert_equal(@time.to_s, record['time'])
assert_equal("#{@hostname} #{@tag_parts[-1]} #{msgs[i]}", record['message'])
end
end
test '(obsolete) output_tag' do
config = %[output_tag reformed.${tag}]
msgs = ['1']
emits = emit(config, use_v1, msgs)
emits.each_with_index do |(tag, time, record), i|
assert_equal("reformed.#{@tag}", tag)
end
end
test 'record directive' do
config = %[
tag reformed.${tag}
hostname ${hostname}
tag ${tag}
time ${time.to_s}
message ${hostname} ${tag_parts.last} ${message}
]
msgs = ['1', '2']
emits = emit(config, use_v1, msgs)
emits.each_with_index do |(tag, time, record), i|
assert_equal("reformed.#{@tag}", tag)
assert_equal('bar', record['eventType0'])
assert_equal(@hostname, record['hostname'])
assert_equal(@tag, record['tag'])
assert_equal(@time.to_s, record['time'])
assert_equal("#{@hostname} #{@tag_parts[-1]} #{msgs[i]}", record['message'])
end
end
test 'remove_keys' do
config = CONFIG + %[remove_keys eventType0,message]
emits = emit(config, use_v1)
emits.each_with_index do |(tag, time, record), i|
assert_equal("reformed.#{@tag}", tag)
assert_not_include(record, 'eventType0')
assert_equal(@hostname, record['hostname'])
assert_equal(@tag, record['input_tag'])
assert_equal(@time.to_s, record['time'])
assert_not_include(record, 'message')
end
end
test 'renew_record' do
config = CONFIG + %[renew_record true]
msgs = ['1', '2']
emits = emit(config, use_v1, msgs)
emits.each_with_index do |(tag, time, record), i|
assert_equal("reformed.#{@tag}", tag)
assert_not_include(record, 'eventType0')
assert_equal(@hostname, record['hostname'])
assert_equal(@tag, record['input_tag'])
assert_equal(@time.to_s, record['time'])
assert_equal("#{@hostname} #{@tag_parts[-1]} #{msgs[i]}", record['message'])
end
end
test 'renew_time_key' do
times = [ Time.local(2,2,3,4,5,2010,nil,nil,nil,nil), Time.local(3,2,3,4,5,2010,nil,nil,nil,nil) ]
config = <
message ${hostname} ${tag_parts.last} ${URI.encode(message)}
]
msgs = ['1', '2']
emits = emit(config, use_v1, msgs)
emits.each_with_index do |(tag, time, record), i|
assert_equal("reformed.#{@tag}", tag)
assert_equal("#{@hostname} ", record['message'])
end
end
end
sub_test_case 'test placeholders' do
%w[yes no].each do |enable_ruby|
test "hostname with enble_ruby #{enable_ruby}" do
config = %[
tag tag
enable_ruby #{enable_ruby}
message ${hostname}
]
emits = emit(config, use_v1)
emits.each do |(tag, time, record)|
assert_equal(@hostname, record['message'])
end
end
test "tag with enable_ruby #{enable_ruby}" do
config = %[
tag tag
enable_ruby #{enable_ruby}
message ${tag}
]
emits = emit(config, use_v1)
emits.each do |(tag, time, record)|
assert_equal(@tag, record['message'])
end
end
test "tag_parts with enable_ruby #{enable_ruby}" do
config = %[
tag tag
enable_ruby #{enable_ruby}
message ${tag_parts[0]} ${tag_parts[-1]}
]
expected = "#{@tag.split('.').first} #{@tag.split('.').last}"
emits = emit(config, use_v1)
emits.each do |(tag, time, record)|
assert_equal(expected, record['message'])
end
end
test "(obsolete) tags with enable_ruby #{enable_ruby}" do
config = %[
tag tag
enable_ruby #{enable_ruby}
message ${tags[0]} ${tags[-1]}
]
expected = "#{@tag.split('.').first} #{@tag.split('.').last}"
emits = emit(config, use_v1)
emits.each do |(tag, time, record)|
assert_equal(expected, record['message'])
end
end
test "${tag_prefix[N]} and ${tag_suffix[N]} with enable_ruby #{enable_ruby}" do
config = %[
tag tag
enable_ruby #{enable_ruby}
message ${tag_prefix[1]} ${tag_prefix[-2]} ${tag_suffix[2]} ${tag_suffix[-3]}
]
@tag = 'prefix.test.tag.suffix'
expected = "prefix.test prefix.test.tag tag.suffix test.tag.suffix"
emits = emit(config, use_v1)
emits.each do |(tag, time, record)|
assert_equal(expected, record['message'])
end
end
test "time with enable_ruby #{enable_ruby}" do
config = %[
tag tag
enable_ruby #{enable_ruby}
message ${time}
]
emits = emit(config, use_v1)
emits.each do |(tag, time, record)|
assert_equal(@time.to_s, record['message'])
end
end
test "record keys with enable_ruby #{enable_ruby}" do
config = %[
tag tag
enable_ruby #{enable_ruby}
remove_keys eventType0
message bar ${message}
eventtype ${eventType0}
]
msgs = ['1', '2']
emits = emit(config, use_v1, msgs)
emits.each_with_index do |(tag, time, record), i|
assert_not_include(record, 'eventType0')
assert_equal("bar", record['eventtype'])
assert_equal("bar #{msgs[i]}", record['message'])
end
end
test "Prevent overriting reserved keys (such as tag, etc) #40 with enable_ruby #{enable_ruby}" do
config = %[
tag tag
enable_ruby #{enable_ruby}
new_tag ${tag}
new_time ${time}
new_record_tag ${record["tag"]}
new_record_time ${record["time"]}
]
records = [{'tag' => 'tag', 'time' => 'time'}]
emits = emit(config, use_v1, records)
emits.each do |(tag, time, record)|
assert_not_equal('tag', record['new_tag'])
assert_equal(@tag, record['new_tag'])
assert_not_equal('time', record['new_time'])
assert_equal(@time.to_s, record['new_time'])
assert_equal('tag', record['new_record_tag'])
assert_equal('time', record['new_record_time'])
end
end
test "hash values with placeholders with enable_ruby #{enable_ruby}" do
config = %[
tag tag
enable_ruby #{enable_ruby}
hash_field {"hostname":"${hostname}", "tag":"${tag}", "${tag}":100}
]
msgs = ['1', '2']
es = emit(config, use_v1, msgs)
es.each_with_index do |(tag, time, record), i|
assert_equal({"hostname" => @hostname, "tag" => @tag, "#{@tag}" => 100}, record['hash_field'])
end
end
test "array values with placeholders with enable_ruby #{enable_ruby}" do
config = %[
tag tag
enable_ruby #{enable_ruby}
array_field ["${hostname}", "${tag}"]
]
msgs = ['1', '2']
es = emit(config, use_v1, msgs)
es.each_with_index do |(tag, time, record), i|
assert_equal([@hostname, @tag], record['array_field'])
end
end
test "array and hash values with placeholders with enable_ruby #{enable_ruby}" do
config = %[
tag tag
enable_ruby #{enable_ruby}
mixed_field [{"tag":"${tag}"}]
]
msgs = ['1', '2']
es = emit(config, use_v1, msgs)
es.each_with_index do |(tag, time, record), i|
assert_equal([{"tag" => @tag}], record['mixed_field'])
end
end
if use_v1 == true
# works with only v1 config
test "keys with placeholders with enable_ruby #{enable_ruby}" do
config = %[
tag tag
enable_ruby #{enable_ruby}
renew_record true
${hostname} hostname
foo.${tag} tag
]
msgs = ['1', '2']
es = emit(config, use_v1, msgs)
es.each_with_index do |(tag, time, record), i|
assert_equal({@hostname=>'hostname',"foo.#{@tag}"=>'tag'}, record)
end
end
end
test "disabled autodetectction of value type with enable_ruby #{enable_ruby}" do
config = %[
tag tag
enable_ruby #{enable_ruby}
auto_typecast false
single ${source}
multiple ${source}${source}
with_prefix prefix-${source}
with_suffix ${source}-suffix
]
msgs = [
{ "source" => "string" },
{ "source" => 123 },
{ "source" => [1, 2] },
{ "source" => {a:1, b:2} },
{ "source" => nil },
]
expected_results = [
{ :single => "string",
:multiple => "stringstring",
:with_prefix => "prefix-string",
:with_suffix => "string-suffix" },
{ :single => 123.to_s,
:multiple => "#{123.to_s}#{123.to_s}",
:with_prefix => "prefix-#{123.to_s}",
:with_suffix => "#{123.to_s}-suffix" },
{ :single => [1, 2].to_s,
:multiple => "#{[1, 2].to_s}#{[1, 2].to_s}",
:with_prefix => "prefix-#{[1, 2].to_s}",
:with_suffix => "#{[1, 2].to_s}-suffix" },
{ :single => {a:1, b:2}.to_s,
:multiple => "#{{a:1, b:2}.to_s}#{{a:1, b:2}.to_s}",
:with_prefix => "prefix-#{{a:1, b:2}.to_s}",
:with_suffix => "#{{a:1, b:2}.to_s}-suffix" },
{ :single => nil.to_s,
:multiple => "#{nil.to_s}#{nil.to_s}",
:with_prefix => "prefix-#{nil.to_s}",
:with_suffix => "#{nil.to_s}-suffix" },
]
actual_results = []
es = emit(config, use_v1, msgs)
es.each_with_index do |(tag, time, record), i|
actual_results << {
:single => record["single"],
:multiple => record["multiple"],
:with_prefix => record["with_prefix"],
:with_suffix => record["with_suffix"],
}
end
assert_equal(expected_results, actual_results)
end
test "enabled autodetectction of value type with enable_ruby #{enable_ruby}" do
config = %[
tag tag
enable_ruby #{enable_ruby}
auto_typecast true
single ${source}
multiple ${source}${source}
with_prefix prefix-${source}
with_suffix ${source}-suffix
]
msgs = [
{ "source" => "string" },
{ "source" => 123 },
{ "source" => [1, 2] },
{ "source" => {a:1, b:2} },
{ "source" => nil },
]
expected_results = [
{ :single => "string",
:multiple => "stringstring",
:with_prefix => "prefix-string",
:with_suffix => "string-suffix" },
{ :single => 123,
:multiple => "#{123.to_s}#{123.to_s}",
:with_prefix => "prefix-#{123.to_s}",
:with_suffix => "#{123.to_s}-suffix" },
{ :single => [1, 2],
:multiple => "#{[1, 2].to_s}#{[1, 2].to_s}",
:with_prefix => "prefix-#{[1, 2].to_s}",
:with_suffix => "#{[1, 2].to_s}-suffix" },
{ :single => {a:1, b:2},
:multiple => "#{{a:1, b:2}.to_s}#{{a:1, b:2}.to_s}",
:with_prefix => "prefix-#{{a:1, b:2}.to_s}",
:with_suffix => "#{{a:1, b:2}.to_s}-suffix" },
{ :single => nil,
:multiple => "#{nil.to_s}#{nil.to_s}",
:with_prefix => "prefix-#{nil.to_s}",
:with_suffix => "#{nil.to_s}-suffix" },
]
actual_results = []
es = emit(config, use_v1, msgs)
es.each_with_index do |(tag, time, record), i|
actual_results << {
:single => record["single"],
:multiple => record["multiple"],
:with_prefix => record["with_prefix"],
:with_suffix => record["with_suffix"],
}
end
assert_equal(expected_results, actual_results)
end
test %Q[record["key"] with enable_ruby #{enable_ruby}] do
config = %[
tag tag
enable_ruby #{enable_ruby}
auto_typecast true
_timestamp ${record["@timestamp"]}
_foo_bar ${record["foo.bar"]}
]
d = create_driver(config, use_v1)
record = {
"foo.bar" => "foo.bar",
"@timestamp" => 10,
}
es = emit(config, use_v1, [record])
es.each_with_index do |(tag, time, r), i|
assert { r['_timestamp'] == record['@timestamp'] }
assert { r['_foo_bar'] == record['foo.bar'] }
end
end
end
test 'unknown placeholder (enable_ruby no)' do
config = %[
tag tag
enable_ruby no
message ${unknown}
]
d = create_driver(config, use_v1)
mock(d.instance.log).warn("record_reformer: unknown placeholder `${unknown}` found")
d.run { d.emit({}, @time) }
assert_equal 1, d.emits.size
end
test 'failed to expand record field (enable_ruby yes)' do
config = %[
tag tag
enable_ruby yes
message ${unknown['bar']}
]
d = create_driver(config, use_v1)
mock(d.instance.log).warn("record_reformer: failed to expand `%Q[\#{unknown['bar']}]`", anything)
d.run { d.emit({}, @time) }
# emit, but nil value
assert_equal 1, d.emits.size
d.emits.each do |(tag, time, record)|
assert_nil(record['message'])
end
end
test 'failed to expand tag (enable_ruby yes)' do
config = %[
tag ${unknown['bar']}
enable_ruby yes
]
d = create_driver(config, use_v1)
mock(d.instance.log).warn("record_reformer: failed to expand `%Q[\#{unknown['bar']}]`", anything)
d.run { d.emit({}, @time) }
# nil tag message should not be emitted
assert_equal 0, d.emits.size
end
test 'expand fields starting with @ (enable_ruby no)' do
config = %[
tag tag
enable_ruby no
foo ${@timestamp}
]
d = create_driver(config, use_v1)
message = {"@timestamp" => "foo"}
d.run { d.emit(message, @time) }
d.emits.each do |(tag, time, record)|
assert_equal message["@timestamp"], record["foo"]
end
end
# https://github.com/sonots/fluent-plugin-record-reformer/issues/35
test 'auto_typecast placeholder containing {} (enable_ruby yes)' do
config = %[
tag tag
enable_ruby yes
auto_typecast yes
foo ${record.map{|k,v|v}}
]
d = create_driver(config, use_v1)
message = {"@timestamp" => "foo"}
d.run { d.emit(message, @time) }
d.emits.each do |(tag, time, record)|
assert_equal [message["@timestamp"]], record["foo"]
end
end
test 'expand fields starting with @ (enable_ruby yes)' do
config = %[
tag tag
enable_ruby yes
foo ${__send__("@timestamp")}
]
d = create_driver(config, use_v1)
message = {"@timestamp" => "foo"}
d.run { d.emit(message, @time) }
d.emits.each do |(tag, time, record)|
assert_equal message["@timestamp"], record["foo"]
end
end
end
test "compatibility test (enable_ruby yes) (use_v1 #{use_v1})" do
config = %[
tag tag
enable_ruby yes
auto_typecast yes
_message prefix-${message}-suffix
_time ${Time.at(time)}
_number ${number == '-' ? 0 : number}
_match ${/0x[0-9a-f]+/.match(hex)[0]}
_timestamp ${__send__("@timestamp")}
_foo_bar ${__send__('foo.bar')}
]
d = create_driver(config, use_v1)
record = {
"number" => "-",
"hex" => "0x10",
"foo.bar" => "foo.bar",
"@timestamp" => 10,
"message" => "10",
}
es = emit(config, use_v1, [record])
es.each_with_index do |(tag, time, r), i|
assert { r['_message'] == "prefix-#{record['message']}-suffix" }
assert { r['_time'] == Time.at(@time) }
assert { r['_number'] == 0 }
assert { r['_match'] == record['hex'] }
assert { r['_timestamp'] == record['@timestamp'] }
assert { r['_foo_bar'] == record['foo.bar'] }
end
end
end
end