fluent-plugin-record-reformer 0.8.3 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dc59bc6bee21df0c85068cc0e3eef1ccfbb9c9a0
4
- data.tar.gz: 88548f35221803b464118e7920893ce59f633139
3
+ metadata.gz: 224873397bd762ffdf8b4256c727f0d274c0f39b
4
+ data.tar.gz: 3c2f1c849c49d690c1e583ac03ecd3817ba670ea
5
5
  SHA512:
6
- metadata.gz: 165159b501d607dde4a932fa8e1e39248b21b4d01f50c87581bd4857a7af529484158140a28088a5717e4358cfd9e1064674c083b5080b56d8f188591889a2fe
7
- data.tar.gz: a5b0ad5603625ba7350f336e9a7fe8bbc63f91081bcf834f59fcb99dc7cc6de8bb04d38bfc24f581a86c3a18b0e0c22450b70f4d944709ed0fc4fd334797dcdd
6
+ metadata.gz: b8dbc917cf34ea266ec33e4a39c59e87536856bf43af2ea1889714b71eb3d1145dc03f45789d7af99b6c89f9d36b581fd171ca94188c1afe25d19bcf3153464a
7
+ data.tar.gz: 9c47cd0a33f18d0b3f62ecc43077df7c1a2710aca5e110d39225a4c2a66e89823826ba3685f9f444f921098897fb67c1b71941575d3580d578e0817f9c4ee9ae
@@ -1,14 +1,14 @@
1
1
  rvm:
2
- - 2.0.*
3
2
  - 2.1.*
4
3
  - 2.2.*
5
4
  - 2.3.0
5
+ - 2.4.0
6
6
  gemfile:
7
7
  - Gemfile
8
8
  - Gemfile.fluentd.0.12
9
9
  - Gemfile.fluentd.0.10
10
10
  matrix:
11
11
  exclude:
12
- - rvm: 2.0.*
13
- gemfile: Gemfile
12
+ - rvm: 2.4.0
13
+ gemfile: Gemfile.fluentd.0.10
14
14
  before_install: gem update bundler
@@ -1,3 +1,9 @@
1
+ ## 0.9.0 (2017/02/21)
2
+
3
+ Enhancements:
4
+
5
+ * Use v0.14 API for fluentd v0.14
6
+
1
7
  ## 0.8.3 (2017/01/26)
2
8
 
3
9
  Fixes
data/README.md CHANGED
@@ -4,6 +4,12 @@
4
4
 
5
5
  Fluentd plugin to add or replace fields of a event record
6
6
 
7
+ ## Requirements
8
+
9
+ See [.travis.yml](.travis.yml)
10
+
11
+ Note that `fluent-plugin-record-reformer` supports both v0.14 API and v0.12 API in one gem.
12
+
7
13
  ## Installation
8
14
 
9
15
  Use RubyGems:
@@ -25,7 +31,7 @@ Example:
25
31
  hostname ${hostname}
26
32
  input_tag ${tag}
27
33
  last_tag ${tag_parts[-1]}
28
- message ${message}, yay!
34
+ message ${record['message']}, yay!
29
35
  </record>
30
36
  </match>
31
37
 
@@ -65,7 +71,7 @@ Example:
65
71
  hostname ${hostname}
66
72
  input_tag ${tag}
67
73
  last_tag ${tag_parts[-1]}
68
- message ${message}, yay!
74
+ message ${record['message']}, yay!
69
75
  </match>
70
76
 
71
77
  This results in same, but please note that following option parameters are reserved, so can not be used as a record key.
@@ -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.8.3"
6
+ gem.version = "0.9.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"
@@ -1,356 +1,7 @@
1
- require 'ostruct'
2
-
3
- module Fluent
4
- class RecordReformerOutput < Output
5
- Fluent::Plugin.register_output('record_reformer', self)
6
-
7
- def initialize
8
- require 'socket'
9
- super
10
- end
11
-
12
- config_param :output_tag, :string, :default => nil, # obsolete
13
- :desc => 'The output tag name. This option is deprecated. Use `tag` option instead.'
14
- config_param :tag, :string, :default => nil,
15
- :desc => 'The output tag name.'
16
- config_param :remove_keys, :string, :default => nil,
17
- :desc => 'Specify record keys to be removed by a string separated by , (comma).'
18
- config_param :keep_keys, :string, :default => nil,
19
- :desc => 'Specify record keys to be kept by a string separated by , (comma).'
20
- config_param :renew_record, :bool, :default => false,
21
- :desc => 'Creates an output record newly without extending (merging) the input record fields.'
22
- config_param :renew_time_key, :string, :default => nil,
23
- :desc => 'Overwrites the time of events with a value of the record field.'
24
- config_param :enable_ruby, :bool, :default => true, # true for lower version compatibility
25
- :desc => 'Enable to use ruby codes in placeholders.'
26
- config_param :auto_typecast, :bool, :default => false, # false for lower version compatibility
27
- :desc => 'Automatically cast the field types.'
28
-
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
-
31
- # To support log_level option implemented by Fluentd v0.10.43
32
- unless method_defined?(:log)
33
- define_method("log") { $log }
34
- end
35
-
36
- # Define `router` method of v0.12 to support v0.10 or earlier
37
- unless method_defined?(:router)
38
- define_method("router") { Fluent::Engine }
39
- end
40
-
41
- def configure(conf)
42
- super
43
-
44
- map = {}
45
- conf.each_pair { |k, v|
46
- next if BUILTIN_CONFIGURATIONS.include?(k)
47
- conf.has_key?(k) # to suppress unread configuration warning
48
- map[k] = parse_value(v)
49
- }
50
- # <record></record> directive
51
- conf.elements.select { |element| element.name == 'record' }.each { |element|
52
- element.each_pair { |k, v|
53
- element.has_key?(k) # to suppress unread configuration warning
54
- map[k] = parse_value(v)
55
- }
56
- }
57
-
58
- if @remove_keys
59
- @remove_keys = @remove_keys.split(',')
60
- end
61
-
62
- if @keep_keys
63
- raise Fluent::ConfigError, "out_record_reformer: `renew_record` must be true to use `keep_keys`" unless @renew_record
64
- @keep_keys = @keep_keys.split(',')
65
- end
66
-
67
- if @output_tag and @tag.nil? # for lower version compatibility
68
- log.warn "out_record_reformer: `output_tag` is deprecated. Use `tag` option instead."
69
- @tag = @output_tag
70
- end
71
- if @tag.nil?
72
- raise Fluent::ConfigError, "out_record_reformer: `tag` must be specified"
73
- end
74
-
75
- placeholder_expander_params = {
76
- :log => log,
77
- :auto_typecast => @auto_typecast,
78
- }
79
- @placeholder_expander =
80
- if @enable_ruby
81
- # require utilities which would be used in ruby placeholders
82
- require 'pathname'
83
- require 'uri'
84
- require 'cgi'
85
- RubyPlaceholderExpander.new(placeholder_expander_params)
86
- else
87
- PlaceholderExpander.new(placeholder_expander_params)
88
- end
89
- @map = @placeholder_expander.preprocess_map(map)
90
- @tag = @placeholder_expander.preprocess_map(@tag)
91
-
92
- @hostname = Socket.gethostname
93
- end
94
-
95
- def emit(tag, es, chain)
96
- tag_parts = tag.split('.')
97
- tag_prefix = tag_prefix(tag_parts)
98
- tag_suffix = tag_suffix(tag_parts)
99
- placeholder_values = {
100
- 'tag' => tag,
101
- 'tags' => tag_parts, # for old version compatibility
102
- 'tag_parts' => tag_parts,
103
- 'tag_prefix' => tag_prefix,
104
- 'tag_suffix' => tag_suffix,
105
- 'hostname' => @hostname,
106
- }
107
- last_record = nil
108
- es.each {|time, record|
109
- last_record = record # for debug log
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)
115
- if new_tag
116
- if @renew_time_key && new_record.has_key?(@renew_time_key)
117
- time = new_record[@renew_time_key].to_i
118
- end
119
- @remove_keys.each {|k| new_record.delete(k) } if @remove_keys
120
- router.emit(new_tag, time, new_record)
121
- end
122
- }
123
- chain.next
124
- rescue => e
125
- log.warn "record_reformer: #{e.class} #{e.message} #{e.backtrace.first}"
126
- log.debug "record_reformer: tag:#{@tag} map:#{@map} record:#{last_record} placeholder_values:#{placeholder_values}"
127
- end
128
-
129
- private
130
-
131
- def parse_value(value_str)
132
- if value_str.start_with?('{', '[')
133
- JSON.parse(value_str)
134
- else
135
- value_str
136
- end
137
- rescue => e
138
- log.warn "failed to parse #{value_str} as json. Assuming #{value_str} is a string", :error_class => e.class, :error => e.message
139
- value_str # emit as string
140
- end
141
-
142
- def reform(tag, record, placeholder_values)
143
- placeholders = @placeholder_expander.prepare_placeholders(placeholder_values)
144
-
145
- new_tag = expand_placeholders(tag, placeholders)
146
-
147
- new_record = @renew_record ? {} : record.dup
148
- @keep_keys.each {|k| new_record[k] = record[k]} if @keep_keys and @renew_record
149
- new_record.merge!(expand_placeholders(@map, placeholders))
150
-
151
- [new_tag, new_record]
152
- end
153
-
154
- def expand_placeholders(value, placeholders)
155
- if value.is_a?(String)
156
- new_value = @placeholder_expander.expand(value, placeholders)
157
- elsif value.is_a?(Hash)
158
- new_value = {}
159
- value.each_pair do |k, v|
160
- new_key = @placeholder_expander.expand(k, placeholders, true)
161
- new_value[new_key] = expand_placeholders(v, placeholders)
162
- end
163
- elsif value.is_a?(Array)
164
- new_value = []
165
- value.each_with_index do |v, i|
166
- new_value[i] = expand_placeholders(v, placeholders)
167
- end
168
- else
169
- new_value = value
170
- end
171
- new_value
172
- end
173
-
174
- def tag_prefix(tag_parts)
175
- return [] if tag_parts.empty?
176
- tag_prefix = [tag_parts.first]
177
- 1.upto(tag_parts.size-1).each do |i|
178
- tag_prefix[i] = "#{tag_prefix[i-1]}.#{tag_parts[i]}"
179
- end
180
- tag_prefix
181
- end
182
-
183
- def tag_suffix(tag_parts)
184
- return [] if tag_parts.empty?
185
- rev_tag_parts = tag_parts.reverse
186
- rev_tag_suffix = [rev_tag_parts.first]
187
- 1.upto(tag_parts.size-1).each do |i|
188
- rev_tag_suffix[i] = "#{rev_tag_parts[i]}.#{rev_tag_suffix[i-1]}"
189
- end
190
- rev_tag_suffix.reverse!
191
- end
192
-
193
- # THIS CLASS MUST BE THREAD-SAFE
194
- class PlaceholderExpander
195
- attr_reader :placeholders, :log
196
-
197
- def initialize(params)
198
- @log = params[:log]
199
- @auto_typecast = params[:auto_typecast]
200
- end
201
-
202
- def time_value(time)
203
- Time.at(time).to_s
204
- end
205
-
206
- def preprocess_map(value, force_stringify = false)
207
- value
208
- end
209
-
210
- def prepare_placeholders(placeholder_values)
211
- placeholders = {}
212
-
213
- placeholder_values.each do |key, value|
214
- if value.kind_of?(Array) # tag_parts, etc
215
- size = value.size
216
- value.each_with_index do |v, idx|
217
- placeholders.store("${#{key}[#{idx}]}", v)
218
- placeholders.store("${#{key}[#{idx-size}]}", v) # support [-1]
219
- end
220
- elsif value.kind_of?(Hash) # record, etc
221
- value.each do |k, v|
222
- unless placeholder_values.has_key?(k) # prevent overwriting the reserved keys such as tag
223
- placeholders.store("${#{k}}", v)
224
- end
225
- placeholders.store(%Q[${#{key}["#{k}"]}], v) # record["foo"]
226
- end
227
- else # string, interger, float, and others?
228
- placeholders.store("${#{key}}", value)
229
- end
230
- end
231
-
232
- placeholders
233
- end
234
-
235
- # Expand string with placeholders
236
- #
237
- # @param [String] str
238
- # @param [Boolean] force_stringify the value must be string, used for hash key
239
- def expand(str, placeholders, force_stringify = false)
240
- if @auto_typecast and !force_stringify
241
- single_placeholder_matched = str.match(/\A(\${[^}]+}|__[A-Z_]+__)\z/)
242
- if single_placeholder_matched
243
- log_if_unknown_placeholder($1, placeholders)
244
- return placeholders[single_placeholder_matched[1]]
245
- end
246
- end
247
- str.gsub(/(\${[^}]+}|__[A-Z_]+__)/) {
248
- log_if_unknown_placeholder($1, placeholders)
249
- placeholders[$1]
250
- }
251
- end
252
-
253
- private
254
-
255
- def log_if_unknown_placeholder(placeholder, placeholders)
256
- unless placeholders.include?(placeholder)
257
- log.warn "record_reformer: unknown placeholder `#{placeholder}` found"
258
- end
259
- end
260
- end
261
-
262
- # THIS CLASS MUST BE THREAD-SAFE
263
- class RubyPlaceholderExpander
264
- attr_reader :log
265
-
266
- def initialize(params)
267
- @log = params[:log]
268
- @auto_typecast = params[:auto_typecast]
269
- @cleanroom_expander = CleanroomExpander.new
270
- end
271
-
272
- def time_value(time)
273
- Time.at(time)
274
- end
275
-
276
- # Preprocess record map to convert into ruby string expansion
277
- #
278
- # @param [Hash|String|Array] value record map config
279
- # @param [Boolean] force_stringify the value must be string, used for hash key
280
- def preprocess_map(value, force_stringify = false)
281
- new_value = nil
282
- if value.is_a?(String)
283
- if @auto_typecast and !force_stringify
284
- num_placeholders = value.scan('${').size
285
- if num_placeholders == 1 and value.start_with?('${') && value.end_with?('}')
286
- new_value = value[2..-2] # ${..} => ..
287
- end
288
- end
289
- unless new_value
290
- new_value = "%Q[#{value.gsub('${', '#{')}]" # xx${..}xx => %Q[xx#{..}xx]
291
- end
292
- elsif value.is_a?(Hash)
293
- new_value = {}
294
- value.each_pair do |k, v|
295
- new_value[preprocess_map(k, true)] = preprocess_map(v)
296
- end
297
- elsif value.is_a?(Array)
298
- new_value = []
299
- value.each_with_index do |v, i|
300
- new_value[i] = preprocess_map(v)
301
- end
302
- else
303
- new_value = value
304
- end
305
- new_value
306
- end
307
-
308
- def prepare_placeholders(placeholder_values)
309
- placeholder_values
310
- end
311
-
312
- # Expand string with placeholders
313
- #
314
- # @param [String] str
315
- def expand(str, placeholders, force_stringify = false)
316
- @cleanroom_expander.expand(
317
- str,
318
- placeholders['tag'],
319
- placeholders['time'],
320
- placeholders['record'],
321
- placeholders['tag_parts'],
322
- placeholders['tag_prefix'],
323
- placeholders['tag_suffix'],
324
- placeholders['hostname'],
325
- )
326
- rescue => e
327
- log.warn "record_reformer: failed to expand `#{str}`", :error_class => e.class, :error => e.message
328
- log.warn_backtrace
329
- nil
330
- end
331
-
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
- Thread.current[:record_reformer_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
- record = Thread.current[:record_reformer_record]
343
- if record.has_key?(key)
344
- record[key]
345
- else
346
- raise NameError, "undefined local variable or method `#{key}'"
347
- end
348
- end
349
-
350
- (Object.instance_methods).each do |m|
351
- 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/
352
- end
353
- end
354
- end
355
- end
1
+ require 'fluent/version'
2
+ major, minor, patch = Fluent::VERSION.split('.').map(&:to_i)
3
+ if major > 0 || (major == 0 && minor >= 14)
4
+ require_relative 'out_record_reformer/v14'
5
+ else
6
+ require_relative 'out_record_reformer/v12'
356
7
  end
@@ -0,0 +1,345 @@
1
+ require 'ostruct'
2
+ require 'socket'
3
+
4
+ module Fluent
5
+ module RecordReformerOutputCore
6
+ def initialize
7
+ super
8
+ end
9
+
10
+ def self.included(klass)
11
+ klass.config_param :output_tag, :string, :default => nil, # obsolete
12
+ :desc => 'The output tag name. This option is deprecated. Use `tag` option instead.'
13
+ klass.config_param :tag, :string, :default => nil,
14
+ :desc => 'The output tag name.'
15
+ klass.config_param :remove_keys, :string, :default => nil,
16
+ :desc => 'Specify record keys to be removed by a string separated by , (comma).'
17
+ klass.config_param :keep_keys, :string, :default => nil,
18
+ :desc => 'Specify record keys to be kept by a string separated by , (comma).'
19
+ klass.config_param :renew_record, :bool, :default => false,
20
+ :desc => 'Creates an output record newly without extending (merging) the input record fields.'
21
+ klass.config_param :renew_time_key, :string, :default => nil,
22
+ :desc => 'Overwrites the time of events with a value of the record field.'
23
+ klass.config_param :enable_ruby, :bool, :default => true, # true for lower version compatibility
24
+ :desc => 'Enable to use ruby codes in placeholders.'
25
+ klass.config_param :auto_typecast, :bool, :default => false, # false for lower version compatibility
26
+ :desc => 'Automatically cast the field types.'
27
+ end
28
+
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
+
31
+ def configure(conf)
32
+ super
33
+
34
+ map = {}
35
+ conf.each_pair { |k, v|
36
+ next if BUILTIN_CONFIGURATIONS.include?(k)
37
+ conf.has_key?(k) # to suppress unread configuration warning
38
+ map[k] = parse_value(v)
39
+ }
40
+ # <record></record> directive
41
+ conf.elements.select { |element| element.name == 'record' }.each { |element|
42
+ element.each_pair { |k, v|
43
+ element.has_key?(k) # to suppress unread configuration warning
44
+ map[k] = parse_value(v)
45
+ }
46
+ }
47
+
48
+ if @remove_keys
49
+ @remove_keys = @remove_keys.split(',')
50
+ end
51
+
52
+ if @keep_keys
53
+ raise Fluent::ConfigError, "out_record_reformer: `renew_record` must be true to use `keep_keys`" unless @renew_record
54
+ @keep_keys = @keep_keys.split(',')
55
+ end
56
+
57
+ if @output_tag and @tag.nil? # for lower version compatibility
58
+ log.warn "out_record_reformer: `output_tag` is deprecated. Use `tag` option instead."
59
+ @tag = @output_tag
60
+ end
61
+ if @tag.nil?
62
+ raise Fluent::ConfigError, "out_record_reformer: `tag` must be specified"
63
+ end
64
+
65
+ placeholder_expander_params = {
66
+ :log => log,
67
+ :auto_typecast => @auto_typecast,
68
+ }
69
+ @placeholder_expander =
70
+ if @enable_ruby
71
+ # require utilities which would be used in ruby placeholders
72
+ require 'pathname'
73
+ require 'uri'
74
+ require 'cgi'
75
+ RubyPlaceholderExpander.new(placeholder_expander_params)
76
+ else
77
+ PlaceholderExpander.new(placeholder_expander_params)
78
+ end
79
+ @map = @placeholder_expander.preprocess_map(map)
80
+ @tag = @placeholder_expander.preprocess_map(@tag)
81
+
82
+ @hostname = Socket.gethostname
83
+ end
84
+
85
+ def process(tag, es)
86
+ tag_parts = tag.split('.')
87
+ tag_prefix = tag_prefix(tag_parts)
88
+ tag_suffix = tag_suffix(tag_parts)
89
+ placeholder_values = {
90
+ 'tag' => tag,
91
+ 'tags' => tag_parts, # for old version compatibility
92
+ 'tag_parts' => tag_parts,
93
+ 'tag_prefix' => tag_prefix,
94
+ 'tag_suffix' => tag_suffix,
95
+ 'hostname' => @hostname,
96
+ }
97
+ last_record = nil
98
+ es.each {|time, record|
99
+ last_record = record # for debug log
100
+ placeholder_values.merge!({
101
+ 'time' => @placeholder_expander.time_value(time),
102
+ 'record' => record,
103
+ })
104
+ new_tag, new_record = reform(@tag, record, placeholder_values)
105
+ if new_tag
106
+ if @renew_time_key && new_record.has_key?(@renew_time_key)
107
+ time = new_record[@renew_time_key].to_i
108
+ end
109
+ @remove_keys.each {|k| new_record.delete(k) } if @remove_keys
110
+ router.emit(new_tag, time, new_record)
111
+ end
112
+ }
113
+ rescue => e
114
+ log.warn "record_reformer: #{e.class} #{e.message} #{e.backtrace.first}"
115
+ log.debug "record_reformer: tag:#{@tag} map:#{@map} record:#{last_record} placeholder_values:#{placeholder_values}"
116
+ end
117
+
118
+ private
119
+
120
+ def parse_value(value_str)
121
+ if value_str.start_with?('{', '[')
122
+ JSON.parse(value_str)
123
+ else
124
+ value_str
125
+ end
126
+ rescue => e
127
+ log.warn "failed to parse #{value_str} as json. Assuming #{value_str} is a string", :error_class => e.class, :error => e.message
128
+ value_str # emit as string
129
+ end
130
+
131
+ def reform(tag, record, placeholder_values)
132
+ placeholders = @placeholder_expander.prepare_placeholders(placeholder_values)
133
+
134
+ new_tag = expand_placeholders(tag, placeholders)
135
+
136
+ new_record = @renew_record ? {} : record.dup
137
+ @keep_keys.each {|k| new_record[k] = record[k]} if @keep_keys and @renew_record
138
+ new_record.merge!(expand_placeholders(@map, placeholders))
139
+
140
+ [new_tag, new_record]
141
+ end
142
+
143
+ def expand_placeholders(value, placeholders)
144
+ if value.is_a?(String)
145
+ new_value = @placeholder_expander.expand(value, placeholders)
146
+ elsif value.is_a?(Hash)
147
+ new_value = {}
148
+ value.each_pair do |k, v|
149
+ new_key = @placeholder_expander.expand(k, placeholders, true)
150
+ new_value[new_key] = expand_placeholders(v, placeholders)
151
+ end
152
+ elsif value.is_a?(Array)
153
+ new_value = []
154
+ value.each_with_index do |v, i|
155
+ new_value[i] = expand_placeholders(v, placeholders)
156
+ end
157
+ else
158
+ new_value = value
159
+ end
160
+ new_value
161
+ end
162
+
163
+ def tag_prefix(tag_parts)
164
+ return [] if tag_parts.empty?
165
+ tag_prefix = [tag_parts.first]
166
+ 1.upto(tag_parts.size-1).each do |i|
167
+ tag_prefix[i] = "#{tag_prefix[i-1]}.#{tag_parts[i]}"
168
+ end
169
+ tag_prefix
170
+ end
171
+
172
+ def tag_suffix(tag_parts)
173
+ return [] if tag_parts.empty?
174
+ rev_tag_parts = tag_parts.reverse
175
+ rev_tag_suffix = [rev_tag_parts.first]
176
+ 1.upto(tag_parts.size-1).each do |i|
177
+ rev_tag_suffix[i] = "#{rev_tag_parts[i]}.#{rev_tag_suffix[i-1]}"
178
+ end
179
+ rev_tag_suffix.reverse!
180
+ end
181
+
182
+ # THIS CLASS MUST BE THREAD-SAFE
183
+ class PlaceholderExpander
184
+ attr_reader :placeholders, :log
185
+
186
+ def initialize(params)
187
+ @log = params[:log]
188
+ @auto_typecast = params[:auto_typecast]
189
+ end
190
+
191
+ def time_value(time)
192
+ Time.at(time).to_s
193
+ end
194
+
195
+ def preprocess_map(value, force_stringify = false)
196
+ value
197
+ end
198
+
199
+ def prepare_placeholders(placeholder_values)
200
+ placeholders = {}
201
+
202
+ placeholder_values.each do |key, value|
203
+ if value.kind_of?(Array) # tag_parts, etc
204
+ size = value.size
205
+ value.each_with_index do |v, idx|
206
+ placeholders.store("${#{key}[#{idx}]}", v)
207
+ placeholders.store("${#{key}[#{idx-size}]}", v) # support [-1]
208
+ end
209
+ elsif value.kind_of?(Hash) # record, etc
210
+ value.each do |k, v|
211
+ unless placeholder_values.has_key?(k) # prevent overwriting the reserved keys such as tag
212
+ placeholders.store("${#{k}}", v)
213
+ end
214
+ placeholders.store(%Q[${#{key}["#{k}"]}], v) # record["foo"]
215
+ end
216
+ else # string, interger, float, and others?
217
+ placeholders.store("${#{key}}", value)
218
+ end
219
+ end
220
+
221
+ placeholders
222
+ end
223
+
224
+ # Expand string with placeholders
225
+ #
226
+ # @param [String] str
227
+ # @param [Boolean] force_stringify the value must be string, used for hash key
228
+ def expand(str, placeholders, force_stringify = false)
229
+ if @auto_typecast and !force_stringify
230
+ single_placeholder_matched = str.match(/\A(\${[^}]+}|__[A-Z_]+__)\z/)
231
+ if single_placeholder_matched
232
+ log_if_unknown_placeholder($1, placeholders)
233
+ return placeholders[single_placeholder_matched[1]]
234
+ end
235
+ end
236
+ str.gsub(/(\${[^}]+}|__[A-Z_]+__)/) {
237
+ log_if_unknown_placeholder($1, placeholders)
238
+ placeholders[$1]
239
+ }
240
+ end
241
+
242
+ private
243
+
244
+ def log_if_unknown_placeholder(placeholder, placeholders)
245
+ unless placeholders.include?(placeholder)
246
+ log.warn "record_reformer: unknown placeholder `#{placeholder}` found"
247
+ end
248
+ end
249
+ end
250
+
251
+ # THIS CLASS MUST BE THREAD-SAFE
252
+ class RubyPlaceholderExpander
253
+ attr_reader :log
254
+
255
+ def initialize(params)
256
+ @log = params[:log]
257
+ @auto_typecast = params[:auto_typecast]
258
+ @cleanroom_expander = CleanroomExpander.new
259
+ end
260
+
261
+ def time_value(time)
262
+ Time.at(time)
263
+ end
264
+
265
+ # Preprocess record map to convert into ruby string expansion
266
+ #
267
+ # @param [Hash|String|Array] value record map config
268
+ # @param [Boolean] force_stringify the value must be string, used for hash key
269
+ def preprocess_map(value, force_stringify = false)
270
+ new_value = nil
271
+ if value.is_a?(String)
272
+ if @auto_typecast and !force_stringify
273
+ num_placeholders = value.scan('${').size
274
+ if num_placeholders == 1 and value.start_with?('${') && value.end_with?('}')
275
+ new_value = value[2..-2] # ${..} => ..
276
+ end
277
+ end
278
+ unless new_value
279
+ new_value = "%Q[#{value.gsub('${', '#{')}]" # xx${..}xx => %Q[xx#{..}xx]
280
+ end
281
+ elsif value.is_a?(Hash)
282
+ new_value = {}
283
+ value.each_pair do |k, v|
284
+ new_value[preprocess_map(k, true)] = preprocess_map(v)
285
+ end
286
+ elsif value.is_a?(Array)
287
+ new_value = []
288
+ value.each_with_index do |v, i|
289
+ new_value[i] = preprocess_map(v)
290
+ end
291
+ else
292
+ new_value = value
293
+ end
294
+ new_value
295
+ end
296
+
297
+ def prepare_placeholders(placeholder_values)
298
+ placeholder_values
299
+ end
300
+
301
+ # Expand string with placeholders
302
+ #
303
+ # @param [String] str
304
+ def expand(str, placeholders, force_stringify = false)
305
+ @cleanroom_expander.expand(
306
+ str,
307
+ placeholders['tag'],
308
+ placeholders['time'],
309
+ placeholders['record'],
310
+ placeholders['tag_parts'],
311
+ placeholders['tag_prefix'],
312
+ placeholders['tag_suffix'],
313
+ placeholders['hostname'],
314
+ )
315
+ rescue => e
316
+ log.warn "record_reformer: failed to expand `#{str}`", :error_class => e.class, :error => e.message
317
+ log.warn_backtrace
318
+ nil
319
+ end
320
+
321
+ class CleanroomExpander
322
+ def expand(__str_to_eval__, tag, time, record, tag_parts, tag_prefix, tag_suffix, hostname)
323
+ tags = tag_parts # for old version compatibility
324
+ Thread.current[:record_reformer_record] = record # for old version compatibility
325
+ instance_eval(__str_to_eval__)
326
+ end
327
+
328
+ # for old version compatibility
329
+ def method_missing(name)
330
+ key = name.to_s
331
+ record = Thread.current[:record_reformer_record]
332
+ if record.has_key?(key)
333
+ record[key]
334
+ else
335
+ raise NameError, "undefined local variable or method `#{key}'"
336
+ end
337
+ end
338
+
339
+ (Object.instance_methods).each do |m|
340
+ 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/
341
+ end
342
+ end
343
+ end
344
+ end
345
+ end
@@ -0,0 +1,32 @@
1
+ require_relative 'core'
2
+
3
+ module Fluent
4
+ class RecordReformerOutput < Output
5
+ Fluent::Plugin.register_output('record_reformer', self)
6
+
7
+ include ::Fluent::RecordReformerOutputCore
8
+
9
+ def initialize
10
+ super
11
+ end
12
+
13
+ # To support log_level option implemented by Fluentd v0.10.43
14
+ unless method_defined?(:log)
15
+ define_method("log") { $log }
16
+ end
17
+
18
+ # Define `router` method of v0.12 to support v0.10 or earlier
19
+ unless method_defined?(:router)
20
+ define_method("router") { Fluent::Engine }
21
+ end
22
+
23
+ def configure(conf)
24
+ super
25
+ end
26
+
27
+ def emit(tag, es, chain)
28
+ process(tag, es)
29
+ chain.next
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,22 @@
1
+ require_relative 'core'
2
+
3
+ module Fluent
4
+ class Plugin::RecordReformerOutput < Plugin::Output
5
+ Fluent::Plugin.register_output('record_reformer', self)
6
+
7
+ helpers :event_emitter
8
+ include ::Fluent::RecordReformerOutputCore
9
+
10
+ def initialize
11
+ super
12
+ end
13
+
14
+ def configure(conf)
15
+ super
16
+ end
17
+
18
+ def process(tag, es)
19
+ super
20
+ end
21
+ end
22
+ end
@@ -28,3 +28,48 @@ module Fluent
28
28
  end
29
29
  end
30
30
  end
31
+
32
+ require 'fluent/version'
33
+ major, minor, patch = Fluent::VERSION.split('.').map(&:to_i)
34
+ if major > 0 || (major == 0 && minor >= 14)
35
+ require 'fluent/test/driver/output'
36
+ require 'fluent/test/helpers'
37
+ include Fluent::Test::Helpers
38
+
39
+ # I do not want to use Fluent::Test::OutputTestDriver, but
40
+ # want to have a test driver whose interface is compatible with of v0.12
41
+ class RecordReformerOutputTestDriver < Fluent::Test::Driver::Output
42
+ def initialize(klass, tag)
43
+ super(klass)
44
+ @tag = tag
45
+ end
46
+
47
+ def configure(conf, use_v1)
48
+ super(conf, syntax: use_v1 ? :v1 : :v0)
49
+ end
50
+
51
+ def run(&block)
52
+ super(default_tag: @tag, &block)
53
+ end
54
+
55
+ def emit(record, time)
56
+ feed(time, record)
57
+ end
58
+
59
+ def emits
60
+ events
61
+ end
62
+ end
63
+
64
+ def create_driver(conf, use_v1)
65
+ RecordReformerOutputTestDriver.new(Fluent::Plugin::RecordReformerOutput, @tag).configure(conf, use_v1)
66
+ end
67
+ else
68
+ def event_time(str)
69
+ Time.parse(str)
70
+ end
71
+
72
+ def create_driver(conf, use_v1)
73
+ Fluent::Test::OutputTestDriver.new(Fluent::RecordReformerOutput, @tag).configure(conf, use_v1)
74
+ end
75
+ end
@@ -5,22 +5,6 @@ require 'fluent/plugin/out_record_reformer'
5
5
  Fluent::Test.setup
6
6
 
7
7
  class RecordReformerOutputTest < Test::Unit::TestCase
8
- setup do
9
- @hostname = Socket.gethostname.chomp
10
- @tag = 'test.tag'
11
- @tag_parts = @tag.split('.')
12
- @time = Time.local(1,2,3,4,5,2010,nil,nil,nil,nil)
13
- Timecop.freeze(@time)
14
- end
15
-
16
- teardown do
17
- Timecop.return
18
- end
19
-
20
- def create_driver(conf, use_v1)
21
- Fluent::Test::OutputTestDriver.new(Fluent::RecordReformerOutput, @tag).configure(conf, use_v1)
22
- end
23
-
24
8
  def emit(config, use_v1, msgs = [''])
25
9
  d = create_driver(config, use_v1)
26
10
  d.run do
@@ -37,6 +21,18 @@ class RecordReformerOutputTest < Test::Unit::TestCase
37
21
  d.emits
38
22
  end
39
23
 
24
+ setup do
25
+ @hostname = Socket.gethostname.chomp
26
+ @tag = 'test.tag'
27
+ @tag_parts = @tag.split('.')
28
+ @time = event_time("2010-05-04 03:02:01")
29
+ Timecop.freeze(@time)
30
+ end
31
+
32
+ teardown do
33
+ Timecop.return
34
+ end
35
+
40
36
  CONFIG = %[
41
37
  tag reformed.${tag}
42
38
 
@@ -77,7 +73,7 @@ class RecordReformerOutputTest < Test::Unit::TestCase
77
73
  assert_equal('bar', record['eventType0'])
78
74
  assert_equal(@hostname, record['hostname'])
79
75
  assert_equal(@tag, record['input_tag'])
80
- assert_equal(@time.to_s, record['time'])
76
+ assert_equal(Time.at(@time).localtime.to_s, record['time'])
81
77
  assert_equal("#{@hostname} #{@tag_parts[-1]} #{msgs[i]}", record['message'])
82
78
  end
83
79
  end
@@ -109,7 +105,7 @@ class RecordReformerOutputTest < Test::Unit::TestCase
109
105
  assert_equal('bar', record['eventType0'])
110
106
  assert_equal(@hostname, record['hostname'])
111
107
  assert_equal(@tag, record['tag'])
112
- assert_equal(@time.to_s, record['time'])
108
+ assert_equal(Time.at(@time).localtime.to_s, record['time'])
113
109
  assert_equal("#{@hostname} #{@tag_parts[-1]} #{msgs[i]}", record['message'])
114
110
  end
115
111
  end
@@ -122,7 +118,7 @@ class RecordReformerOutputTest < Test::Unit::TestCase
122
118
  assert_not_include(record, 'eventType0')
123
119
  assert_equal(@hostname, record['hostname'])
124
120
  assert_equal(@tag, record['input_tag'])
125
- assert_equal(@time.to_s, record['time'])
121
+ assert_equal(Time.at(@time).localtime.to_s, record['time'])
126
122
  assert_not_include(record, 'message')
127
123
  end
128
124
  end
@@ -136,13 +132,13 @@ class RecordReformerOutputTest < Test::Unit::TestCase
136
132
  assert_not_include(record, 'eventType0')
137
133
  assert_equal(@hostname, record['hostname'])
138
134
  assert_equal(@tag, record['input_tag'])
139
- assert_equal(@time.to_s, record['time'])
135
+ assert_equal(Time.at(@time).localtime.to_s, record['time'])
140
136
  assert_equal("#{@hostname} #{@tag_parts[-1]} #{msgs[i]}", record['message'])
141
137
  end
142
138
  end
143
139
 
144
140
  test 'renew_time_key' do
145
- 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) ]
141
+ times = [ Time.at(event_time("2010-05-04 03:02:02")), Time.at(event_time("2010-05-04 03:02:03")) ]
146
142
  config = <<EOC
147
143
  tag reformed.${tag}
148
144
  enable_ruby true
@@ -170,7 +166,7 @@ EOC
170
166
  event_time_key ${Time.parse(record["message"]).to_i}
171
167
  </record>
172
168
  EOC
173
- 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) ]
169
+ times = [ Time.at(event_time("2010-05-04 03:02:02")), Time.at(event_time("2010-05-04 03:02:03")) ]
174
170
  msgs = times.map{|t| t.to_s }
175
171
  emits = emit(config, use_v1, msgs)
176
172
  emits.each_with_index do |(tag, time, record), i|
@@ -295,7 +291,7 @@ EOC
295
291
  ]
296
292
  emits = emit(config, use_v1)
297
293
  emits.each do |(tag, time, record)|
298
- assert_equal(@time.to_s, record['message'])
294
+ assert_equal(Time.at(time).localtime.to_s, record['message'])
299
295
  end
300
296
  end
301
297
 
@@ -335,7 +331,7 @@ EOC
335
331
  assert_not_equal('tag', record['new_tag'])
336
332
  assert_equal(@tag, record['new_tag'])
337
333
  assert_not_equal('time', record['new_time'])
338
- assert_equal(@time.to_s, record['new_time'])
334
+ assert_equal(Time.at(@time).localtime.to_s, record['new_time'])
339
335
  assert_equal('tag', record['new_record_tag'])
340
336
  assert_equal('time', record['new_record_time'])
341
337
  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.8.3
4
+ version: 0.9.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: 2017-01-26 00:00:00.000000000 Z
11
+ date: 2017-02-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fluentd
@@ -127,6 +127,9 @@ files:
127
127
  - example.conf
128
128
  - fluent-plugin-record-reformer.gemspec
129
129
  - lib/fluent/plugin/out_record_reformer.rb
130
+ - lib/fluent/plugin/out_record_reformer/core.rb
131
+ - lib/fluent/plugin/out_record_reformer/v12.rb
132
+ - lib/fluent/plugin/out_record_reformer/v14.rb
130
133
  - test/bench_out_record_reformer.rb
131
134
  - test/helper.rb
132
135
  - test/test_out_record_reformer.rb
@@ -150,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
150
153
  version: '0'
151
154
  requirements: []
152
155
  rubyforge_project:
153
- rubygems_version: 2.5.1
156
+ rubygems_version: 2.6.8
154
157
  signing_key:
155
158
  specification_version: 4
156
159
  summary: Fluentd plugin to add or replace fields of a event record