diff --git a/lib/rackstash/filters/replace.rb b/lib/rackstash/filters/replace.rb index 40ec694..81c6719 100644 --- a/lib/rackstash/filters/replace.rb +++ b/lib/rackstash/filters/replace.rb @@ -14,7 +14,8 @@ module Rackstash # new value which is set on the key. # # If a specified field does not exist in the event hash, it will be created - # with the given (or calculated) value anyway. + # with the given (or calculated) value anyway. To ignore a missing field, + # use the {Update} filter instead. # # @example # Rackstash::Flow.new(STDOUT) do diff --git a/lib/rackstash/filters/update.rb b/lib/rackstash/filters/update.rb new file mode 100644 index 0000000..08b9478 --- /dev/null +++ b/lib/rackstash/filters/update.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true +# +# Copyright 2017 Holger Just +# +# This software may be modified and distributed under the terms +# of the MIT license. See the LICENSE.txt file for details. + +module Rackstash + module Filters + # Update fields in the given event with new values. A new value can be + # specified as either a fixed value or as a `Proc` (or any other object + # responding to `call`). In the latter case, the callable object will be + # called with the event as its argument. It is then expected to return the + # new value which is set on the key. + # + # If a specified field does not exist in the event hash yet, it will not be + # set and the respective proc will not be called. To set the field with the + # specified value anyway, use the {Replace} filter instead. + # + # @example + # Rackstash::Flow.new(STDOUT) do + # filter :update, { + # "sample" => ->(event) { event['key'] } + # } + # end + # + # You should make sure to only set a new object of one of the basic types + # here, namely `String`, `Integer`, `Float`, `Hash`, `Array`, `nil`, `true`, + # or `false`. + class Update + # @param spec [Hash<#to_s => #call,Object>] a `Hash` specifying new field + # values for the named keys. Values can be given in the form of a fixed + # value or a callable object (e.g. a `Proc`) which accepts the event as + # its argument and returns the new value. + def initialize(spec) + @update = {} + Hash(spec).each_pair do |key, value| + @update[key.to_s] = value + end + end + + # Update existing field fields in the event with a new value. + # + # @param event [Hash] an event hash + # return [Hash] the given `event` with the fields renamed + def call(event) + @update.each_pair do |key, value| + next unless event.key?(key) + + value = value.call(event) if value.respond_to?(:call) + event[key] = value + end + event + end + end + end +end diff --git a/spec/rackstash/filters/replace_spec.rb b/spec/rackstash/filters/replace_spec.rb index 32db849..9d5680f 100644 --- a/spec/rackstash/filters/replace_spec.rb +++ b/spec/rackstash/filters/replace_spec.rb @@ -21,7 +21,7 @@ describe Rackstash::Filters::Replace do described_class.new(spec).call(event) end - it 'sets evaluates values from callable objects' do + it 'sets evaluated values from callable objects' do filter!('foo' => ->(event) { event['foo'].upcase } ) expect(event).to eql 'foo' => 'FOO VALUE', 'bar' => 'bar value' end diff --git a/spec/rackstash/filters/update_spec.rb b/spec/rackstash/filters/update_spec.rb new file mode 100644 index 0000000..e8011f5 --- /dev/null +++ b/spec/rackstash/filters/update_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true +# +# Copyright 2017 Holger Just +# +# This software may be modified and distributed under the terms +# of the MIT license. See the LICENSE.txt file for details. + +require 'spec_helper' + +require 'rackstash/filters/update' + +describe Rackstash::Filters::Update do + let(:event) { + { + 'foo' => 'foo value', + 'bar' => 'bar value' + } + } + + def filter!(spec) + described_class.new(spec).call(event) + end + + it 'sets evaluated values from callable objects' do + filter!('foo' => ->(event) { event['foo'].upcase } ) + expect(event).to eql 'foo' => 'FOO VALUE', 'bar' => 'bar value' + end + + it 'sets raw values' do + filter!('bar' => 123 ) + expect(event).to eql 'foo' => 'foo value', 'bar' => 123 + end + + it 'ignores missing fields' do + spec = {'baz' => 42, 'boing' => ->(event) { 'quark' }} + expect(spec['boing']).not_to receive(:call) + + filter!(spec) + expect(event).to eql( + 'foo' => 'foo value', + 'bar' => 'bar value' + ) + end + + it 'stringifies keys' do + filter!(foo: ->(event) { event['foo'].upcase } ) + expect(event).to eql 'foo' => 'FOO VALUE', 'bar' => 'bar value' + end + + it 'returns the given event object' do + expect(filter!('bar' => 'baz')).to equal event + end +end