fluentd 0.14.8 → 0.14.9
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of fluentd might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/CONTRIBUTING.md +6 -1
- data/ChangeLog +38 -0
- data/Rakefile +21 -0
- data/example/out_exec_filter.conf +42 -0
- data/lib/fluent/agent.rb +2 -2
- data/lib/fluent/command/binlog_reader.rb +1 -1
- data/lib/fluent/command/cat.rb +5 -2
- data/lib/fluent/compat/output.rb +7 -8
- data/lib/fluent/compat/parser.rb +139 -11
- data/lib/fluent/config/configure_proxy.rb +2 -11
- data/lib/fluent/config/section.rb +7 -0
- data/lib/fluent/configurable.rb +1 -3
- data/lib/fluent/log.rb +1 -1
- data/lib/fluent/plugin/base.rb +17 -0
- data/lib/fluent/plugin/filter_parser.rb +108 -0
- data/lib/fluent/plugin/filter_record_transformer.rb +4 -7
- data/lib/fluent/plugin/filter_stdout.rb +1 -1
- data/lib/fluent/plugin/formatter.rb +5 -0
- data/lib/fluent/plugin/formatter_msgpack.rb +4 -0
- data/lib/fluent/plugin/formatter_stdout.rb +3 -2
- data/lib/fluent/plugin/formatter_tsv.rb +34 -0
- data/lib/fluent/plugin/in_exec.rb +48 -93
- data/lib/fluent/plugin/in_forward.rb +25 -105
- data/lib/fluent/plugin/in_http.rb +68 -65
- data/lib/fluent/plugin/in_syslog.rb +29 -51
- data/lib/fluent/plugin/multi_output.rb +1 -3
- data/lib/fluent/plugin/out_exec.rb +58 -71
- data/lib/fluent/plugin/out_exec_filter.rb +199 -279
- data/lib/fluent/plugin/out_file.rb +155 -80
- data/lib/fluent/plugin/out_forward.rb +44 -47
- data/lib/fluent/plugin/out_stdout.rb +6 -21
- data/lib/fluent/plugin/output.rb +23 -17
- data/lib/fluent/plugin/parser.rb +121 -61
- data/lib/fluent/plugin/parser_csv.rb +9 -3
- data/lib/fluent/plugin/parser_json.rb +37 -35
- data/lib/fluent/plugin/parser_ltsv.rb +11 -19
- data/lib/fluent/plugin/parser_msgpack.rb +50 -0
- data/lib/fluent/plugin/parser_regexp.rb +15 -42
- data/lib/fluent/plugin/parser_tsv.rb +8 -3
- data/lib/fluent/plugin_helper.rb +8 -1
- data/lib/fluent/plugin_helper/child_process.rb +139 -73
- data/lib/fluent/plugin_helper/compat_parameters.rb +93 -4
- data/lib/fluent/plugin_helper/event_emitter.rb +14 -1
- data/lib/fluent/plugin_helper/extract.rb +16 -4
- data/lib/fluent/plugin_helper/formatter.rb +9 -11
- data/lib/fluent/plugin_helper/inject.rb +4 -0
- data/lib/fluent/plugin_helper/parser.rb +3 -3
- data/lib/fluent/root_agent.rb +1 -1
- data/lib/fluent/test/driver/base.rb +51 -37
- data/lib/fluent/test/driver/base_owner.rb +18 -8
- data/lib/fluent/test/driver/multi_output.rb +2 -1
- data/lib/fluent/test/driver/output.rb +29 -6
- data/lib/fluent/test/helpers.rb +3 -1
- data/lib/fluent/test/log.rb +4 -0
- data/lib/fluent/test/startup_shutdown.rb +13 -0
- data/lib/fluent/time.rb +14 -8
- data/lib/fluent/version.rb +1 -1
- data/test/command/test_binlog_reader.rb +5 -1
- data/test/config/test_configurable.rb +173 -0
- data/test/config/test_configure_proxy.rb +0 -43
- data/test/plugin/test_base.rb +16 -0
- data/test/plugin/test_filter_parser.rb +665 -0
- data/test/plugin/test_filter_record_transformer.rb +11 -3
- data/test/plugin/test_filter_stdout.rb +18 -27
- data/test/plugin/test_in_dummy.rb +1 -1
- data/test/plugin/test_in_exec.rb +206 -94
- data/test/plugin/test_in_forward.rb +310 -327
- data/test/plugin/test_in_http.rb +310 -186
- data/test/plugin/test_out_exec.rb +223 -68
- data/test/plugin/test_out_exec_filter.rb +520 -169
- data/test/plugin/test_out_file.rb +620 -177
- data/test/plugin/test_out_forward.rb +110 -132
- data/test/plugin/test_out_null.rb +1 -1
- data/test/plugin/test_out_secondary_file.rb +4 -2
- data/test/plugin/test_out_stdout.rb +14 -35
- data/test/plugin/test_parser.rb +359 -0
- data/test/plugin/test_parser_csv.rb +1 -2
- data/test/plugin/test_parser_json.rb +3 -4
- data/test/plugin/test_parser_labeled_tsv.rb +1 -2
- data/test/plugin/test_parser_none.rb +1 -2
- data/test/plugin/test_parser_regexp.rb +8 -4
- data/test/plugin/test_parser_tsv.rb +4 -3
- data/test/plugin_helper/test_child_process.rb +184 -0
- data/test/plugin_helper/test_compat_parameters.rb +88 -1
- data/test/plugin_helper/test_extract.rb +0 -1
- data/test/plugin_helper/test_formatter.rb +5 -2
- data/test/plugin_helper/test_parser.rb +6 -5
- data/test/test_output.rb +24 -2
- data/test/test_plugin_classes.rb +20 -0
- data/test/test_root_agent.rb +139 -0
- data/test/test_test_drivers.rb +132 -0
- metadata +12 -4
- data/test/plugin/test_parser_base.rb +0 -32
@@ -22,6 +22,7 @@ module Fluent::Plugin
|
|
22
22
|
|
23
23
|
helpers :inject, :formatter, :compat_parameters
|
24
24
|
|
25
|
+
DEFAULT_LINE_FORMAT_TYPE = 'stdout'
|
25
26
|
DEFAULT_FORMAT_TYPE = 'json'
|
26
27
|
TIME_FORMAT = '%Y-%m-%d %H:%M:%S.%9N %z'
|
27
28
|
|
@@ -32,33 +33,22 @@ module Fluent::Plugin
|
|
32
33
|
end
|
33
34
|
|
34
35
|
config_section :format do
|
35
|
-
config_set_default :@type,
|
36
|
+
config_set_default :@type, DEFAULT_LINE_FORMAT_TYPE
|
37
|
+
config_set_default :output_type, DEFAULT_FORMAT_TYPE
|
36
38
|
end
|
37
39
|
|
38
40
|
def prefer_buffered_processing
|
39
41
|
false
|
40
42
|
end
|
41
43
|
|
42
|
-
|
43
|
-
@delayed
|
44
|
-
end
|
45
|
-
|
46
|
-
attr_accessor :delayed
|
47
|
-
|
48
|
-
def initialize
|
49
|
-
super
|
50
|
-
@delayed = false
|
51
|
-
end
|
44
|
+
attr_accessor :formatter
|
52
45
|
|
53
46
|
def configure(conf)
|
54
|
-
if conf['output_type'] && !conf['format']
|
55
|
-
conf['format'] = conf['output_type']
|
56
|
-
end
|
57
47
|
compat_parameters_convert(conf, :inject, :formatter)
|
58
48
|
|
59
49
|
super
|
60
50
|
|
61
|
-
@formatter = formatter_create
|
51
|
+
@formatter = formatter_create
|
62
52
|
end
|
63
53
|
|
64
54
|
def process(tag, es)
|
@@ -70,16 +60,11 @@ module Fluent::Plugin
|
|
70
60
|
|
71
61
|
def format(tag, time, record)
|
72
62
|
record = inject_values_to_record(tag, time, record)
|
73
|
-
|
63
|
+
@formatter.format(tag, time, record).chomp + "\n"
|
74
64
|
end
|
75
65
|
|
76
66
|
def write(chunk)
|
77
67
|
chunk.write_to($log)
|
78
68
|
end
|
79
|
-
|
80
|
-
def try_write(chunk)
|
81
|
-
chunk.write_to($log)
|
82
|
-
commit_write(chunk.unique_id)
|
83
|
-
end
|
84
69
|
end
|
85
70
|
end
|
data/lib/fluent/plugin/output.rb
CHANGED
@@ -150,10 +150,9 @@ module Fluent
|
|
150
150
|
|
151
151
|
# for tests
|
152
152
|
attr_reader :buffer, :retry, :secondary, :chunk_keys, :chunk_key_time, :chunk_key_tag
|
153
|
-
attr_accessor :output_enqueue_thread_waiting, :
|
154
|
-
|
153
|
+
attr_accessor :output_enqueue_thread_waiting, :dequeued_chunks, :dequeued_chunks_mutex
|
155
154
|
# output_enqueue_thread_waiting: for test of output.rb itself
|
156
|
-
|
155
|
+
attr_accessor :retry_for_error_chunk # if true, error flush will be retried even if under_plugin_development is true
|
157
156
|
|
158
157
|
def initialize
|
159
158
|
super
|
@@ -161,7 +160,6 @@ module Fluent
|
|
161
160
|
@buffering = false
|
162
161
|
@delayed_commit = false
|
163
162
|
@as_secondary = false
|
164
|
-
@in_tests = false
|
165
163
|
@primary_instance = nil
|
166
164
|
|
167
165
|
# TODO: well organized counters
|
@@ -188,6 +186,7 @@ module Fluent
|
|
188
186
|
@secondary = nil
|
189
187
|
@retry = nil
|
190
188
|
@dequeued_chunks = nil
|
189
|
+
@dequeued_chunks_mutex = nil
|
191
190
|
@output_enqueue_thread = nil
|
192
191
|
@output_flush_threads = nil
|
193
192
|
|
@@ -195,6 +194,8 @@ module Fluent
|
|
195
194
|
@chunk_keys = @chunk_key_time = @chunk_key_tag = nil
|
196
195
|
@flush_mode = nil
|
197
196
|
@timekey_zone = nil
|
197
|
+
|
198
|
+
@retry_for_error_chunk = false
|
198
199
|
end
|
199
200
|
|
200
201
|
def acts_as_secondary(primary)
|
@@ -207,6 +208,7 @@ module Fluent
|
|
207
208
|
@timekey_zone = @primary_instance.timekey_zone
|
208
209
|
@output_time_formatter_cache = {}
|
209
210
|
end
|
211
|
+
self.context_router = primary.context_router
|
210
212
|
|
211
213
|
(class << self; self; end).module_eval do
|
212
214
|
define_method(:commit_write){ |chunk_id| @primary_instance.commit_write(chunk_id, delayed: delayed_commit, secondary: true) }
|
@@ -317,7 +319,6 @@ module Fluent
|
|
317
319
|
@secondary = Plugin.new_output(secondary_type)
|
318
320
|
@secondary.acts_as_secondary(self)
|
319
321
|
@secondary.configure(secondary_conf)
|
320
|
-
@secondary.router = router if @secondary.has_router?
|
321
322
|
if (self.class != @secondary.class) && (@custom_format || @secondary.implement?(:custom_format))
|
322
323
|
log.warn "secondary type should be same with primary one", primary: self.class.to_s, secondary: @secondary.class.to_s
|
323
324
|
end
|
@@ -399,10 +400,8 @@ module Fluent
|
|
399
400
|
end
|
400
401
|
@output_flush_thread_current_position = 0
|
401
402
|
|
402
|
-
|
403
|
-
|
404
|
-
@output_enqueue_thread = thread_create(:enqueue_thread, &method(:enqueue_thread_run))
|
405
|
-
end
|
403
|
+
if !@under_plugin_development && (@flush_mode == :interval || @chunk_key_time)
|
404
|
+
@output_enqueue_thread = thread_create(:enqueue_thread, &method(:enqueue_thread_run))
|
406
405
|
end
|
407
406
|
end
|
408
407
|
@secondary.start if @secondary
|
@@ -981,11 +980,11 @@ module Fluent
|
|
981
980
|
if output.delayed_commit
|
982
981
|
log.trace "executing delayed write and commit", chunk: dump_unique_id_hex(chunk.unique_id)
|
983
982
|
@counters_monitor.synchronize{ @write_count += 1 }
|
984
|
-
output.try_write(chunk)
|
985
983
|
@dequeued_chunks_mutex.synchronize do
|
986
984
|
# delayed_commit_timeout for secondary is configured in <buffer> of primary (<secondary> don't get <buffer>)
|
987
985
|
@dequeued_chunks << DequeuedChunkInfo.new(chunk.unique_id, Time.now, self.delayed_commit_timeout)
|
988
986
|
end
|
987
|
+
output.try_write(chunk)
|
989
988
|
else # output plugin without delayed purge
|
990
989
|
chunk_id = chunk.unique_id
|
991
990
|
dump_chunk_id = dump_unique_id_hex(chunk_id)
|
@@ -994,16 +993,17 @@ module Fluent
|
|
994
993
|
log.trace "executing sync write", chunk: dump_chunk_id
|
995
994
|
output.write(chunk)
|
996
995
|
log.trace "write operation done, committing", chunk: dump_chunk_id
|
997
|
-
commit_write(chunk_id, secondary: using_secondary)
|
996
|
+
commit_write(chunk_id, delayed: false, secondary: using_secondary)
|
998
997
|
log.trace "done to commit a chunk", chunk: dump_chunk_id
|
999
998
|
end
|
1000
999
|
rescue => e
|
1001
1000
|
log.debug "taking back chunk for errors.", plugin_id: plugin_id, chunk: dump_unique_id_hex(chunk.unique_id)
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
1005
|
-
|
1001
|
+
if output.delayed_commit
|
1002
|
+
@dequeued_chunks_mutex.synchronize do
|
1003
|
+
@dequeued_chunks.delete_if{|d| d.chunk_id == chunk.unique_id }
|
1004
|
+
end
|
1006
1005
|
end
|
1006
|
+
@buffer.takeback_chunk(chunk.unique_id)
|
1007
1007
|
|
1008
1008
|
@retry_mutex.synchronize do
|
1009
1009
|
if @retry
|
@@ -1032,6 +1032,8 @@ module Fluent
|
|
1032
1032
|
log.warn_backtrace e.backtrace
|
1033
1033
|
end
|
1034
1034
|
end
|
1035
|
+
|
1036
|
+
raise if @under_plugin_development && !@retry_for_error_chunk
|
1035
1037
|
end
|
1036
1038
|
end
|
1037
1039
|
|
@@ -1059,7 +1061,11 @@ module Fluent
|
|
1059
1061
|
@output_flush_thread_current_position = (@output_flush_thread_current_position + 1) % @buffer_config.flush_thread_count
|
1060
1062
|
state = @output_flush_threads[@output_flush_thread_current_position]
|
1061
1063
|
state.next_time = 0
|
1062
|
-
state.thread.run
|
1064
|
+
if state.thread && state.thread.status # "run"/"sleep"/"aborting" or false(successfully stop) or nil(killed by exception)
|
1065
|
+
state.thread.run
|
1066
|
+
else
|
1067
|
+
log.warn "thread is already dead"
|
1068
|
+
end
|
1063
1069
|
end
|
1064
1070
|
|
1065
1071
|
def force_flush
|
@@ -1204,7 +1210,7 @@ module Fluent
|
|
1204
1210
|
rescue => e
|
1205
1211
|
# normal errors are rescued by output plugins in #try_flush
|
1206
1212
|
# so this rescue section is for critical & unrecoverable errors
|
1207
|
-
log.error "error on output thread", plugin_id: plugin_id,
|
1213
|
+
log.error "error on output thread", plugin_id: plugin_id, error: e
|
1208
1214
|
log.error_backtrace
|
1209
1215
|
raise
|
1210
1216
|
end
|
data/lib/fluent/plugin/parser.rb
CHANGED
@@ -31,17 +31,43 @@ module Fluent
|
|
31
31
|
|
32
32
|
configured_in :parse
|
33
33
|
|
34
|
-
|
35
|
-
|
34
|
+
### types can be specified as string-based hash style
|
35
|
+
# field1:type, field2:type, field3:type:option, field4:type:option
|
36
|
+
### or, JSON format
|
37
|
+
# {"field1":"type", "field2":"type", "field3":"type:option", "field4":"type:option"}
|
38
|
+
config_param :types, :hash, value_type: :string, default: nil
|
36
39
|
|
40
|
+
# available options are:
|
41
|
+
# array: (1st) delimiter
|
42
|
+
# time : type[, format, timezone] -> type should be a valid "time_type"(string/unixtime/float)
|
43
|
+
# : format[, timezone]
|
44
|
+
|
45
|
+
config_param :time_key, :string, default: nil
|
46
|
+
config_param :null_value_pattern, :string, default: nil
|
47
|
+
config_param :null_empty_string, :bool, default: false
|
48
|
+
config_param :estimate_current_event, :bool, default: true
|
37
49
|
config_param :keep_time_key, :bool, default: false
|
38
50
|
|
39
|
-
|
51
|
+
AVAILABLE_PARSER_VALUE_TYPES = ['string', 'integer', 'float', 'bool', 'time', 'array']
|
52
|
+
|
53
|
+
# for tests
|
54
|
+
attr_reader :type_converters
|
55
|
+
|
56
|
+
PARSER_TYPES = [:text_per_line, :text, :binary]
|
57
|
+
def parser_type
|
58
|
+
:text_per_line
|
59
|
+
end
|
60
|
+
|
61
|
+
def configure(conf)
|
40
62
|
super
|
41
|
-
|
63
|
+
|
64
|
+
@time_parser = time_parser_create
|
65
|
+
@null_value_regexp = @null_value_pattern && Regexp.new(@null_value_pattern)
|
66
|
+
@type_converters = build_type_converters(@types)
|
67
|
+
@execute_convert_values = @type_converters || @null_value_regexp || @null_empty_string
|
42
68
|
end
|
43
69
|
|
44
|
-
def parse(text)
|
70
|
+
def parse(text, &block)
|
45
71
|
raise NotImplementedError, "Implement this method in child class"
|
46
72
|
end
|
47
73
|
|
@@ -51,80 +77,114 @@ module Fluent
|
|
51
77
|
parse(*a, &b)
|
52
78
|
end
|
53
79
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
80
|
+
def implement?(feature)
|
81
|
+
methods_of_plugin = self.class.instance_methods(false)
|
82
|
+
case feature
|
83
|
+
when :parse_io then methods_of_plugin.include?(:parse_io)
|
84
|
+
when :parse_partial_data then methods_of_plugin.include?(:parse_partial_data)
|
85
|
+
else
|
86
|
+
raise ArgumentError, "Unknown feature for parser plugin: #{feature}"
|
87
|
+
end
|
88
|
+
end
|
59
89
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
config_param :null_empty_string, :bool, default: false
|
90
|
+
def parse_io(io, &block)
|
91
|
+
raise NotImplementedError, "Optional API #parse_io is not implemented"
|
92
|
+
end
|
64
93
|
|
65
|
-
def
|
66
|
-
|
94
|
+
def parse_partial_data(data, &block)
|
95
|
+
raise NotImplementedError, "Optional API #parse_partial_data is not implemented"
|
96
|
+
end
|
67
97
|
|
68
|
-
|
69
|
-
|
98
|
+
def parse_time(record)
|
99
|
+
if @time_key && record.respond_to?(:has_key?) && record.has_key?(@time_key)
|
100
|
+
src = if @keep_time_key
|
101
|
+
record[@time_key]
|
102
|
+
else
|
103
|
+
record.delete(@time_key)
|
104
|
+
end
|
105
|
+
@time_parser.parse(src)
|
106
|
+
elsif @estimate_current_event
|
107
|
+
Fluent::EventTime.now
|
108
|
+
else
|
109
|
+
nil
|
70
110
|
end
|
111
|
+
rescue Fluent::TimeParser::TimeParseError => e
|
112
|
+
raise ParserError, e.message
|
113
|
+
end
|
71
114
|
|
72
|
-
|
73
|
-
|
74
|
-
|
115
|
+
# def parse(text, &block)
|
116
|
+
# time, record = convert_values(time, record)
|
117
|
+
# yield time, record
|
118
|
+
# end
|
119
|
+
def convert_values(time, record)
|
120
|
+
return time, record unless @execute_convert_values
|
75
121
|
|
76
|
-
|
122
|
+
record.each_key do |key|
|
123
|
+
value = record[key]
|
124
|
+
next unless value # nil/null value is always left as-is.
|
77
125
|
|
78
|
-
|
79
|
-
|
126
|
+
if value.is_a?(String) && string_like_null(value)
|
127
|
+
record[key] = nil
|
128
|
+
next
|
129
|
+
end
|
130
|
+
|
131
|
+
if @type_converters && @type_converters.has_key?(key)
|
132
|
+
record[key] = @type_converters[key].call(value)
|
133
|
+
end
|
80
134
|
end
|
81
135
|
|
82
|
-
|
136
|
+
return time, record
|
83
137
|
end
|
84
138
|
|
85
|
-
def
|
86
|
-
|
87
|
-
|
88
|
-
if @time_key
|
89
|
-
value = @keep_time_key ? record[@time_key] : record.delete(@time_key)
|
90
|
-
time = if value.nil?
|
91
|
-
if @estimate_current_event
|
92
|
-
Fluent::EventTime.now
|
93
|
-
else
|
94
|
-
nil
|
95
|
-
end
|
96
|
-
else
|
97
|
-
@mutex.synchronize { @time_parser.parse(value) }
|
98
|
-
end
|
99
|
-
elsif @estimate_current_event
|
100
|
-
time = Fluent::EventTime.now
|
101
|
-
else
|
102
|
-
time = nil
|
103
|
-
end
|
139
|
+
def string_like_null(value, null_empty_string = @null_empty_string, null_value_regexp = @null_value_regexp)
|
140
|
+
null_empty_string && value.empty? || null_value_regexp && string_safe_encoding(value){|s| null_value_regexp.match(s) }
|
141
|
+
end
|
104
142
|
|
105
|
-
|
143
|
+
TRUTHY_VALUES = ['true', 'yes', '1']
|
106
144
|
|
107
|
-
|
108
|
-
|
145
|
+
def build_type_converters(types)
|
146
|
+
return nil unless types
|
109
147
|
|
110
|
-
|
148
|
+
converters = {}
|
111
149
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
150
|
+
types.each_pair do |field_name, type_definition|
|
151
|
+
type, option = type_definition.split(":", 2)
|
152
|
+
unless AVAILABLE_PARSER_VALUE_TYPES.include?(type)
|
153
|
+
raise Fluent::ConfigError, "unknown value conversion for key:'#{field_name}', type:'#{type}'"
|
116
154
|
end
|
117
|
-
}
|
118
|
-
end
|
119
155
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
156
|
+
conv = case type
|
157
|
+
when 'string' then ->(v){ v.to_s }
|
158
|
+
when 'integer' then ->(v){ v.to_i rescue v.to_s.to_i }
|
159
|
+
when 'float' then ->(v){ v.to_f rescue v.to_s.to_f }
|
160
|
+
when 'bool' then ->(v){ TRUTHY_VALUES.include?(v.to_s.downcase) }
|
161
|
+
when 'time'
|
162
|
+
# comma-separated: time:[timezone:]time_format
|
163
|
+
# time_format is unixtime/float/string-time-format
|
164
|
+
timep = if option
|
165
|
+
time_type = 'string' # estimate
|
166
|
+
timezone, time_format = option.split(':', 2)
|
167
|
+
unless Fluent::Timezone.validate(timezone)
|
168
|
+
timezone, time_format = nil, option
|
169
|
+
end
|
170
|
+
if Fluent::TimeMixin::TIME_TYPES.include?(time_format)
|
171
|
+
time_type, time_format = time_format, nil # unixtime/float
|
172
|
+
end
|
173
|
+
time_parser_create(type: time_type.to_sym, format: time_format, timezone: timezone)
|
174
|
+
else
|
175
|
+
time_parser_create(type: :string, format: nil, timezone: nil)
|
176
|
+
end
|
177
|
+
->(v){ timep.parse(v) rescue nil }
|
178
|
+
when 'array'
|
179
|
+
delimiter = option ? option.to_s : ','
|
180
|
+
->(v){ string_safe_encoding(v.to_s){|s| s.split(delimiter) } }
|
181
|
+
else
|
182
|
+
raise "BUG: unknown type even after check: #{type}"
|
183
|
+
end
|
184
|
+
converters[field_name] = conv
|
126
185
|
end
|
127
|
-
|
186
|
+
|
187
|
+
converters
|
128
188
|
end
|
129
189
|
end
|
130
190
|
end
|
@@ -20,13 +20,19 @@ require 'csv'
|
|
20
20
|
|
21
21
|
module Fluent
|
22
22
|
module Plugin
|
23
|
-
class CSVParser <
|
23
|
+
class CSVParser < Parser
|
24
24
|
Plugin.register_parser('csv', self)
|
25
25
|
|
26
|
+
desc 'Names of fields included in each lines'
|
27
|
+
config_param :keys, :array, value_type: :string
|
28
|
+
desc 'The delimiter character (or string) of CSV values'
|
26
29
|
config_param :delimiter, :string, default: ','
|
27
30
|
|
28
|
-
def parse(text)
|
29
|
-
|
31
|
+
def parse(text, &block)
|
32
|
+
values = CSV.parse_line(text, col_sep: @delimiter)
|
33
|
+
r = Hash[@keys.zip(values)]
|
34
|
+
time, record = convert_values(parse_time(r), r)
|
35
|
+
yield time, record
|
30
36
|
end
|
31
37
|
end
|
32
38
|
end
|
@@ -19,61 +19,63 @@ require 'fluent/env'
|
|
19
19
|
require 'fluent/time'
|
20
20
|
|
21
21
|
require 'yajl'
|
22
|
+
require 'json'
|
22
23
|
|
23
24
|
module Fluent
|
24
25
|
module Plugin
|
25
26
|
class JSONParser < Parser
|
26
27
|
Plugin.register_parser('json', self)
|
27
28
|
|
28
|
-
|
29
|
-
config_param :json_parser, :
|
29
|
+
config_set_default :time_key, 'time'
|
30
|
+
config_param :json_parser, :enum, list: [:oj, :yajl, :json], default: :oj
|
30
31
|
|
31
|
-
|
32
|
-
super
|
32
|
+
config_set_default :time_type, :float
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
|
34
|
+
def configure(conf)
|
35
|
+
if conf.has_key?('time_format')
|
36
|
+
conf['time_type'] ||= 'string'
|
37
37
|
end
|
38
38
|
|
39
|
-
|
40
|
-
|
39
|
+
super
|
40
|
+
@load_proc, @error_class = configure_json_parser(@json_parser)
|
41
|
+
end
|
42
|
+
|
43
|
+
def configure_json_parser(name)
|
44
|
+
case name
|
45
|
+
when :oj
|
41
46
|
require 'oj'
|
42
47
|
Oj.default_options = Fluent::DEFAULT_OJ_OPTIONS
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
+
[Oj.method(:load), Oj::ParseError]
|
49
|
+
when :json then [JSON.method(:load), JSON::ParserError]
|
50
|
+
when :yajl then [Yajl.method(:load), Yajl::ParseError]
|
51
|
+
else
|
52
|
+
raise "BUG: unknown json parser specified: #{name}"
|
48
53
|
end
|
54
|
+
rescue LoadError
|
55
|
+
name = :yajl
|
56
|
+
log.info "Oj is not installed, and failing back to Yajl for json parser"
|
57
|
+
retry
|
49
58
|
end
|
50
59
|
|
51
60
|
def parse(text)
|
52
|
-
|
53
|
-
|
54
|
-
value = @keep_time_key ? record[@time_key] : record.delete(@time_key)
|
55
|
-
if value
|
56
|
-
if @time_format
|
57
|
-
time = @mutex.synchronize { @time_parser.parse(value) }
|
58
|
-
else
|
59
|
-
begin
|
60
|
-
time = Fluent::EventTime.from_time(Time.at(value.to_f))
|
61
|
-
rescue => e
|
62
|
-
raise ParserError, "invalid time value: value = #{value}, error_class = #{e.class.name}, error = #{e.message}"
|
63
|
-
end
|
64
|
-
end
|
65
|
-
else
|
66
|
-
if @estimate_current_event
|
67
|
-
time = Fluent::EventTime.now
|
68
|
-
else
|
69
|
-
time = nil
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
61
|
+
r = @load_proc.call(text)
|
62
|
+
time, record = convert_values(parse_time(r), r)
|
73
63
|
yield time, record
|
74
64
|
rescue @error_class
|
75
65
|
yield nil, nil
|
76
66
|
end
|
67
|
+
|
68
|
+
def parser_type
|
69
|
+
:text
|
70
|
+
end
|
71
|
+
|
72
|
+
def parse_io(io, &block)
|
73
|
+
y = Yajl::Parser.new
|
74
|
+
y.on_parse_complete = ->(record){
|
75
|
+
block.call(parse_time(record), record)
|
76
|
+
}
|
77
|
+
y.parse(io)
|
78
|
+
end
|
77
79
|
end
|
78
80
|
end
|
79
81
|
end
|