fluent-plugin-mutate_filter 0.3.1 → 1.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/.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
|
-
|