fluent-plugin-mutate_filter 0.3.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.rbenv-gemsets +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +1 -2
- data/LICENSE +202 -0
- data/README.md +121 -20
- data/Rakefile +13 -2
- data/fluent-plugin-mutate_filter.gemspec +25 -11
- data/lib/fluent/plugin/filter_mutate.rb +416 -441
- data/lib/fluent/plugin/mixin/mutate_event.rb +82 -0
- data/test/helper.rb +8 -0
- data/test/plugin/test_filter_mutate.rb +18 -0
- metadata +47 -22
- data/CHANGELOG.md +0 -29
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/lib/fluent/plugin_mixin/mutate_event.rb +0 -83
@@ -1,514 +1,489 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require "fluent/config/types"
|
2
|
+
require "fluent/plugin/filter"
|
3
|
+
require "fluent/plugin/mixin/mutate_event"
|
4
4
|
|
5
5
|
module Fluent
|
6
|
-
|
7
|
-
Fluent::Plugin
|
8
|
-
|
9
|
-
# Treat periods as nested field names
|
10
|
-
config_param :expand_nesting, :bool, default: true
|
11
|
-
|
12
|
-
# Remove any empty hashes or arrays
|
13
|
-
config_param :prune_empty, :bool, default: true
|
14
|
-
|
15
|
-
# Rename one or more fields
|
16
|
-
# @example
|
17
|
-
# rename {
|
18
|
-
# "timestamp": "@timestamp"
|
19
|
-
# }
|
20
|
-
config_param :rename, :hash, default: Hash.new
|
21
|
-
|
22
|
-
# Update an existing field with a new value.
|
23
|
-
# - If the field does not exist then no action will be taken.
|
24
|
-
# - If the new value contains a placeholder %{}, then the value will be
|
25
|
-
# expanded to the related event record field.
|
26
|
-
# - If the new value contains a placeholder %e{}, then the value will be
|
27
|
-
# expanded to the relevant environment variable.
|
28
|
-
# @example
|
29
|
-
# update {
|
30
|
-
# "message": "%e{HOSTNAME}: new message"
|
31
|
-
# }
|
32
|
-
config_param :update, :hash, default: Hash.new
|
33
|
-
|
34
|
-
# Remove an existing field
|
35
|
-
# @example
|
36
|
-
# remove [
|
37
|
-
# "dummy1", "placeholder1"
|
38
|
-
# ]
|
39
|
-
config_param :remove, :array, default: Array.new
|
40
|
-
|
41
|
-
# Replace a field with a new value
|
42
|
-
# - If the field does not exist, then it will be created.
|
43
|
-
# - If the new value contains a placeholder %{}, then the value will be
|
44
|
-
# expanded to the related event record field.
|
45
|
-
# - If the new value contains a placeholder %e{}, then the value will be
|
46
|
-
# expanded to the relevant environment variable.
|
47
|
-
# @example
|
48
|
-
# replace {
|
49
|
-
# "new_message": "a new field"
|
50
|
-
# }
|
51
|
-
config_param :replace, :hash, default: Hash.new
|
52
|
-
|
53
|
-
# Convert a field's value to a different type, like turning a string to an
|
54
|
-
# integer.
|
55
|
-
# - If the field value is an array, all members will be converted.
|
56
|
-
# - If the field value is a hash, then no action will be taken.
|
57
|
-
# - Valid conversion types are integer, float, string, boolean
|
58
|
-
# @example
|
59
|
-
# convert {
|
60
|
-
# "id": "integer",
|
61
|
-
# "message": "string"
|
62
|
-
# }
|
63
|
-
config_param :convert, :hash, default: Hash.new
|
64
|
-
|
65
|
-
# Convert a string field by applying a regular expression and replacement.
|
66
|
-
# - If the field is not a string, then no action will be taken.
|
67
|
-
#
|
68
|
-
# The configuration takes an array consisting of 3 elements per field/sub.
|
69
|
-
#
|
70
|
-
# @example
|
71
|
-
# gsub [
|
72
|
-
# "fieldname", "/", "_",
|
73
|
-
# "fieldname2", "[\\?#-]", "."
|
74
|
-
# ]
|
75
|
-
config_param :gsub, :array, default: Array.new
|
76
|
-
|
77
|
-
# Convert a string to it's uppercase equivalent
|
78
|
-
# @example
|
79
|
-
# uppercase [
|
80
|
-
# "field1", "field2"
|
81
|
-
# ]
|
82
|
-
config_param :uppercase, :array, default: Array.new
|
83
|
-
|
84
|
-
# Convert a string to it's lowercase equivalent
|
85
|
-
# @example
|
86
|
-
# lowercase [
|
87
|
-
# "field1", "field2"
|
88
|
-
# ]
|
89
|
-
config_param :lowercase, :array, default: Array.new
|
90
|
-
|
91
|
-
# Strip whitespace from field.
|
92
|
-
# @example
|
93
|
-
# strip [
|
94
|
-
# "field1"
|
95
|
-
# ]
|
96
|
-
config_param :strip, :array, default: Array.new
|
97
|
-
|
98
|
-
# Split a field to an array using a separator character
|
99
|
-
# @example
|
100
|
-
# split {
|
101
|
-
# "field1": ","
|
102
|
-
# }
|
103
|
-
config_param :split, :hash, default: Hash.new
|
104
|
-
|
105
|
-
# Join an array using a separator character
|
106
|
-
# @example
|
107
|
-
# join {
|
108
|
-
# "field1": " "
|
109
|
-
# }
|
110
|
-
config_param :join, :hash, default: Hash.new
|
111
|
-
|
112
|
-
# Merge two fields of arrays or hashes
|
113
|
-
# @example
|
114
|
-
# merge {
|
115
|
-
# "dest_field": "added_field"
|
116
|
-
# }
|
117
|
-
config_param :merge, :hash, default: Hash.new
|
118
|
-
|
119
|
-
# List of all possible mutate actions, in the order that we will apply
|
120
|
-
# them. As it stands, this is the order in which Logstash would apply them.
|
121
|
-
MUTATE_ACTIONS = %w(
|
122
|
-
rename
|
123
|
-
update
|
124
|
-
replace
|
125
|
-
convert
|
126
|
-
gsub
|
127
|
-
uppercase
|
128
|
-
lowercase
|
129
|
-
strip
|
130
|
-
remove
|
131
|
-
split
|
132
|
-
join
|
133
|
-
merge
|
134
|
-
)
|
135
|
-
|
136
|
-
# Convert valid types
|
137
|
-
VALID_CONVERSIONS = %w(string integer float boolean datetime)
|
138
|
-
|
139
|
-
# Convert helper method prefix
|
140
|
-
CONVERT_PREFIX = "convert_".freeze
|
141
|
-
|
142
|
-
# Convert boolean regex
|
143
|
-
TRUE_REGEX = (/^(true|t|yes|y|1)$/i).freeze
|
144
|
-
FALSE_REGEX = (/^(false|f|no|n|0)$/i).freeze
|
145
|
-
|
146
|
-
# Placeholder regex
|
147
|
-
ENVIRONMENT_TAG_REGEXP = /%e\{[^}]+\}/
|
148
|
-
|
149
|
-
# Placeholder regex
|
150
|
-
TEMPLATE_TAG_REGEXP = /%\{[^}]+\}/
|
151
|
-
|
152
|
-
# Initialize attributes and parameters
|
153
|
-
# @since 0.1.0
|
154
|
-
# @return [NilClass]
|
155
|
-
def configure(conf)
|
156
|
-
super
|
157
|
-
|
158
|
-
@convert.nil? or @convert.each do |field, type|
|
159
|
-
if !VALID_CONVERSIONS.include?(type)
|
160
|
-
raise ConfigError,
|
161
|
-
"convert #{type} is not one of #{VALID_CONVERSIONS.join(',')}."
|
162
|
-
end
|
163
|
-
end
|
6
|
+
module Plugin
|
7
|
+
class MutateFilter < Fluent::Plugin::Filter
|
8
|
+
Fluent::Plugin.register_filter("mutate", self)
|
164
9
|
|
165
|
-
|
166
|
-
|
167
|
-
@gsub.each_slice(3) do |field, needle, replacement|
|
168
|
-
if [field, needle, replacement].any? {|n| n.nil?}
|
169
|
-
raise ConfigError,
|
170
|
-
"gsub #{[field,needle,replacement]} requires 3 elements."
|
171
|
-
end
|
10
|
+
# Treat periods as nested field names
|
11
|
+
config_param :expand_nesting, :bool, default: true
|
172
12
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
13
|
+
# Remove any empty hashes or arrays
|
14
|
+
config_param :prune_empty, :bool, default: true
|
15
|
+
|
16
|
+
# Define mutators
|
17
|
+
config_section :mutate, param_name: :mutators, multi: true do
|
18
|
+
config_param :@type, :string, default: nil
|
178
19
|
end
|
179
|
-
end
|
180
20
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
# In order to more easily navigate the record, we wrap the record in a
|
186
|
-
# delegator. We additionally pass the `expand_nesting` option which
|
187
|
-
# determines whether we should treat periods as field separators.
|
188
|
-
result = Fluent::PluginMixin::MutateEvent.
|
189
|
-
new(record, expand_nesting: @expand_nesting)
|
190
|
-
result.event_time = time.to_i
|
191
|
-
result.event_tag = tag
|
192
|
-
|
193
|
-
MUTATE_ACTIONS.each do |action|
|
194
|
-
begin
|
195
|
-
send(action.to_sym, result) if instance_variable_get("@#{action}")
|
196
|
-
rescue => e
|
197
|
-
log.warn "failed to mutate #{action} action", error: e
|
198
|
-
log.warn_backtrace
|
199
|
-
end
|
21
|
+
def initialize
|
22
|
+
super
|
23
|
+
|
24
|
+
@actions = []
|
200
25
|
end
|
201
26
|
|
202
|
-
|
203
|
-
|
204
|
-
|
27
|
+
# Initialize attributes and parameters
|
28
|
+
# @since 0.1.0
|
29
|
+
# @return [NilClass]
|
30
|
+
def configure(conf)
|
31
|
+
super
|
205
32
|
|
206
|
-
|
33
|
+
@mutators.each do |mutator|
|
34
|
+
section = mutator.corresponding_config_element
|
207
35
|
|
208
|
-
|
209
|
-
|
210
|
-
# @return [String] the modified string
|
211
|
-
def expand_patterns(event, string)
|
212
|
-
string = expand_references(event, string)
|
213
|
-
string = expand_environment(event, string)
|
214
|
-
string
|
215
|
-
end
|
36
|
+
type = section["@type"]
|
37
|
+
data = section.to_h.tap { |h| h.delete("@type") }
|
216
38
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
39
|
+
unless type
|
40
|
+
raise Fluent::ConfigError, "Missing '@type' parameter in <mutator>"
|
41
|
+
end
|
42
|
+
|
43
|
+
unless self.respond_to?(type.to_sym, :include_private)
|
44
|
+
raise Fluent::ConfigError, "Invalid mutator #{type}"
|
45
|
+
end
|
46
|
+
|
47
|
+
# Iterate over section keys to remove access warnings, we'll be
|
48
|
+
# iterating over the data which has been dumped to array later
|
49
|
+
data.keys.each do |key|
|
50
|
+
section.has_key?(key)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Validate config section types
|
54
|
+
case type
|
55
|
+
when "convert"
|
56
|
+
data.each do |key, value|
|
57
|
+
unless VALID_CONVERSIONS.include?(value)
|
58
|
+
raise Fluent::ConfigError, "mutate #{type} action " +
|
59
|
+
"received an invalid type for #{key}, should be one " +
|
60
|
+
"of #{VALID_CONVERSIONS.join(', ')}."
|
61
|
+
end
|
62
|
+
end
|
63
|
+
when "parse"
|
64
|
+
data.each do |key, value|
|
65
|
+
unless VALID_PARSERS.include?(value)
|
66
|
+
raise Fluent::ConfigError, "mutate #{type} action " +
|
67
|
+
"received an invalid type for #{key}, should be one " +
|
68
|
+
"of #{VALID_PARSERS.join(', ')}."
|
69
|
+
end
|
70
|
+
end
|
71
|
+
when "lowercase", "uppercase", "remove", "strip"
|
72
|
+
data.each do |key, value|
|
73
|
+
v = Fluent::Config.bool_value(value)
|
74
|
+
if v.nil?
|
75
|
+
raise Fluent::ConfigError, "mutate #{type} action " +
|
76
|
+
"requires boolean values, received '#{value}' " +
|
77
|
+
"for #{key}."
|
78
|
+
end
|
79
|
+
data[key] = v
|
80
|
+
end
|
81
|
+
when "gsub"
|
82
|
+
data.each do |key, value|
|
83
|
+
v = Fluent::Config::ARRAY_TYPE.call(value, {value_type: :string})
|
84
|
+
if not v.is_a?(Array) or not v.length == 2
|
85
|
+
raise Fluent::ConfigError, "mutate #{type} action " +
|
86
|
+
"requires array values, representing " +
|
87
|
+
"[pattern, replacement] for #{key}, " +
|
88
|
+
"received '#{value}'"
|
89
|
+
end
|
90
|
+
|
91
|
+
pattern = v[0]
|
92
|
+
replacement = v[1]
|
93
|
+
|
94
|
+
data[key] = {
|
95
|
+
pattern: (
|
96
|
+
pattern.index("%{").nil?? Regexp.new(pattern): pattern\
|
97
|
+
),
|
98
|
+
replacement: replacement
|
99
|
+
}
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
@actions << {
|
104
|
+
"@type": type,
|
105
|
+
"data": data
|
106
|
+
}
|
236
107
|
end
|
108
|
+
end
|
237
109
|
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
110
|
+
# Convert valid types
|
111
|
+
VALID_CONVERSIONS = %w(string integer float boolean datetime)
|
112
|
+
|
113
|
+
# Parser valid types
|
114
|
+
VALID_PARSERS = %w(json)
|
115
|
+
|
116
|
+
# Convert helper method prefix
|
117
|
+
CONVERT_PREFIX = "convert_".freeze
|
118
|
+
|
119
|
+
# Convert boolean regex
|
120
|
+
TRUE_REGEX = (/^(true|t|yes|y|1)$/i).freeze
|
121
|
+
FALSE_REGEX = (/^(false|f|no|n|0)$/i).freeze
|
122
|
+
|
123
|
+
# Placeholder regex
|
124
|
+
ENVIRONMENT_TAG_REGEXP = /%e\{[^}]+\}/
|
125
|
+
|
126
|
+
# Placeholder regex
|
127
|
+
TEMPLATE_TAG_REGEXP = /%\{[^}]+\}/
|
128
|
+
|
129
|
+
# Filter action which will manipulate records
|
130
|
+
# @since 0.1.0
|
131
|
+
# @return [Hash] the modified event record
|
132
|
+
def filter(tag, time, record)
|
133
|
+
# In order to more easily navigate the record, we wrap the record in a
|
134
|
+
# delegator. We additionally pass the `expand_nesting` option which
|
135
|
+
# determines whether we should treat periods as field separators.
|
136
|
+
result = Fluent::Plugin::Mixin::MutateEvent.
|
137
|
+
new(record, expand_nesting: @expand_nesting)
|
138
|
+
result.event_time = time.to_i
|
139
|
+
result.event_tag = tag
|
140
|
+
|
141
|
+
@actions.each do |action|
|
142
|
+
type = action[:@type]
|
143
|
+
data = action[:data]
|
144
|
+
|
145
|
+
begin
|
146
|
+
send(type.to_sym, data, result)
|
147
|
+
rescue => e
|
148
|
+
log.warn "failed to mutate #{action} action", error: e
|
149
|
+
log.warn_backtrace
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
result.prune if @prune_empty
|
154
|
+
result.to_record
|
242
155
|
end
|
243
156
|
|
244
|
-
|
245
|
-
|
157
|
+
protected
|
158
|
+
|
159
|
+
# Expand replacable patterns on the event
|
160
|
+
# @since 0.3.0
|
161
|
+
# @return [String] the modified string
|
162
|
+
def expand_patterns(event, string)
|
163
|
+
string = expand_references(event, string)
|
164
|
+
string = expand_environment(event, string)
|
165
|
+
string
|
246
166
|
end
|
247
167
|
|
248
|
-
|
249
|
-
|
168
|
+
# Expand %{} strings to the related event fields.
|
169
|
+
# @since 0.1.0
|
170
|
+
# @return [String] the modified string
|
171
|
+
def expand_references(event, string)
|
172
|
+
new_string = ''
|
173
|
+
|
174
|
+
position = 0
|
175
|
+
matches = string.scan(TEMPLATE_TAG_REGEXP).map{|m| $~}
|
176
|
+
|
177
|
+
matches.each do |match|
|
178
|
+
reference_tag = match[0][2..-2]
|
179
|
+
reference_value = case reference_tag
|
180
|
+
when "event_time" then event.event_time.to_s
|
181
|
+
when "event_tag" then event.event_tag
|
182
|
+
else event.get(reference_tag.downcase).to_s
|
183
|
+
end
|
184
|
+
if reference_value.nil?
|
185
|
+
@log.error "failed to replace tag", field: reference_tag.downcase
|
186
|
+
reference_value = match.to_s
|
187
|
+
end
|
250
188
|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
new_string = ''
|
256
|
-
|
257
|
-
position = 0
|
258
|
-
matches = string.scan(ENVIRONMENT_TAG_REGEXP).map{|m| $~}
|
259
|
-
|
260
|
-
matches.each do |match|
|
261
|
-
reference_tag = match[0][3..-2]
|
262
|
-
reference_value = case reference_tag
|
263
|
-
when "hostname" then Socket.gethostname
|
264
|
-
else ENV[reference_tag]
|
265
|
-
end
|
266
|
-
if reference_value.nil?
|
267
|
-
@log.error "failed to replace tag", field: reference_tag
|
268
|
-
reference_value = match.to_s
|
189
|
+
start = match.offset(0).first
|
190
|
+
new_string << string[position..(start-1)] if start > 0
|
191
|
+
new_string << reference_value
|
192
|
+
position = match.offset(0).last
|
269
193
|
end
|
270
194
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
position = match.offset(0).last
|
275
|
-
end
|
195
|
+
if position < string.size
|
196
|
+
new_string << string[position..-1]
|
197
|
+
end
|
276
198
|
|
277
|
-
|
278
|
-
new_string << string[position..-1]
|
199
|
+
new_string
|
279
200
|
end
|
280
201
|
|
281
|
-
|
282
|
-
|
202
|
+
# Expand %e{} strings to the related environment variables.
|
203
|
+
# @since 0.3.0
|
204
|
+
# @return [String] the modified string
|
205
|
+
def expand_environment(event, string)
|
206
|
+
new_string = ''
|
207
|
+
|
208
|
+
position = 0
|
209
|
+
matches = string.scan(ENVIRONMENT_TAG_REGEXP).map{|m| $~}
|
210
|
+
|
211
|
+
matches.each do |match|
|
212
|
+
reference_tag = match[0][3..-2]
|
213
|
+
reference_value = case reference_tag
|
214
|
+
when "hostname" then Socket.gethostname
|
215
|
+
else ENV[reference_tag]
|
216
|
+
end
|
217
|
+
if reference_value.nil?
|
218
|
+
@log.error "failed to replace tag", field: reference_tag
|
219
|
+
reference_value = match.to_s
|
220
|
+
end
|
283
221
|
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
222
|
+
start = match.offset(0).first
|
223
|
+
new_string << string[position..(start-1)] if start > 0
|
224
|
+
new_string << reference_value
|
225
|
+
position = match.offset(0).last
|
226
|
+
end
|
227
|
+
|
228
|
+
if position < string.size
|
229
|
+
new_string << string[position..-1]
|
230
|
+
end
|
231
|
+
|
232
|
+
new_string
|
289
233
|
end
|
290
|
-
end
|
291
234
|
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
event.remove(old)
|
235
|
+
# Remove fields from the event hash
|
236
|
+
# @since 0.1.0
|
237
|
+
def remove(params, event)
|
238
|
+
params.each do |field, bool|
|
239
|
+
next unless bool
|
240
|
+
event.remove(field)
|
241
|
+
end
|
300
242
|
end
|
301
|
-
end
|
302
243
|
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
244
|
+
# Rename fields in the event hash
|
245
|
+
# @since 0.1.0
|
246
|
+
def rename(params, event)
|
247
|
+
params.each do |old, new|
|
248
|
+
item = event.get(old)
|
249
|
+
next if item.nil?
|
250
|
+
event.set(new, item)
|
251
|
+
event.remove(old)
|
252
|
+
end
|
310
253
|
end
|
311
|
-
end
|
312
254
|
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
255
|
+
# Update (existing) fields in the event hash
|
256
|
+
# @since 0.1.0
|
257
|
+
def update(params, event)
|
258
|
+
params.each do |field, newvalue|
|
259
|
+
newvalue = expand_patterns(event, newvalue)
|
260
|
+
next unless event.include?(field)
|
261
|
+
event.set(field, newvalue)
|
262
|
+
end
|
319
263
|
end
|
320
|
-
end
|
321
264
|
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
case original = event.get(field)
|
329
|
-
when NilClass
|
330
|
-
next
|
331
|
-
when Hash
|
332
|
-
@log.error("cannot convert hash", field: field, value: original)
|
333
|
-
when Array
|
334
|
-
event.set(field, original.map{|v| converter.call(v)})
|
335
|
-
else
|
336
|
-
event.set(field, converter.call(original))
|
265
|
+
# Replace fields in the event hash
|
266
|
+
# @since 0.1.0
|
267
|
+
def replace(params, event)
|
268
|
+
params.each do |field, newvalue|
|
269
|
+
newvalue = expand_patterns(event, newvalue)
|
270
|
+
event.set(field, newvalue)
|
337
271
|
end
|
338
272
|
end
|
339
|
-
end
|
340
273
|
|
341
|
-
|
342
|
-
|
343
|
-
|
274
|
+
# Convert fields to given types in the record hash
|
275
|
+
# @since 0.1.0
|
276
|
+
def convert(params, event)
|
277
|
+
params.each do |field, type|
|
278
|
+
converter = method(CONVERT_PREFIX + type)
|
344
279
|
|
345
|
-
|
346
|
-
|
347
|
-
|
280
|
+
case original = event.get(field)
|
281
|
+
when NilClass
|
282
|
+
next
|
283
|
+
when Hash
|
284
|
+
@log.error("cannot convert hash", field: field, value: original)
|
285
|
+
when Array
|
286
|
+
event.set(field, original.map{|v| converter.call(v)})
|
287
|
+
else
|
288
|
+
event.set(field, converter.call(original))
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
348
292
|
|
349
|
-
|
350
|
-
|
351
|
-
|
293
|
+
def convert_string(value)
|
294
|
+
value.to_s.force_encoding(Encoding::UTF_8)
|
295
|
+
end
|
352
296
|
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
end
|
297
|
+
def convert_integer(value)
|
298
|
+
value.to_i
|
299
|
+
end
|
357
300
|
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
@log.error("failed to convert to boolean", value: value)
|
362
|
-
end
|
301
|
+
def convert_float(value)
|
302
|
+
value.to_f
|
303
|
+
end
|
363
304
|
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
305
|
+
def convert_datetime(value)
|
306
|
+
value = convert_integer(value) if value.is_a?(String)
|
307
|
+
Time.at(value).to_datetime.to_s
|
308
|
+
end
|
309
|
+
|
310
|
+
def convert_boolean(value)
|
311
|
+
return true if value =~ TRUE_REGEX
|
312
|
+
return false if value.empty? || value =~ FALSE_REGEX
|
313
|
+
@log.error("failed to convert to boolean", value: value)
|
314
|
+
end
|
315
|
+
|
316
|
+
# Convert field values to uppercase in the record hash
|
317
|
+
# @since 0.1.0
|
318
|
+
def uppercase(params, event)
|
319
|
+
params.each do |field, bool|
|
320
|
+
next unless bool
|
321
|
+
|
322
|
+
original = event.get(field)
|
323
|
+
result = case original
|
324
|
+
when Array
|
325
|
+
original.map do |elem|
|
326
|
+
(elem.is_a?(String) ? elemen.upcase : elem)
|
327
|
+
end
|
328
|
+
when String
|
329
|
+
original.upcase! || original
|
330
|
+
else
|
331
|
+
@log.error("can't uppercase field",
|
332
|
+
field: field,
|
333
|
+
value: original)
|
334
|
+
original
|
373
335
|
end
|
374
|
-
|
375
|
-
|
376
|
-
else
|
377
|
-
@log.error("can't uppercase field",
|
378
|
-
field: field,
|
379
|
-
value: original)
|
380
|
-
original
|
381
|
-
end
|
382
|
-
event.set(field, result)
|
336
|
+
event.set(field, result)
|
337
|
+
end
|
383
338
|
end
|
384
|
-
end
|
385
339
|
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
340
|
+
# Convert field values to lowercase in the record hash
|
341
|
+
# @since 0.1.0
|
342
|
+
def lowercase(params, event)
|
343
|
+
params.each do |field, bool|
|
344
|
+
next unless bool
|
345
|
+
original = event.get(field)
|
346
|
+
result = case original
|
347
|
+
when Array
|
348
|
+
original.map do |elem|
|
349
|
+
(elem.is_a?(String) ? elemen.downcase : elem)
|
350
|
+
end
|
351
|
+
when String
|
352
|
+
original.downcase! || original
|
353
|
+
else
|
354
|
+
@log.error("can't lowercase field",
|
355
|
+
field: field,
|
356
|
+
value: original)
|
357
|
+
original
|
395
358
|
end
|
396
|
-
|
397
|
-
|
398
|
-
else
|
399
|
-
@log.error("can't lowercase field",
|
400
|
-
field: field,
|
401
|
-
value: original)
|
402
|
-
original
|
403
|
-
end
|
404
|
-
event.set(field, result)
|
359
|
+
event.set(field, result)
|
360
|
+
end
|
405
361
|
end
|
406
|
-
end
|
407
362
|
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
363
|
+
# Split fields based on delimiters in the record hash
|
364
|
+
# @since 0.1.0
|
365
|
+
def split(params, event)
|
366
|
+
params.each do |field, separator|
|
367
|
+
value = event.get(field)
|
368
|
+
if value.is_a?(String)
|
369
|
+
event.set(field, value.split(separator))
|
370
|
+
else
|
371
|
+
@log.error("can't split field",
|
372
|
+
field: field,
|
373
|
+
value: value)
|
374
|
+
end
|
419
375
|
end
|
420
376
|
end
|
421
|
-
end
|
422
377
|
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
378
|
+
# Join fields based on delimiters in the record hash
|
379
|
+
# @since 0.1.0
|
380
|
+
def join(params, event)
|
381
|
+
params.each do |field, separator|
|
382
|
+
value = event.get(field)
|
383
|
+
if value.is_a?(Array)
|
384
|
+
event.set(field, value.join(separator))
|
385
|
+
end
|
430
386
|
end
|
431
387
|
end
|
432
|
-
end
|
433
388
|
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
389
|
+
# Strip whitespace surrounding fields in the record hash
|
390
|
+
# @since 0.1.0
|
391
|
+
def strip(params, event)
|
392
|
+
params.each do |field, bool|
|
393
|
+
next unless bool
|
394
|
+
value = event.get(field)
|
395
|
+
case value
|
396
|
+
when Array
|
397
|
+
event.set(field, value.map{|s| s.strip})
|
398
|
+
when String
|
399
|
+
event.set(field, value.strip)
|
400
|
+
end
|
444
401
|
end
|
445
402
|
end
|
446
|
-
end
|
447
403
|
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
404
|
+
# Merge hashes and arrays in the record hash
|
405
|
+
# @since 0.1.0
|
406
|
+
def merge(params, event)
|
407
|
+
params.each do |dest_field, added_fields|
|
408
|
+
dest_field_value = event.get(dest_field)
|
409
|
+
|
410
|
+
Array(added_fields).each do |added_field|
|
411
|
+
added_field_value = event.get(added_field)
|
412
|
+
|
413
|
+
if dest_field_value.is_a?(Hash) ^ added_field_value.is_a?(Hash)
|
414
|
+
@log.error('cannot merge an array and hash',
|
415
|
+
dest_field: dest_field,
|
416
|
+
added_field: added_field)
|
417
|
+
next
|
418
|
+
end
|
419
|
+
|
420
|
+
if dest_field_value.is_a?(Hash)
|
421
|
+
event.set(dest_field, dest_field_value.update(added_field_value))
|
422
|
+
else
|
423
|
+
event.set(dest_field, Array(dest_field_value).
|
424
|
+
concat(Array(added_field_value)))
|
425
|
+
end
|
426
|
+
end
|
427
|
+
end
|
428
|
+
end
|
453
429
|
|
454
|
-
|
455
|
-
|
430
|
+
# Parse the value of a field
|
431
|
+
# Lazily just support json for now
|
432
|
+
# @since 1.0.0
|
433
|
+
def parse(params, event)
|
434
|
+
params.each do |field, parser|
|
435
|
+
value = event.get(field)
|
456
436
|
|
457
|
-
|
458
|
-
@log.
|
459
|
-
dest_field: dest_field,
|
460
|
-
added_field: added_field)
|
437
|
+
unless value.is_a?(String)
|
438
|
+
@log.warn("field value cannot be parsed by #{parser}")
|
461
439
|
next
|
462
440
|
end
|
463
441
|
|
464
|
-
if
|
465
|
-
|
466
|
-
|
467
|
-
event.set(
|
468
|
-
concat(Array(added_field_value)))
|
442
|
+
if value.start_with?('{') and value.ends_with?('}') \
|
443
|
+
or value.start_with?('[') and value.ends_with?(']')
|
444
|
+
value = JSON.load(value)
|
445
|
+
event.set(field, value)
|
469
446
|
end
|
470
447
|
end
|
471
448
|
end
|
472
|
-
end
|
473
449
|
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
450
|
+
# Perform regular expression substitutions in the record hahs
|
451
|
+
# @since 0.1.0
|
452
|
+
def gsub(params, event)
|
453
|
+
params.each do |key, config|
|
454
|
+
pattern = config[:pattern]
|
455
|
+
replacement = config[:replacement]
|
456
|
+
|
457
|
+
value = event.get(key)
|
458
|
+
case value
|
459
|
+
when Array
|
460
|
+
result = value.map do |v|
|
461
|
+
if v.is_a?(String)
|
462
|
+
gsub_dynamic_fields(event, v, pattern, replacement)
|
463
|
+
else
|
464
|
+
@log.error('cannot gsub non Strings',
|
465
|
+
field: key,
|
466
|
+
value: v)
|
467
|
+
end
|
468
|
+
event.set(key, result)
|
492
469
|
end
|
493
|
-
|
470
|
+
when String
|
471
|
+
v = gsub_dynamic_fields(event, value, pattern, replacement)
|
472
|
+
event.set(key, v)
|
473
|
+
else
|
474
|
+
@log.error('cannot gsub non Strings', field: key, value: value)
|
494
475
|
end
|
495
|
-
when String
|
496
|
-
v = gsub_dynamic_fields(event, value, needle, replacement)
|
497
|
-
event.set(field, v)
|
498
|
-
else
|
499
|
-
@log.error('cannot gsub non Strings', field: field, value: value)
|
500
476
|
end
|
501
477
|
end
|
502
|
-
end
|
503
478
|
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
479
|
+
def gsub_dynamic_fields(event, original, pattern, replacement)
|
480
|
+
replacement = expand_patterns(event, replacement)
|
481
|
+
if pattern.is_a?(Regexp)
|
482
|
+
original.gsub(pattern, replacement)
|
483
|
+
else
|
484
|
+
original.gsub(Regexp.new(pattern), replacement)
|
485
|
+
end
|
510
486
|
end
|
511
487
|
end
|
512
488
|
end
|
513
489
|
end
|
514
|
-
|