logstash-filter-kv 4.4.1 → 4.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +1 -1
- data/docs/index.asciidoc +35 -4
- data/lib/logstash/filters/kv.rb +34 -18
- data/logstash-filter-kv.gemspec +3 -1
- data/spec/filters/kv_spec.rb +32 -3
- metadata +31 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: da90e7e03c6cb4960a1c224d216f03ee985b6f1b320832b265b4abd112ac5ade
|
4
|
+
data.tar.gz: a4f50d4707f2a3c74cd917e8f86b1acc53e974b56e967953009c6b1f6d241ec3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6b2f78f389c0ae6d43132aff4fce9ff868dd044a220cd9aeaf69c5015d05c0a200be2c6fdc0df9876c4395912e8ff32ee2649e464a2d2d7dc70274243994f08e
|
7
|
+
data.tar.gz: 855fb4feebcb15be71505bef3ca4920b60d2e06c1adfd3ae646dc2caf5c95ac69609a6a7cc52ab327bcfce29f92cace5e47612b6a8af542016f468657a007b17
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
## 4.7.0
|
2
|
+
- Allow attaching multiple tags on failure. The `tag_on_failure` option now also supports an array of strings [#92](https://github.com/logstash-plugins/logstash-filter-kv/issues/92)
|
3
|
+
|
4
|
+
## 4.6.0
|
5
|
+
- Added `allow_empty_values` option [#72](https://github.com/logstash-plugins/logstash-filter-kv/pull/72)
|
6
|
+
|
7
|
+
## 4.5.0
|
8
|
+
- Feat: check that target is set in ECS mode [#96](https://github.com/logstash-plugins/logstash-filter-kv/pull/96)
|
9
|
+
|
1
10
|
## 4.4.1
|
2
11
|
- Fixed issue where a `field_split_pattern` containing a literal backslash failed to match correctly [#87](https://github.com/logstash-plugins/logstash-filter-kv/issues/87)
|
3
12
|
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Logstash Plugin
|
2
2
|
|
3
|
-
[![Travis Build Status](https://travis-ci.
|
3
|
+
[![Travis Build Status](https://travis-ci.com/logstash-plugins/logstash-filter-kv.svg)](https://travis-ci.com/logstash-plugins/logstash-filter-kv)
|
4
4
|
|
5
5
|
This is a plugin for [Logstash](https://github.com/elastic/logstash).
|
6
6
|
|
data/docs/index.asciidoc
CHANGED
@@ -44,6 +44,13 @@ in case your data is not structured using `=` signs and whitespace.
|
|
44
44
|
For example, this filter can also be used to parse query parameters like
|
45
45
|
`foo=bar&baz=fizz` by setting the `field_split` parameter to `&`.
|
46
46
|
|
47
|
+
[id="plugins-{type}s-{plugin}-ecs_metadata"]
|
48
|
+
==== Event Metadata and the Elastic Common Schema (ECS)
|
49
|
+
|
50
|
+
The plugin behaves the same regardless of ECS compatibility, except giving a warning when ECS is enabled and `target` isn't set.
|
51
|
+
|
52
|
+
TIP: Set the `target` option to avoid potential schema conflicts.
|
53
|
+
|
47
54
|
[id="plugins-{type}s-{plugin}-options"]
|
48
55
|
==== Kv Filter Configuration Options
|
49
56
|
|
@@ -53,7 +60,9 @@ This plugin supports the following configuration options plus the <<plugins-{typ
|
|
53
60
|
|=======================================================================
|
54
61
|
|Setting |Input type|Required
|
55
62
|
| <<plugins-{type}s-{plugin}-allow_duplicate_values>> |<<boolean,boolean>>|No
|
63
|
+
| <<plugins-{type}s-{plugin}-allow_empty_values>> |<<boolean,boolean>>|No
|
56
64
|
| <<plugins-{type}s-{plugin}-default_keys>> |<<hash,hash>>|No
|
65
|
+
| <<plugins-{type}s-{plugin}-ecs_compatibility>> | <<string,string>>|No
|
57
66
|
| <<plugins-{type}s-{plugin}-exclude_keys>> |<<array,array>>|No
|
58
67
|
| <<plugins-{type}s-{plugin}-field_split>> |<<string,string>>|No
|
59
68
|
| <<plugins-{type}s-{plugin}-field_split_pattern>> |<<string,string>>|No
|
@@ -65,7 +74,7 @@ This plugin supports the following configuration options plus the <<plugins-{typ
|
|
65
74
|
| <<plugins-{type}s-{plugin}-remove_char_value>> |<<string,string>>|No
|
66
75
|
| <<plugins-{type}s-{plugin}-source>> |<<string,string>>|No
|
67
76
|
| <<plugins-{type}s-{plugin}-target>> |<<string,string>>|No
|
68
|
-
| <<plugins-{type}s-{plugin}-tag_on_failure>> |<<
|
77
|
+
| <<plugins-{type}s-{plugin}-tag_on_failure>> |<<array,array>>|No
|
69
78
|
| <<plugins-{type}s-{plugin}-tag_on_timeout>> |<<string,string>>|No
|
70
79
|
| <<plugins-{type}s-{plugin}-timeout_millis>> |<<number,number>>|No
|
71
80
|
| <<plugins-{type}s-{plugin}-transform_key>> |<<string,string>>, one of `["lowercase", "uppercase", "capitalize"]`|No
|
@@ -101,6 +110,17 @@ you could use this configuration:
|
|
101
110
|
}
|
102
111
|
}
|
103
112
|
|
113
|
+
[id="plugins-{type}s-{plugin}-allow_empty_values"]
|
114
|
+
===== `allow_empty_values`
|
115
|
+
|
116
|
+
* Value type is <<boolean,boolean>>
|
117
|
+
* Default value is `false`
|
118
|
+
|
119
|
+
A bool option for explicitly including empty values.
|
120
|
+
When set to true, empty values will be added to the event.
|
121
|
+
|
122
|
+
NOTE: Parsing empty values typically requires <<plugins-{type}s-{plugin}-whitespace,`whitespace => strict`>>.
|
123
|
+
|
104
124
|
[id="plugins-{type}s-{plugin}-default_keys"]
|
105
125
|
===== `default_keys`
|
106
126
|
|
@@ -117,6 +137,17 @@ in case these keys do not exist in the source field being parsed.
|
|
117
137
|
}
|
118
138
|
}
|
119
139
|
|
140
|
+
[id="plugins-{type}s-{plugin}-ecs_compatibility"]
|
141
|
+
===== `ecs_compatibility`
|
142
|
+
|
143
|
+
* Value type is <<string,string>>
|
144
|
+
* Supported values are:
|
145
|
+
** `disabled`: does not use ECS-compatible field names
|
146
|
+
** `v1`: Elastic Common Schema compliant behavior (warns when `target` isn't set)
|
147
|
+
|
148
|
+
Controls this plugin's compatibility with the {ecs-ref}[Elastic Common Schema (ECS)].
|
149
|
+
See <<plugins-{type}s-{plugin}-ecs_metadata>> for detailed information.
|
150
|
+
|
120
151
|
[id="plugins-{type}s-{plugin}-exclude_keys"]
|
121
152
|
===== `exclude_keys`
|
122
153
|
|
@@ -341,12 +372,12 @@ For example, to place all keys into the event field kv:
|
|
341
372
|
[id="plugins-{type}s-{plugin}-tag_on_failure"]
|
342
373
|
===== `tag_on_failure`
|
343
374
|
|
344
|
-
* Value type is <<
|
345
|
-
* The default value for this setting is `_kv_filter_error
|
375
|
+
* Value type is <<array,array>>
|
376
|
+
* The default value for this setting is [`_kv_filter_error`].
|
346
377
|
|
347
378
|
When a kv operation causes a runtime exception to be thrown within the plugin,
|
348
379
|
the operation is safely aborted without crashing the plugin, and the event is
|
349
|
-
tagged with the provided
|
380
|
+
tagged with the provided values.
|
350
381
|
|
351
382
|
[id="plugins-{type}s-{plugin}-tag_on_timeout"]
|
352
383
|
===== `tag_on_timeout`
|
data/lib/logstash/filters/kv.rb
CHANGED
@@ -2,6 +2,9 @@
|
|
2
2
|
|
3
3
|
require "logstash/filters/base"
|
4
4
|
require "logstash/namespace"
|
5
|
+
require 'logstash/plugin_mixins/ecs_compatibility_support'
|
6
|
+
require 'logstash/plugin_mixins/ecs_compatibility_support/target_check'
|
7
|
+
require 'logstash/plugin_mixins/validator_support/field_reference_validation_adapter'
|
5
8
|
require "timeout"
|
6
9
|
|
7
10
|
# This filter helps automatically parse messages (or specific event fields)
|
@@ -30,6 +33,11 @@ require "timeout"
|
|
30
33
|
class LogStash::Filters::KV < LogStash::Filters::Base
|
31
34
|
config_name "kv"
|
32
35
|
|
36
|
+
include LogStash::PluginMixins::ECSCompatibilitySupport
|
37
|
+
include LogStash::PluginMixins::ECSCompatibilitySupport::TargetCheck
|
38
|
+
|
39
|
+
extend LogStash::PluginMixins::ValidatorSupport::FieldReferenceValidationAdapter
|
40
|
+
|
33
41
|
# Constants used for transform check
|
34
42
|
TRANSFORM_LOWERCASE_KEY = "lowercase"
|
35
43
|
TRANSFORM_UPPERCASE_KEY = "uppercase"
|
@@ -59,7 +67,7 @@ class LogStash::Filters::KV < LogStash::Filters::Base
|
|
59
67
|
# These characters form a regex character class and thus you must escape special regex
|
60
68
|
# characters like `[` or `]` using `\`.
|
61
69
|
#
|
62
|
-
# Only leading and trailing characters are
|
70
|
+
# Only leading and trailing characters are trimmed from the key.
|
63
71
|
#
|
64
72
|
# For example, to trim `<` `>` `[` `]` and `,` characters from keys:
|
65
73
|
# [source,ruby]
|
@@ -201,7 +209,7 @@ class LogStash::Filters::KV < LogStash::Filters::Base
|
|
201
209
|
# For example, to process the `not_the_message` field:
|
202
210
|
# [source,ruby]
|
203
211
|
# filter { kv { source => "not_the_message" } }
|
204
|
-
config :source, :validate => :
|
212
|
+
config :source, :validate => :field_reference, :default => "message"
|
205
213
|
|
206
214
|
# The name of the container to put all of the key-value pairs into.
|
207
215
|
#
|
@@ -211,7 +219,7 @@ class LogStash::Filters::KV < LogStash::Filters::Base
|
|
211
219
|
# For example, to place all keys into the event field kv:
|
212
220
|
# [source,ruby]
|
213
221
|
# filter { kv { target => "kv" } }
|
214
|
-
config :target, :validate => :
|
222
|
+
config :target, :validate => :field_reference
|
215
223
|
|
216
224
|
# An array specifying the parsed keys which should be added to the event.
|
217
225
|
# By default all keys will be added.
|
@@ -264,6 +272,9 @@ class LogStash::Filters::KV < LogStash::Filters::Base
|
|
264
272
|
# }
|
265
273
|
config :allow_duplicate_values, :validate => :boolean, :default => true
|
266
274
|
|
275
|
+
# A bool option for keeping empty or nil values.
|
276
|
+
config :allow_empty_values, :validate => :boolean, :default => false
|
277
|
+
|
267
278
|
# A boolean specifying whether to treat square brackets, angle brackets,
|
268
279
|
# and parentheses as value "wrappers" that should be removed from the value.
|
269
280
|
# [source,ruby]
|
@@ -327,7 +338,10 @@ class LogStash::Filters::KV < LogStash::Filters::Base
|
|
327
338
|
config :tag_on_timeout, :validate => :string, :default => '_kv_filter_timeout'
|
328
339
|
|
329
340
|
# Tag to apply if kv errors
|
330
|
-
config :tag_on_failure, :validate => :
|
341
|
+
config :tag_on_failure, :validate => :array, :default => ['_kv_filter_error']
|
342
|
+
|
343
|
+
|
344
|
+
EMPTY_STRING = ''.freeze
|
331
345
|
|
332
346
|
def register
|
333
347
|
# Too late to set the regexp interruptible flag, at least warn if it is not set.
|
@@ -411,8 +425,8 @@ class LogStash::Filters::KV < LogStash::Filters::Base
|
|
411
425
|
|
412
426
|
@logger.debug? && @logger.debug("KV scan regex", :regex => @scan_re.inspect)
|
413
427
|
|
414
|
-
# divide by float to allow
|
415
|
-
# executor resolution is in microseconds so
|
428
|
+
# divide by float to allow fractional seconds, the Timeout class timeout value is in seconds but the underlying
|
429
|
+
# executor resolution is in microseconds so fractional second parameter down to microseconds is possible.
|
416
430
|
# see https://github.com/jruby/jruby/blob/9.2.7.0/core/src/main/java/org/jruby/ext/timeout/Timeout.java#L125
|
417
431
|
@timeout_seconds = @timeout_millis / 1000.0
|
418
432
|
end
|
@@ -429,7 +443,9 @@ class LogStash::Filters::KV < LogStash::Filters::Base
|
|
429
443
|
return if kv.empty?
|
430
444
|
|
431
445
|
if @target
|
432
|
-
|
446
|
+
if event.include?(@target)
|
447
|
+
@logger.debug? && @logger.debug("Overwriting existing target field", field: @target, existing_value: event.get(@target))
|
448
|
+
end
|
433
449
|
event.set(@target, kv)
|
434
450
|
else
|
435
451
|
kv.each{|k, v| event.set(k, v)}
|
@@ -444,7 +460,7 @@ class LogStash::Filters::KV < LogStash::Filters::Base
|
|
444
460
|
meta = { :exception => ex.message }
|
445
461
|
meta[:backtrace] = ex.backtrace if logger.debug?
|
446
462
|
logger.warn('Exception while parsing KV', meta)
|
447
|
-
event.tag(
|
463
|
+
@tag_on_failure.each { |tag| event.tag(tag) }
|
448
464
|
end
|
449
465
|
|
450
466
|
def close
|
@@ -484,9 +500,9 @@ class LogStash::Filters::KV < LogStash::Filters::Base
|
|
484
500
|
|
485
501
|
value = value.to_s
|
486
502
|
|
487
|
-
value.bytesize < 255 ? "`#{value}`" : "entry too large; first 255
|
503
|
+
value.bytesize < 255 ? "`#{value.dump}`" : "(entry too large to show; showing first 255 characters) `#{value[0..255].dump}`[...]"
|
488
504
|
end
|
489
|
-
|
505
|
+
|
490
506
|
def has_value_splitter?(s)
|
491
507
|
s =~ @value_split_re
|
492
508
|
end
|
@@ -559,12 +575,12 @@ class LogStash::Filters::KV < LogStash::Filters::Base
|
|
559
575
|
exclude_keys = @exclude_keys.map{|key| event.sprintf(key)}
|
560
576
|
|
561
577
|
text.scan(@scan_re) do |key, *value_candidates|
|
562
|
-
value = value_candidates.compact.first
|
563
|
-
next if value.
|
578
|
+
value = value_candidates.compact.first || EMPTY_STRING
|
579
|
+
next if value.empty? && !@allow_empty_values
|
564
580
|
|
565
|
-
key =
|
566
|
-
key =
|
567
|
-
key =
|
581
|
+
key = key.gsub(@trim_key_re, EMPTY_STRING) if @trim_key
|
582
|
+
key = key.gsub(@remove_char_key_re, EMPTY_STRING) if @remove_char_key
|
583
|
+
key = transform(key, @transform_key) if @transform_key
|
568
584
|
|
569
585
|
# Bail out as per the values of include_keys and exclude_keys
|
570
586
|
next if not include_keys.empty? and not include_keys.include?(key)
|
@@ -573,9 +589,9 @@ class LogStash::Filters::KV < LogStash::Filters::Base
|
|
573
589
|
|
574
590
|
key = event.sprintf(@prefix) + key
|
575
591
|
|
576
|
-
value =
|
577
|
-
value =
|
578
|
-
value =
|
592
|
+
value = value.gsub(@trim_value_re, EMPTY_STRING) if @trim_value
|
593
|
+
value = value.gsub(@remove_char_value_re, EMPTY_STRING) if @remove_char_value
|
594
|
+
value = transform(value, @transform_value) if @transform_value
|
579
595
|
|
580
596
|
# Bail out if inserting duplicate value in key mapping when unique_values
|
581
597
|
# option is set to true.
|
data/logstash-filter-kv.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
|
3
3
|
s.name = 'logstash-filter-kv'
|
4
|
-
s.version = '4.
|
4
|
+
s.version = '4.7.0'
|
5
5
|
s.licenses = ['Apache License (2.0)']
|
6
6
|
s.summary = "Parses key-value pairs"
|
7
7
|
s.description = "This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This gem is not a stand-alone program"
|
@@ -21,6 +21,8 @@ Gem::Specification.new do |s|
|
|
21
21
|
|
22
22
|
# Gem dependencies
|
23
23
|
s.add_runtime_dependency "logstash-core-plugin-api", ">= 1.60", "<= 2.99"
|
24
|
+
s.add_runtime_dependency 'logstash-mixin-ecs_compatibility_support', '~> 1.3'
|
25
|
+
s.add_runtime_dependency 'logstash-mixin-validator_support', '~> 1.0'
|
24
26
|
|
25
27
|
s.add_development_dependency 'logstash-devutils'
|
26
28
|
s.add_development_dependency 'insist'
|
data/spec/filters/kv_spec.rb
CHANGED
@@ -460,7 +460,6 @@ describe LogStash::Filters::KV do
|
|
460
460
|
end
|
461
461
|
end
|
462
462
|
|
463
|
-
|
464
463
|
describe "test data from specific sub source" do
|
465
464
|
config <<-CONFIG
|
466
465
|
filter {
|
@@ -519,7 +518,6 @@ describe LogStash::Filters::KV do
|
|
519
518
|
end
|
520
519
|
end
|
521
520
|
|
522
|
-
|
523
521
|
describe "test data from specific sub source and target" do
|
524
522
|
config <<-CONFIG
|
525
523
|
filter {
|
@@ -721,6 +719,27 @@ describe LogStash::Filters::KV do
|
|
721
719
|
end
|
722
720
|
end
|
723
721
|
|
722
|
+
describe "Allowing empty values" do
|
723
|
+
config <<-CONFIG
|
724
|
+
filter {
|
725
|
+
kv {
|
726
|
+
field_split => " "
|
727
|
+
source => "source"
|
728
|
+
allow_empty_values => true
|
729
|
+
whitespace => strict
|
730
|
+
}
|
731
|
+
}
|
732
|
+
CONFIG
|
733
|
+
|
734
|
+
sample("source" => "present=one empty= emptyquoted='' present=two emptybracketed=[] endofinput=") do
|
735
|
+
insist { subject.get('[present]') } == ['one','two']
|
736
|
+
insist { subject.get('[empty]') } == ''
|
737
|
+
insist { subject.get('[emptyquoted]') } == ''
|
738
|
+
insist { subject.get('[emptybracketed]') } == ''
|
739
|
+
insist { subject.get('[endofinput]') } == ''
|
740
|
+
end
|
741
|
+
end
|
742
|
+
|
724
743
|
describe "Allow duplicate key/value pairs by default" do
|
725
744
|
config <<-CONFIG
|
726
745
|
filter {
|
@@ -1038,7 +1057,6 @@ describe "multi character splitting" do
|
|
1038
1057
|
it_behaves_like "parsing all fields and values"
|
1039
1058
|
end
|
1040
1059
|
|
1041
|
-
|
1042
1060
|
context "example from @guyboertje in #15" do
|
1043
1061
|
let(:message) { 'key1: val1; key2: val2; key3: https://site/?g={......"...; CLR rv:11.0)"..}; key4: val4;' }
|
1044
1062
|
let(:options) {
|
@@ -1131,6 +1149,17 @@ context 'runtime errors' do
|
|
1131
1149
|
plugin.filter(event)
|
1132
1150
|
expect(event.get('tags')).to_not be_nil
|
1133
1151
|
expect(event.get('tags')).to include('KV-ERROR')
|
1152
|
+
expect(event.get('tags')).to_not include('_kv_filter_error')
|
1153
|
+
end
|
1154
|
+
end
|
1155
|
+
context 'when multiple custom tags are defined' do
|
1156
|
+
let(:options) { super().merge("tag_on_failure" => ["kv_FAIL_one", "_kv_fail_TWO"])}
|
1157
|
+
it 'tags the event with the custom tag' do
|
1158
|
+
plugin.filter(event)
|
1159
|
+
expect(event.get('tags')).to_not be_nil
|
1160
|
+
expect(event.get('tags')).to include('kv_FAIL_one')
|
1161
|
+
expect(event.get('tags')).to include('_kv_fail_TWO')
|
1162
|
+
expect(event.get('tags')).to_not include('_kv_filter_error')
|
1134
1163
|
end
|
1135
1164
|
end
|
1136
1165
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: logstash-filter-kv
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Elastic
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-03-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
@@ -30,6 +30,34 @@ dependencies:
|
|
30
30
|
- - "<="
|
31
31
|
- !ruby/object:Gem::Version
|
32
32
|
version: '2.99'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - "~>"
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '1.3'
|
39
|
+
name: logstash-mixin-ecs_compatibility_support
|
40
|
+
prerelease: false
|
41
|
+
type: :runtime
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '1.3'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - "~>"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '1.0'
|
53
|
+
name: logstash-mixin-validator_support
|
54
|
+
prerelease: false
|
55
|
+
type: :runtime
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '1.0'
|
33
61
|
- !ruby/object:Gem::Dependency
|
34
62
|
requirement: !ruby/object:Gem::Requirement
|
35
63
|
requirements:
|
@@ -97,8 +125,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
97
125
|
- !ruby/object:Gem::Version
|
98
126
|
version: '0'
|
99
127
|
requirements: []
|
100
|
-
|
101
|
-
rubygems_version: 2.6.13
|
128
|
+
rubygems_version: 3.1.6
|
102
129
|
signing_key:
|
103
130
|
specification_version: 4
|
104
131
|
summary: Parses key-value pairs
|