diff --git a/lib/rackstash.rb b/lib/rackstash.rb index 53965cb..ce81394 100644 --- a/lib/rackstash.rb +++ b/lib/rackstash.rb @@ -56,3 +56,4 @@ end require 'rackstash/logger' require 'rackstash/adapters/io' +require 'rackstash/adapters/callable' diff --git a/lib/rackstash/adapters/callable.rb b/lib/rackstash/adapters/callable.rb new file mode 100644 index 0000000..9a129ec --- /dev/null +++ b/lib/rackstash/adapters/callable.rb @@ -0,0 +1,69 @@ +# 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 'rackstash/adapters/adapter' +require 'rackstash/encoders/raw' + +module Rackstash + module Adapters + # This adapter calls a user-provided "callable", i.e., a `Proc` or block for + # each written log line. This allows users to custom handle the logs without + # having to write a full custom adapter class. + # + # You can pass the callable as a block to {#initialize} or as a Proc or any + # other object responding to `call`. For each written log, we call the block + # once. + # + # Note that we do not ensure that the calls are sequentially. If multiple + # threads are concurrently writing logs to the logger, the calable might be + # called concurrently from multiple threads too. + # + # To create an adapter instance, you can use this example: + # + # Rackstash::Adapters::Callable.new do |log| + # # handle the log as required + # end + class Callable < Adapter + register_for ::Proc, :call + + # Create a new Callable adapter by wrapping a proc. You can pass the proc + # either as the firat parameter to {#initialize} or as a block which is + # then transformed into a proc internally. + # + # @param callable [Proc, #call] a callable object, usually a proc or + # lambda + def initialize(callable = nil, &block) + if callable.respond_to?(:call) + @callable = callable + elsif block_given? + @callable = block + else + raise TypeError, "#{callable.inspect} does not appear to be callable" + end + end + + # By default, we use an {Rackstash::Encoders::Raw} to encode the events. + # This ensures that the raw event is passed through to to the callable by + # default. + # + # You can define a custom encoder in the responsible {Flow}. + # + # @return [Rackstash::Encoders::Raw] a new Raw encoder + def default_encoder + Rackstash::Encoders::Raw.new + end + + # Write a single log line by calling the defined `callable` given in + # {#initialize}. + # + # @param log [Object] the encoded log event + # @return [nil] + def write_single(log) + @callable.call(log) + nil + end + end + end +end diff --git a/spec/rackstash/adapters/callable_spec.rb b/spec/rackstash/adapters/callable_spec.rb new file mode 100644 index 0000000..9c0c81b --- /dev/null +++ b/spec/rackstash/adapters/callable_spec.rb @@ -0,0 +1,63 @@ +# 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/adapters/callable' + +describe Rackstash::Adapters::Callable do + let(:callable) { ->(log) { log } } + let(:adapter) { Rackstash::Adapters::Callable.new(callable) } + + describe '#initialize' do + it 'accepts a callable' do + expect { Rackstash::Adapters::Callable.new(->{}) }.not_to raise_error + expect { Rackstash::Adapters::Callable.new(proc {}) }.not_to raise_error + expect { Rackstash::Adapters::Callable.new(Struct.new(:call).new) }.not_to raise_error + + expect { Rackstash::Adapters::Callable.new { |log| log } }.not_to raise_error + end + + it 'rejects non-IO objects' do + expect { Rackstash::Adapters::Callable.new(nil) }.to raise_error TypeError + expect { Rackstash::Adapters::Callable.new('hello') }.to raise_error TypeError + expect { Rackstash::Adapters::Callable.new(Object.new) }.to raise_error TypeError + expect { Rackstash::Adapters::Callable.new([]) }.to raise_error TypeError + expect { Rackstash::Adapters::Callable.new(Struct.new(:foo).new) }.to raise_error TypeError + end + end + + describe '.default_encoder' do + it 'returns a Raw encoder' do + expect(adapter.default_encoder).to be_instance_of Rackstash::Encoders::Raw + end + end + + describe '#close' do + it 'does nothing' do + expect(callable).not_to receive(:close) + adapter.close + end + end + + describe '#reopen' do + it 'does nothing' do + expect(callable).not_to receive(:close) + adapter.reopen + end + end + + describe '#write_single' do + it 'calls the callable with the log' do + expect(callable).to receive(:call).with('a log line') + adapter.write('a log line') + end + + it 'passes through the original object' do + expect(callable).to receive(:call).with([123, 'hello']) + adapter.write([123, 'hello']) + end + end +end