From 6eaeaedaf8158f59d5bbe11a2b85ee21b9cc7870 Mon Sep 17 00:00:00 2001 From: Holger Just Date: Wed, 20 Dec 2017 23:58:32 +0100 Subject: [PATCH] Register encoders using a ClassRegistry --- lib/rackstash.rb | 7 +++ lib/rackstash/encoder.rb | 83 ++++++++++++++++++++++++++--- lib/rackstash/encoder/hash.rb | 3 ++ lib/rackstash/encoder/json.rb | 3 ++ lib/rackstash/encoder/lograge.rb | 3 ++ lib/rackstash/encoder/logstash.rb | 3 ++ lib/rackstash/encoder/message.rb | 3 ++ lib/rackstash/encoder/raw.rb | 4 ++ spec/rackstash/encoder_spec.rb | 88 +++++++++++++++++++++++++++++++ 9 files changed, 191 insertions(+), 6 deletions(-) create mode 100644 spec/rackstash/encoder_spec.rb diff --git a/lib/rackstash.rb b/lib/rackstash.rb index 4901556..bffdcac 100644 --- a/lib/rackstash.rb +++ b/lib/rackstash.rb @@ -173,6 +173,13 @@ require 'rackstash/adapter/logger' require 'rackstash/adapter/io' require 'rackstash/adapter/null' +require 'rackstash/encoder/hash' +require 'rackstash/encoder/json' +require 'rackstash/encoder/lograge' +require 'rackstash/encoder/logstash' +require 'rackstash/encoder/message' +require 'rackstash/encoder/raw' + require 'rackstash/filter/clear_color' require 'rackstash/filter/default_fields' require 'rackstash/filter/default_tags' diff --git a/lib/rackstash/encoder.rb b/lib/rackstash/encoder.rb index 7a118ae..7c8a52a 100644 --- a/lib/rackstash/encoder.rb +++ b/lib/rackstash/encoder.rb @@ -5,9 +5,80 @@ # This software may be modified and distributed under the terms # of the MIT license. See the LICENSE.txt file for details. -require 'rackstash/encoder/hash' -require 'rackstash/encoder/json' -require 'rackstash/encoder/lograge' -require 'rackstash/encoder/logstash' -require 'rackstash/encoder/message' -require 'rackstash/encoder/raw' +module Rackstash + # An Encoder is part of a {Flow} where they are responsible to transform the + # filtered event into a format suitable for writing by the final log + # {Adapter}. + # + # The encoder needs to be selected together with the log {Adapter}. While many + # adapters support different encoders, some require a specific format to be + # sent over a wire. Please consult the documentation of your desired adapter + # for details. + # + # Each adapter can define their common default encoder. In a flow, you can + # optionally overwrite the used encoder to select a different log format (e.g. + # to log using the {Lograge} key-value syntax instead of the common default of + # {JSON}. + # + # An encoder can be any object responding to `encode`, e.g. a Proc or an + # instance of a class inside this module. Note that although Strings respond + # to the `encode` method, they are not suitable encoders since Strings can not + # deal with events on their own. + module Encoder + class << self + # @param encoder_class [Class] a class from which a new encoder can be + # created. Filter objects must respond to `encode` and accept an event + # hash. + # @param names [Array] one or more names for the + # registered `encoder_class`. Using these names, the user can create a + # new encoder object from the registered class in {.build}. + # @raise [TypeError] if objects of type were passed + # @return [Class] the passed `filter_class` + def register(encoder_class, *names) + unless encoder_class.is_a?(Class) && + encoder_class.instance_methods.include?(:encode) + raise TypeError, 'Can only register encoder classes' + end + + names.flatten.each do |name| + registry[name] = encoder_class + end + encoder_class + end + + # @return [ClassRegistry] the registry object which allows to register and + # retrieve available encoder classes + def registry + @registry ||= Rackstash::ClassRegistry.new('encoder'.freeze) + end + + # Create a new encoder instance from the specified class and the given + # arguments. The class can be given as an actual class or as the name of + # an encoder in which case we are resolving it to a class registered to + # the {.registry}. + # + # @param encoder_spec [Class, Symbol, String, #encode] a description of + # the class from which we are creating a new filter object. When giving + # a `Class`, we are using it as is to create a new filter object with + # the supplied `args` and `block`. When giving a `String` or `Symbol`, + # we first use the filter registry to find the matching class. With + # that, we then create a filter object as before. When giving an object + # which responds to `call` already (e.g. a `Proc`, we return it + # unchanged, ignoring any additional passed `args`. + # @param args [Array] an optional list of arguments which is passed to the + # initializer for the new filter object. + # @raise [TypeError] if we can not create a new filter object from the + # given `filter_spec`, usually because it is an unsupported type + # @raise [KeyError] if we could not find a filter class in the registry + # for the specified class name + # @return [Object] a new filter object + def build(encoder_spec, *args, &block) + if encoder_spec.respond_to?(:encode) && !encoder_spec.is_a?(String) + encoder_spec + else + registry[encoder_spec].new(*args, &block) + end + end + end + end +end diff --git a/lib/rackstash/encoder/hash.rb b/lib/rackstash/encoder/hash.rb index 2ef701f..2606e71 100644 --- a/lib/rackstash/encoder/hash.rb +++ b/lib/rackstash/encoder/hash.rb @@ -5,6 +5,7 @@ # This software may be modified and distributed under the terms # of the MIT license. See the LICENSE.txt file for details. +require 'rackstash/encoder' require 'rackstash/encoder/helper/message' require 'rackstash/encoder/helper/timestamp' @@ -26,5 +27,7 @@ module Rackstash event end end + + register Hash, :hash end end diff --git a/lib/rackstash/encoder/json.rb b/lib/rackstash/encoder/json.rb index 99edfbf..6beaaa7 100644 --- a/lib/rackstash/encoder/json.rb +++ b/lib/rackstash/encoder/json.rb @@ -7,6 +7,7 @@ require 'json' +require 'rackstash/encoder' require 'rackstash/encoder/helper/message' require 'rackstash/encoder/helper/timestamp' @@ -29,5 +30,7 @@ module Rackstash ::JSON.dump(event) end end + + register JSON, :json, :JSON end end diff --git a/lib/rackstash/encoder/lograge.rb b/lib/rackstash/encoder/lograge.rb index 814ca5f..84b34f8 100644 --- a/lib/rackstash/encoder/lograge.rb +++ b/lib/rackstash/encoder/lograge.rb @@ -5,6 +5,7 @@ # This software may be modified and distributed under the terms # of the MIT license. See the LICENSE.txt file for details. +require 'rackstash/encoder' require 'rackstash/encoder/helper/timestamp' module Rackstash @@ -136,5 +137,7 @@ module Rackstash "#{key}=#{value}" end end + + register Lograge, :lograge end end diff --git a/lib/rackstash/encoder/logstash.rb b/lib/rackstash/encoder/logstash.rb index 953f003..c090cac 100644 --- a/lib/rackstash/encoder/logstash.rb +++ b/lib/rackstash/encoder/logstash.rb @@ -5,6 +5,7 @@ # This software may be modified and distributed under the terms # of the MIT license. See the LICENSE.txt file for details. +require 'rackstash/encoder' require 'rackstash/encoder/json' module Rackstash @@ -26,5 +27,7 @@ module Rackstash super(event) end end + + register Logstash, :logstash end end diff --git a/lib/rackstash/encoder/message.rb b/lib/rackstash/encoder/message.rb index a9ddebe..3904d03 100644 --- a/lib/rackstash/encoder/message.rb +++ b/lib/rackstash/encoder/message.rb @@ -5,6 +5,7 @@ # This software may be modified and distributed under the terms # of the MIT license. See the LICENSE.txt file for details. +require 'rackstash/encoder' require 'rackstash/encoder/helper/message' module Rackstash @@ -70,5 +71,7 @@ module Rackstash end end end + + register Message, :message end end diff --git a/lib/rackstash/encoder/raw.rb b/lib/rackstash/encoder/raw.rb index 18110c9..68febbc 100644 --- a/lib/rackstash/encoder/raw.rb +++ b/lib/rackstash/encoder/raw.rb @@ -5,6 +5,8 @@ # This software may be modified and distributed under the terms # of the MIT license. See the LICENSE.txt file for details. +require 'rackstash/encoder' + module Rackstash module Encoder # The Raw encoder passes along the raw unformatted event hash. It still @@ -20,5 +22,7 @@ module Rackstash event end end + + register Raw, :raw end end diff --git a/spec/rackstash/encoder_spec.rb b/spec/rackstash/encoder_spec.rb new file mode 100644 index 0000000..613a0a1 --- /dev/null +++ b/spec/rackstash/encoder_spec.rb @@ -0,0 +1,88 @@ +# 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 'securerandom' + +require 'rackstash/encoder' + +describe Rackstash::Encoder do + let(:registry) { Rackstash::ClassRegistry.new('encoder') } + + let(:encoder_class) { + Class.new do + def encode(event) + 'encoded' + end + end + } + let(:encoder_name) { :"encoder_class_#{SecureRandom.hex(6)}" } + + describe '.build' do + before do + allow(described_class).to receive(:registry).and_return(registry) + described_class.register(encoder_class, encoder_name) + end + + it 'builds an encoder from a class' do + args = ['arg1', foo: 'bar'] + expect(encoder_class).to receive(:new).with(*args) + + described_class.build(encoder_class, *args) + end + + it 'builds a encoder from a Symbol' do + args = ['arg1', foo: 'bar'] + expect(encoder_class).to receive(:new).with(*args) + + p described_class.registry + + described_class.build(encoder_name.to_sym, *args) + end + + it 'builds a encoder from a String' do + args = ['arg1', foo: 'bar'] + expect(encoder_class).to receive(:new).with(*args) + + described_class.build(encoder_name.to_s, *args) + end + + it 'returns an existing encoder' do + encoder = Class.new do + def encode(event) + 'custom' + end + end.new + + expect(described_class.build(encoder)).to equal encoder + expect(described_class.build(encoder, :ignored, 42)).to equal encoder + end + + it 'raises a TypeError with invalid spec types' do + expect { described_class.build(123) } + .to raise_error(TypeError, '123 can not be used to describe encoders') + expect { described_class.build(nil) } + .to raise_error(TypeError, 'nil can not be used to describe encoders') + expect { described_class.build(true) } + .to raise_error(TypeError, 'true can not be used to describe encoders') + end + + it 'raises a KeyError for undefined encoders' do + expect { described_class.build('MissingEncoder') } + .to raise_error(KeyError, 'No encoder was registered for "MissingEncoder"') + expect { described_class.build(:missing_encoder) } + .to raise_error(KeyError, 'No encoder was registered for :missing_encoder') + end + end + + describe 'registry' do + it 'returns the encoder registry' do + expect(described_class.registry).to be_instance_of Rackstash::ClassRegistry + expect(described_class.registry.object_type).to eql 'encoder' + end + end +end