fluent-plugin-record-reformer 0.8.3 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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