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.

Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/CONTRIBUTING.md +6 -1
  4. data/ChangeLog +38 -0
  5. data/Rakefile +21 -0
  6. data/example/out_exec_filter.conf +42 -0
  7. data/lib/fluent/agent.rb +2 -2
  8. data/lib/fluent/command/binlog_reader.rb +1 -1
  9. data/lib/fluent/command/cat.rb +5 -2
  10. data/lib/fluent/compat/output.rb +7 -8
  11. data/lib/fluent/compat/parser.rb +139 -11
  12. data/lib/fluent/config/configure_proxy.rb +2 -11
  13. data/lib/fluent/config/section.rb +7 -0
  14. data/lib/fluent/configurable.rb +1 -3
  15. data/lib/fluent/log.rb +1 -1
  16. data/lib/fluent/plugin/base.rb +17 -0
  17. data/lib/fluent/plugin/filter_parser.rb +108 -0
  18. data/lib/fluent/plugin/filter_record_transformer.rb +4 -7
  19. data/lib/fluent/plugin/filter_stdout.rb +1 -1
  20. data/lib/fluent/plugin/formatter.rb +5 -0
  21. data/lib/fluent/plugin/formatter_msgpack.rb +4 -0
  22. data/lib/fluent/plugin/formatter_stdout.rb +3 -2
  23. data/lib/fluent/plugin/formatter_tsv.rb +34 -0
  24. data/lib/fluent/plugin/in_exec.rb +48 -93
  25. data/lib/fluent/plugin/in_forward.rb +25 -105
  26. data/lib/fluent/plugin/in_http.rb +68 -65
  27. data/lib/fluent/plugin/in_syslog.rb +29 -51
  28. data/lib/fluent/plugin/multi_output.rb +1 -3
  29. data/lib/fluent/plugin/out_exec.rb +58 -71
  30. data/lib/fluent/plugin/out_exec_filter.rb +199 -279
  31. data/lib/fluent/plugin/out_file.rb +155 -80
  32. data/lib/fluent/plugin/out_forward.rb +44 -47
  33. data/lib/fluent/plugin/out_stdout.rb +6 -21
  34. data/lib/fluent/plugin/output.rb +23 -17
  35. data/lib/fluent/plugin/parser.rb +121 -61
  36. data/lib/fluent/plugin/parser_csv.rb +9 -3
  37. data/lib/fluent/plugin/parser_json.rb +37 -35
  38. data/lib/fluent/plugin/parser_ltsv.rb +11 -19
  39. data/lib/fluent/plugin/parser_msgpack.rb +50 -0
  40. data/lib/fluent/plugin/parser_regexp.rb +15 -42
  41. data/lib/fluent/plugin/parser_tsv.rb +8 -3
  42. data/lib/fluent/plugin_helper.rb +8 -1
  43. data/lib/fluent/plugin_helper/child_process.rb +139 -73
  44. data/lib/fluent/plugin_helper/compat_parameters.rb +93 -4
  45. data/lib/fluent/plugin_helper/event_emitter.rb +14 -1
  46. data/lib/fluent/plugin_helper/extract.rb +16 -4
  47. data/lib/fluent/plugin_helper/formatter.rb +9 -11
  48. data/lib/fluent/plugin_helper/inject.rb +4 -0
  49. data/lib/fluent/plugin_helper/parser.rb +3 -3
  50. data/lib/fluent/root_agent.rb +1 -1
  51. data/lib/fluent/test/driver/base.rb +51 -37
  52. data/lib/fluent/test/driver/base_owner.rb +18 -8
  53. data/lib/fluent/test/driver/multi_output.rb +2 -1
  54. data/lib/fluent/test/driver/output.rb +29 -6
  55. data/lib/fluent/test/helpers.rb +3 -1
  56. data/lib/fluent/test/log.rb +4 -0
  57. data/lib/fluent/test/startup_shutdown.rb +13 -0
  58. data/lib/fluent/time.rb +14 -8
  59. data/lib/fluent/version.rb +1 -1
  60. data/test/command/test_binlog_reader.rb +5 -1
  61. data/test/config/test_configurable.rb +173 -0
  62. data/test/config/test_configure_proxy.rb +0 -43
  63. data/test/plugin/test_base.rb +16 -0
  64. data/test/plugin/test_filter_parser.rb +665 -0
  65. data/test/plugin/test_filter_record_transformer.rb +11 -3
  66. data/test/plugin/test_filter_stdout.rb +18 -27
  67. data/test/plugin/test_in_dummy.rb +1 -1
  68. data/test/plugin/test_in_exec.rb +206 -94
  69. data/test/plugin/test_in_forward.rb +310 -327
  70. data/test/plugin/test_in_http.rb +310 -186
  71. data/test/plugin/test_out_exec.rb +223 -68
  72. data/test/plugin/test_out_exec_filter.rb +520 -169
  73. data/test/plugin/test_out_file.rb +620 -177
  74. data/test/plugin/test_out_forward.rb +110 -132
  75. data/test/plugin/test_out_null.rb +1 -1
  76. data/test/plugin/test_out_secondary_file.rb +4 -2
  77. data/test/plugin/test_out_stdout.rb +14 -35
  78. data/test/plugin/test_parser.rb +359 -0
  79. data/test/plugin/test_parser_csv.rb +1 -2
  80. data/test/plugin/test_parser_json.rb +3 -4
  81. data/test/plugin/test_parser_labeled_tsv.rb +1 -2
  82. data/test/plugin/test_parser_none.rb +1 -2
  83. data/test/plugin/test_parser_regexp.rb +8 -4
  84. data/test/plugin/test_parser_tsv.rb +4 -3
  85. data/test/plugin_helper/test_child_process.rb +184 -0
  86. data/test/plugin_helper/test_compat_parameters.rb +88 -1
  87. data/test/plugin_helper/test_extract.rb +0 -1
  88. data/test/plugin_helper/test_formatter.rb +5 -2
  89. data/test/plugin_helper/test_parser.rb +6 -5
  90. data/test/test_output.rb +24 -2
  91. data/test/test_plugin_classes.rb +20 -0
  92. data/test/test_root_agent.rb +139 -0
  93. data/test/test_test_drivers.rb +132 -0
  94. metadata +12 -4
  95. 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, DEFAULT_FORMAT_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
- def prefer_delayed_commit
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(conf: conf.elements('format').first, default_type: DEFAULT_FORMAT_TYPE)
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
- "#{Time.at(time).localtime.strftime(TIME_FORMAT)} #{tag}: #{@formatter.format(tag, time, record).chomp}\n"
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
@@ -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, :in_tests
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
- # in_tests: for tests of plugins with test drivers
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
- unless @in_tests
403
- if @flush_mode == :interval || @chunk_key_time
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
- @buffer.takeback_chunk(chunk.unique_id)
1003
-
1004
- if @under_plugin_development
1005
- raise
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, error_class: e.class, error: e
1213
+ log.error "error on output thread", plugin_id: plugin_id, error: e
1208
1214
  log.error_backtrace
1209
1215
  raise
1210
1216
  end
@@ -31,17 +31,43 @@ module Fluent
31
31
 
32
32
  configured_in :parse
33
33
 
34
- # SET false BEFORE CONFIGURE, to return nil when time not parsed
35
- attr_accessor :estimate_current_event
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
- def initialize
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
- @estimate_current_event = true
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
- TimeParser = Fluent::TimeParser
55
- end
56
-
57
- class ValuesParser < Parser
58
- include Fluent::TypeConverter
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
- config_param :keys, :array, default: []
61
- config_param :time_key, :string, default: nil
62
- config_param :null_value_pattern, :string, default: nil
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 configure(conf)
66
- super
94
+ def parse_partial_data(data, &block)
95
+ raise NotImplementedError, "Optional API #parse_partial_data is not implemented"
96
+ end
67
97
 
68
- if @time_key && !@keys.include?(@time_key) && @estimate_current_event
69
- raise Fluent::ConfigError, "time_key (#{@time_key.inspect}) is not included in keys (#{@keys.inspect})"
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
- if @time_format && !@time_key
73
- raise Fluent::ConfigError, "time_format parameter is ignored because time_key parameter is not set. at #{conf.inspect}"
74
- end
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
- @time_parser = time_parser_create
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
- if @null_value_pattern
79
- @null_value_pattern = Regexp.new(@null_value_pattern)
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
- @mutex = Mutex.new
136
+ return time, record
83
137
  end
84
138
 
85
- def values_map(values)
86
- record = Hash[keys.zip(values.map { |value| convert_value_to_nil(value) })]
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
- convert_field_type!(record) if @type_converters
143
+ TRUTHY_VALUES = ['true', 'yes', '1']
106
144
 
107
- return time, record
108
- end
145
+ def build_type_converters(types)
146
+ return nil unless types
109
147
 
110
- private
148
+ converters = {}
111
149
 
112
- def convert_field_type!(record)
113
- @type_converters.each_key { |key|
114
- if value = record[key]
115
- record[key] = convert_type(key, value)
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
- def convert_value_to_nil(value)
121
- if value and @null_empty_string
122
- value = (value == '') ? nil : value
123
- end
124
- if value and @null_value_pattern
125
- value = ::Fluent::StringUtil.match_regexp(@null_value_pattern, value) ? nil : value
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
- value
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 < ValuesParser
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
- yield values_map(CSV.parse_line(text, col_sep: @delimiter))
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
- config_param :time_key, :string, default: 'time'
29
- config_param :json_parser, :string, default: 'oj'
29
+ config_set_default :time_key, 'time'
30
+ config_param :json_parser, :enum, list: [:oj, :yajl, :json], default: :oj
30
31
 
31
- def configure(conf)
32
- super
32
+ config_set_default :time_type, :float
33
33
 
34
- if @time_format
35
- @time_parser = time_parser_create
36
- @mutex = Mutex.new
34
+ def configure(conf)
35
+ if conf.has_key?('time_format')
36
+ conf['time_type'] ||= 'string'
37
37
  end
38
38
 
39
- begin
40
- raise LoadError unless @json_parser == 'oj'
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
- @load_proc = Oj.method(:load)
44
- @error_class = Oj::ParseError
45
- rescue LoadError
46
- @load_proc = Yajl.method(:load)
47
- @error_class = Yajl::ParseError
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
- record = @load_proc.call(text)
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