fluent-plugin-record-modifier 1.1.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/ChangeLog +6 -1
- data/README.md +9 -6
- data/VERSION +1 -1
- data/fluent-plugin-record-modifier.gemspec +1 -1
- data/lib/fluent/plugin/filter_record_modifier.rb +0 -15
- data/lib/fluent/plugin/out_record_modifier.rb +156 -42
- data/test/test_out_record_modifier.rb +31 -19
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 50fbb4b964b12f2b00fcb947614ba55e7d5df199
|
4
|
+
data.tar.gz: 1184b66de40e482d7cdc882b903b60656b8f323d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ca78925ef98fca002b037e3e4e250563eec3d1782e998ae3e8594ca72c363d1c510909a4ad44d7b56c3d6b1f274ef43c0438e515f2c984fcd9994ab9f6faa458
|
7
|
+
data.tar.gz: 03bc94a47d90ad10e3fa96931d9e2d315e3a3fdee6ccc901a7b233f5c927e7adf8d667580a333a9ec749adc1eb41885fb0b2529a0fd868a5e7663984ba070384
|
data/ChangeLog
CHANGED
@@ -1,4 +1,9 @@
|
|
1
|
-
Release
|
1
|
+
Release 2.0.0 - 2019/01/23
|
2
|
+
|
3
|
+
* Update output plugin to follow filter plugin
|
4
|
+
* Remove backward compatibility check for 0.5.0 or earlier version
|
5
|
+
|
6
|
+
Release 1.1.0 - 2018/06/01
|
2
7
|
|
3
8
|
* Add `replace` config sections
|
4
9
|
https://github.com/repeatedly/fluent-plugin-record-modifier/pull/33
|
data/README.md
CHANGED
@@ -9,6 +9,7 @@ In this case, you can use *record_modifier* to add "hostname" field to event rec
|
|
9
9
|
|
10
10
|
| fluent-plugin-record-modifier | fluentd | ruby |
|
11
11
|
|--------------------------------|---------|------|
|
12
|
+
| >= 2.0.0 | >= v1.0.0 | >= 2.1 |
|
12
13
|
| >= 1.0.0 | >= v0.14.0 | >= 2.1 |
|
13
14
|
| < 1.0.0 | >= v0.12.0 | >= 1.9 |
|
14
15
|
|
@@ -16,7 +17,7 @@ In this case, you can use *record_modifier* to add "hostname" field to event rec
|
|
16
17
|
|
17
18
|
Use RubyGems:
|
18
19
|
|
19
|
-
gem install fluent-plugin-record-modifier --no-document
|
20
|
+
fluent-gem install fluent-plugin-record-modifier --no-document
|
20
21
|
|
21
22
|
## Configuration
|
22
23
|
|
@@ -206,14 +207,16 @@ If you need own complex logic in filter, writing filter plugin is better. But if
|
|
206
207
|
|
207
208
|
### record_modifier output
|
208
209
|
|
209
|
-
|
210
|
+
Output plugin version of `record_modifier` filter. If you want to process events and change tag at the same time, this plugin is useful.
|
210
211
|
|
211
212
|
<match pattern>
|
212
|
-
type record_modifier
|
213
|
-
tag foo
|
213
|
+
@type record_modifier
|
214
|
+
tag foo.${record["field1"]}
|
214
215
|
|
215
|
-
|
216
|
-
|
216
|
+
<record>
|
217
|
+
gen_host "#{Socket.gethostname}"
|
218
|
+
foo bar
|
219
|
+
</record>
|
217
220
|
</match>
|
218
221
|
|
219
222
|
## Copyright
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
2.0.0
|
@@ -16,7 +16,7 @@ Gem::Specification.new do |gem|
|
|
16
16
|
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
17
|
gem.require_paths = ['lib']
|
18
18
|
|
19
|
-
gem.add_dependency "fluentd", [">=
|
19
|
+
gem.add_dependency "fluentd", [">= 1.0", "< 2"]
|
20
20
|
gem.add_development_dependency "rake", ">= 0.9.2"
|
21
21
|
gem.add_development_dependency("test-unit", ["~> 3.1.4"])
|
22
22
|
end
|
@@ -50,10 +50,6 @@ DESC
|
|
50
50
|
def configure(conf)
|
51
51
|
super
|
52
52
|
|
53
|
-
if conf.has_key?('include_tag_key')
|
54
|
-
raise ConfigError, "include_tag_key and tag_key parameters are removed. Use 'tag ${tag}' in <record> section"
|
55
|
-
end
|
56
|
-
|
57
53
|
@map = {}
|
58
54
|
@to_enc = nil
|
59
55
|
if @char_encoding
|
@@ -75,7 +71,6 @@ DESC
|
|
75
71
|
@has_tag_parts = false
|
76
72
|
conf.elements.select { |element| element.name == 'record' }.each do |element|
|
77
73
|
element.each_pair do |k, v|
|
78
|
-
check_config_placeholders(k, v)
|
79
74
|
element.has_key?(k) # to suppress unread configuration warning
|
80
75
|
@has_tag_parts = true if v.include?('tag_parts')
|
81
76
|
@map[k] = DynamicExpander.new(k, v, @prepare_value)
|
@@ -165,16 +160,6 @@ DESC
|
|
165
160
|
end
|
166
161
|
end
|
167
162
|
|
168
|
-
HOSTNAME_PLACEHOLDERS = %W(__HOSTNAME__ ${hostname})
|
169
|
-
|
170
|
-
def check_config_placeholders(k, v)
|
171
|
-
HOSTNAME_PLACEHOLDERS.each { |ph|
|
172
|
-
if v.include?(ph)
|
173
|
-
raise ConfigError, %!#{ph} placeholder in #{k} is removed. Use "\#{Socket.gethostname}" instead.!
|
174
|
-
end
|
175
|
-
}
|
176
|
-
end
|
177
|
-
|
178
163
|
class DynamicExpander
|
179
164
|
def initialize(param_key, param_value, prepare_value)
|
180
165
|
if param_value.include?('${')
|
@@ -4,10 +4,16 @@ module Fluent
|
|
4
4
|
class Plugin::RecordModifierOutput < Plugin::Output
|
5
5
|
Fluent::Plugin.register_output('record_modifier', self)
|
6
6
|
|
7
|
-
helpers :event_emitter
|
7
|
+
helpers :event_emitter
|
8
8
|
|
9
9
|
config_param :tag, :string,
|
10
10
|
desc: "The output record tag name."
|
11
|
+
|
12
|
+
config_param :prepare_value, :string, default: nil,
|
13
|
+
desc: <<-DESC
|
14
|
+
Prepare values for filtering in configure phase. Prepared values can be used in <record>.
|
15
|
+
You can write any ruby code.
|
16
|
+
DESC
|
11
17
|
config_param :char_encoding, :string, default: nil,
|
12
18
|
desc: <<-DESC
|
13
19
|
Fluentd including some plugins treats the logs as a BINARY by default to forward.
|
@@ -16,7 +22,6 @@ e.g. handling char encoding correctly.
|
|
16
22
|
In more detail, please refer this section:
|
17
23
|
https://github.com/repeatedly/fluent-plugin-record-modifier#char_encoding.
|
18
24
|
DESC
|
19
|
-
|
20
25
|
config_param :remove_keys, :string, default: nil,
|
21
26
|
desc: <<-DESC
|
22
27
|
The logs include needless record keys in some cases.
|
@@ -31,21 +36,26 @@ Modified events will have only specified keys (if exist in original events).
|
|
31
36
|
This option is exclusive with `remove_keys`.
|
32
37
|
DESC
|
33
38
|
|
34
|
-
|
39
|
+
config_section :replace, param_name: :replaces, multi: true do
|
40
|
+
desc "The field name to which the regular expression is applied"
|
41
|
+
config_param :key, :string
|
42
|
+
desc "The regular expression"
|
43
|
+
config_param :expression do |value|
|
44
|
+
if value.start_with?("/") && value.end_with?("/")
|
45
|
+
Regexp.compile(value[1..-2])
|
46
|
+
else
|
47
|
+
$log.warn "You should use \"pattern /#{value}/\" instead of \"pattern #{value}\""
|
48
|
+
Regexp.compile(value)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
desc "The replacement string"
|
52
|
+
config_param :replace, :string
|
53
|
+
end
|
35
54
|
|
36
55
|
def configure(conf)
|
37
|
-
compat_parameters_convert(conf, :buffer, :inject)
|
38
56
|
super
|
39
57
|
|
40
58
|
@map = {}
|
41
|
-
conf.each_pair { |k, v|
|
42
|
-
unless BUILTIN_CONFIGURATIONS.include?(k)
|
43
|
-
check_config_placeholders(k, v)
|
44
|
-
conf.has_key?(k)
|
45
|
-
@map[k] = v
|
46
|
-
end
|
47
|
-
}
|
48
|
-
|
49
59
|
@to_enc = nil
|
50
60
|
if @char_encoding
|
51
61
|
from, to = @char_encoding.split(':', 2)
|
@@ -63,6 +73,15 @@ DESC
|
|
63
73
|
end
|
64
74
|
end
|
65
75
|
|
76
|
+
@has_tag_parts = false
|
77
|
+
conf.elements.select { |element| element.name == 'record' }.each do |element|
|
78
|
+
element.each_pair do |k, v|
|
79
|
+
element.has_key?(k) # to suppress unread configuration warning
|
80
|
+
@has_tag_parts = true if v.include?('tag_parts')
|
81
|
+
@map[k] = DynamicExpander.new(k, v, @prepare_value)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
66
85
|
if @remove_keys and @whitelist_keys
|
67
86
|
raise Fluent::ConfigError, "remove_keys and whitelist_keys are exclusive with each other."
|
68
87
|
elsif @remove_keys
|
@@ -70,32 +89,42 @@ DESC
|
|
70
89
|
elsif @whitelist_keys
|
71
90
|
@whitelist_keys = @whitelist_keys.split(',').map(&:strip)
|
72
91
|
end
|
92
|
+
|
93
|
+
@has_tag_parts = true if @tag.include?('tag_parts')
|
94
|
+
@tag_ex = DynamicExpander.new('tag', @tag, @prepare_value)
|
95
|
+
|
96
|
+
# Collect DynamicExpander related garbage instructions
|
97
|
+
GC.start
|
73
98
|
end
|
74
99
|
|
75
100
|
def process(tag, es)
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
101
|
+
tag_parts = @has_tag_parts ? tag.split('.') : nil
|
102
|
+
if @tag_ex.param_value.nil?
|
103
|
+
result = {}
|
104
|
+
es.each { |time, record|
|
105
|
+
new_record = modify_record(tag, time, record, tag_parts)
|
106
|
+
new_tag = @tag_ex.expand(tag, time, new_record, tag_parts)
|
107
|
+
result[new_tag] ||= MultiEventStream.new
|
108
|
+
result[new_tag].add(time, new_record)
|
109
|
+
}
|
110
|
+
result.each { |tag, stream|
|
111
|
+
router.emit_stream(tag, stream)
|
112
|
+
}
|
113
|
+
else
|
114
|
+
stream = MultiEventStream.new
|
115
|
+
es.each { |time, record|
|
116
|
+
new_record = modify_record(tag, time, record, tag_parts)
|
117
|
+
stream.add(time, new_record)
|
118
|
+
}
|
119
|
+
router.emit_stream(@tag, stream)
|
120
|
+
end
|
82
121
|
end
|
83
122
|
|
84
123
|
private
|
85
124
|
|
86
|
-
|
87
|
-
|
88
|
-
def check_config_placeholders(k, v)
|
89
|
-
HOSTNAME_PLACEHOLDERS.each { |ph|
|
90
|
-
if v.include?(ph)
|
91
|
-
raise ConfigError, %!#{ph} placeholder in #{k} is removed. Use "\#{Socket.gethostname}" instead.!
|
92
|
-
end
|
93
|
-
}
|
94
|
-
end
|
95
|
-
|
96
|
-
def modify_record(record)
|
125
|
+
def modify_record(tag, time, record, tag_parts)
|
97
126
|
@map.each_pair { |k, v|
|
98
|
-
record[k] = v
|
127
|
+
record[k] = v.expand(tag, time, record, tag_parts)
|
99
128
|
}
|
100
129
|
|
101
130
|
if @remove_keys
|
@@ -110,25 +139,110 @@ DESC
|
|
110
139
|
record = modified
|
111
140
|
end
|
112
141
|
|
142
|
+
unless @replaces.empty?
|
143
|
+
@replaces.each { |replace|
|
144
|
+
target_key = replace.key
|
145
|
+
if record.include?(target_key) && replace.expression.match(record[target_key])
|
146
|
+
record[target_key] = record[target_key].gsub(replace.expression, replace.replace)
|
147
|
+
end
|
148
|
+
}
|
149
|
+
end
|
150
|
+
|
113
151
|
record = change_encoding(record) if @char_encoding
|
114
152
|
record
|
115
153
|
end
|
116
154
|
|
117
|
-
def set_encoding(
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
155
|
+
def set_encoding(value)
|
156
|
+
if value.is_a?(String)
|
157
|
+
value.force_encoding(@from_enc)
|
158
|
+
elsif value.is_a?(Hash)
|
159
|
+
value.each_pair { |k, v|
|
160
|
+
if v.frozen? && v.is_a?(String)
|
161
|
+
value[k] = set_encoding(v.dup)
|
162
|
+
else
|
163
|
+
set_encoding(v)
|
164
|
+
end
|
165
|
+
}
|
166
|
+
elsif value.is_a?(Array)
|
167
|
+
value.each { |v| set_encoding(v) }
|
168
|
+
else
|
169
|
+
value
|
170
|
+
end
|
123
171
|
end
|
124
172
|
|
125
|
-
def convert_encoding(
|
126
|
-
|
127
|
-
if
|
128
|
-
|
129
|
-
|
173
|
+
def convert_encoding(value)
|
174
|
+
if value.is_a?(String)
|
175
|
+
value.force_encoding(@from_enc) if value.encoding == Encoding::BINARY
|
176
|
+
value.encode!(@to_enc, @from_enc, :invalid => :replace, :undef => :replace)
|
177
|
+
elsif value.is_a?(Hash)
|
178
|
+
value.each_pair { |k, v|
|
179
|
+
if v.frozen? && v.is_a?(String)
|
180
|
+
value[k] = convert_encoding(v.dup)
|
181
|
+
else
|
182
|
+
convert_encoding(v)
|
183
|
+
end
|
184
|
+
}
|
185
|
+
elsif value.is_a?(Array)
|
186
|
+
value.each { |v| convert_encoding(v) }
|
187
|
+
else
|
188
|
+
value
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
class DynamicExpander
|
193
|
+
attr_reader :param_value
|
194
|
+
|
195
|
+
def initialize(param_key, param_value, prepare_value)
|
196
|
+
if param_value.include?('${')
|
197
|
+
__str_eval_code__ = parse_parameter(param_value)
|
198
|
+
|
199
|
+
# Use class_eval with string instead of define_method for performance.
|
200
|
+
# It can't share instructions but this is 2x+ faster than define_method in filter case.
|
201
|
+
# Refer: http://tenderlovemaking.com/2013/03/03/dynamic_method_definitions.html
|
202
|
+
(class << self; self; end).class_eval <<-EORUBY, __FILE__, __LINE__ + 1
|
203
|
+
def expand(tag, time, record, tag_parts)
|
204
|
+
#{__str_eval_code__}
|
205
|
+
end
|
206
|
+
EORUBY
|
207
|
+
else
|
208
|
+
@param_value = param_value
|
130
209
|
end
|
131
|
-
|
210
|
+
|
211
|
+
begin
|
212
|
+
eval prepare_value if prepare_value
|
213
|
+
rescue SyntaxError
|
214
|
+
raise ConfigError, "Pass invalid syntax parameter : key = prepare_value, value = #{prepare_value}"
|
215
|
+
end
|
216
|
+
|
217
|
+
begin
|
218
|
+
# check eval genarates wrong code or not
|
219
|
+
expand(nil, nil, nil, nil)
|
220
|
+
rescue SyntaxError
|
221
|
+
raise ConfigError, "Pass invalid syntax parameter : key = #{param_key}, value = #{param_value}"
|
222
|
+
rescue
|
223
|
+
# Ignore other runtime errors
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
# Default implementation for fixed value. This is overwritten when parameter contains '${xxx}' placeholder
|
228
|
+
def expand(tag, time, record, tag_parts)
|
229
|
+
@param_value
|
230
|
+
end
|
231
|
+
|
232
|
+
private
|
233
|
+
|
234
|
+
def parse_parameter(value)
|
235
|
+
num_placeholders = value.scan('${').size
|
236
|
+
if num_placeholders == 1
|
237
|
+
if value.start_with?('${') && value.end_with?('}')
|
238
|
+
return value[2..-2]
|
239
|
+
else
|
240
|
+
"\"#{value.gsub('${', '#{')}\""
|
241
|
+
end
|
242
|
+
else
|
243
|
+
"\"#{value.gsub('${', '#{')}\""
|
244
|
+
end
|
245
|
+
end
|
132
246
|
end
|
133
247
|
end
|
134
248
|
end
|
@@ -8,13 +8,11 @@ class RecordModifierOutputTest < Test::Unit::TestCase
|
|
8
8
|
end
|
9
9
|
|
10
10
|
CONFIG = %q!
|
11
|
-
type record_modifier
|
12
11
|
tag foo.filtered
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
tag_key included_tag
|
12
|
+
<record>
|
13
|
+
gen_host "#{Socket.gethostname}"
|
14
|
+
foo bar
|
15
|
+
</record>
|
18
16
|
remove_keys hoge
|
19
17
|
!
|
20
18
|
|
@@ -31,8 +29,8 @@ class RecordModifierOutputTest < Test::Unit::TestCase
|
|
31
29
|
d = create_driver
|
32
30
|
map = d.instance.instance_variable_get(:@map)
|
33
31
|
|
34
|
-
assert_equal get_hostname, map['gen_host']
|
35
|
-
assert_equal 'bar', map['foo']
|
32
|
+
assert_equal get_hostname, map['gen_host'].param_value
|
33
|
+
assert_equal 'bar', map['foo'].param_value
|
36
34
|
end
|
37
35
|
|
38
36
|
def test_format
|
@@ -43,17 +41,39 @@ class RecordModifierOutputTest < Test::Unit::TestCase
|
|
43
41
|
d.feed({"a" => 2})
|
44
42
|
end
|
45
43
|
|
46
|
-
mapped = {'gen_host' => get_hostname, 'foo' => 'bar'
|
44
|
+
mapped = {'gen_host' => get_hostname, 'foo' => 'bar'}
|
47
45
|
assert_equal [
|
48
46
|
{"a" => 1}.merge(mapped),
|
49
47
|
{"a" => 2}.merge(mapped),
|
50
48
|
], d.events.map { |e| e.last }
|
51
49
|
end
|
52
50
|
|
53
|
-
def
|
51
|
+
def test_dynamic_tag_with_tag
|
54
52
|
d = create_driver %[
|
55
|
-
|
53
|
+
tag foo.${tag}
|
54
|
+
]
|
55
|
+
|
56
|
+
d.run(default_tag: 'test_tag') do
|
57
|
+
d.feed({"k" => 'v'})
|
58
|
+
end
|
59
|
+
|
60
|
+
assert_equal 'foo.test_tag', d.events.first.first
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_dynamic_tag_with_record_field
|
64
|
+
d = create_driver %[
|
65
|
+
tag foo.${record["k"]}
|
66
|
+
]
|
56
67
|
|
68
|
+
d.run(default_tag: 'test_tag') do
|
69
|
+
d.feed({"k" => 'v'})
|
70
|
+
end
|
71
|
+
|
72
|
+
assert_equal 'foo.v', d.events.first.first
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_set_char_encoding
|
76
|
+
d = create_driver %[
|
57
77
|
tag foo.filtered
|
58
78
|
char_encoding utf-8
|
59
79
|
]
|
@@ -67,8 +87,6 @@ class RecordModifierOutputTest < Test::Unit::TestCase
|
|
67
87
|
|
68
88
|
def test_convert_char_encoding
|
69
89
|
d = create_driver %[
|
70
|
-
type record_modifier
|
71
|
-
|
72
90
|
tag foo.filtered
|
73
91
|
char_encoding utf-8:cp932
|
74
92
|
]
|
@@ -82,8 +100,6 @@ class RecordModifierOutputTest < Test::Unit::TestCase
|
|
82
100
|
|
83
101
|
def test_remove_one_key
|
84
102
|
d = create_driver %[
|
85
|
-
type record_modifier
|
86
|
-
|
87
103
|
tag foo.filtered
|
88
104
|
remove_keys k1
|
89
105
|
]
|
@@ -97,8 +113,6 @@ class RecordModifierOutputTest < Test::Unit::TestCase
|
|
97
113
|
|
98
114
|
def test_remove_multiple_keys
|
99
115
|
d = create_driver %[
|
100
|
-
type record_modifier
|
101
|
-
|
102
116
|
tag foo.filtered
|
103
117
|
remove_keys k1, k2, k3
|
104
118
|
]
|
@@ -112,8 +126,6 @@ class RecordModifierOutputTest < Test::Unit::TestCase
|
|
112
126
|
|
113
127
|
def test_remove_non_whitelist_keys
|
114
128
|
d = create_driver %[
|
115
|
-
type record_modifier
|
116
|
-
|
117
129
|
tag foo.filtered
|
118
130
|
whitelist_keys k1, k2, k3
|
119
131
|
]
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fluent-plugin-record-modifier
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Masahiro Nakagawa
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-01-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fluentd
|
@@ -16,7 +16,7 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: '1.0'
|
20
20
|
- - "<"
|
21
21
|
- !ruby/object:Gem::Version
|
22
22
|
version: '2'
|
@@ -26,7 +26,7 @@ dependencies:
|
|
26
26
|
requirements:
|
27
27
|
- - ">="
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version:
|
29
|
+
version: '1.0'
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
32
|
version: '2'
|
@@ -95,7 +95,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
95
95
|
version: '0'
|
96
96
|
requirements: []
|
97
97
|
rubyforge_project:
|
98
|
-
rubygems_version: 2.
|
98
|
+
rubygems_version: 2.6.14.1
|
99
99
|
signing_key:
|
100
100
|
specification_version: 4
|
101
101
|
summary: Filter plugin for modifying event record
|