fluent-plugin-record-modifier 1.1.0 → 2.0.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 +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
|