diff --git a/logstash-core/lib/logstash/compiler/lscl.rb b/logstash-core/lib/logstash/compiler/lscl.rb index d4ee27fcc90..ab0c87c9c43 100644 --- a/logstash-core/lib/logstash/compiler/lscl.rb +++ b/logstash-core/lib/logstash/compiler/lscl.rb @@ -103,10 +103,18 @@ def expr_attributes end }.reduce({}) do |hash, kv| k, v = kv - if hash[k].nil? + existing = hash[k] + if existing.nil? hash[k] = v + elsif existing.kind_of?(::Hash) + # For legacy reasons, a config can contain multiple `AST::Attribute`s with the same name + # and a hash-type value (e.g., "match" in the grok filter), which are merged into a single + # hash value; e.g., `{"match" => {"baz" => "bar"}, "match" => {"foo" => "bulb"}}` is + # interpreted as `{"match" => {"baz" => "bar", "foo" => "blub"}}`. + # (NOTE: this bypasses `AST::Hash`'s ability to detect duplicate keys) + hash[k] = existing.merge(v) else - hash[k] += v + hash[k] = existing + v end hash end diff --git a/logstash-core/spec/logstash/compiler/compiler_spec.rb b/logstash-core/spec/logstash/compiler/compiler_spec.rb index 16c81e85462..977360e3641 100644 --- a/logstash-core/spec/logstash/compiler/compiler_spec.rb +++ b/logstash-core/spec/logstash/compiler/compiler_spec.rb @@ -193,6 +193,35 @@ def j expect(c_plugin).to ir_eql(j.iPlugin(INPUT, "generator", expected_plugin_args)) end end + + describe "a filter plugin that repeats a Hash directive" do + let(:source) { "input { } filter { #{plugin_source} } output { } " } + subject(:c_plugin) { compiled[:filter] } + + let(:plugin_source) do + %q[ + grok { + match => { "message" => "%{WORD:word}" } + match => { "examplefield" => "%{NUMBER:num}" } + break_on_match => false + } + ] + end + + let(:expected_plugin_args) do + { + "match" => { + "message" => "%{WORD:word}", + "examplefield" => "%{NUMBER:num}" + }, + "break_on_match" => "false" + } + end + + it "should merge the contents of the individual directives" do + expect(c_plugin).to ir_eql(j.iPlugin(FILTER, "grok", expected_plugin_args)) + end + end end context "inputs" do