1
0
mirror of https://github.com/meineerde/rackstash.git synced 2026-01-31 17:27:13 +00:00

Use the ClassRegistry to register filters

This removes the requirement that filters need to be defined in the
Rackstash::Filters namespace in order to be usable with a short name.
Filters can be registered with an arbitrary name.

As before, raw blocks / procs can be defined as ad-hoc filters.
This commit is contained in:
Holger Just 2017-12-20 23:13:34 +01:00
parent 01593a9b8b
commit a475a5c6fc
12 changed files with 134 additions and 92 deletions

View File

@ -172,3 +172,12 @@ require 'rackstash/adapter/file'
require 'rackstash/adapter/logger'
require 'rackstash/adapter/io'
require 'rackstash/adapter/null'
require 'rackstash/filter/clear_color'
require 'rackstash/filter/default_fields'
require 'rackstash/filter/default_tags'
require 'rackstash/filter/drop_if'
require 'rackstash/filter/rename'
require 'rackstash/filter/replace'
require 'rackstash/filter/truncate_message'
require 'rackstash/filter/update'

View File

@ -5,13 +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/filter/clear_color'
require 'rackstash/filter/default_fields'
require 'rackstash/filter/default_tags'
require 'rackstash/filter/drop_if'
require 'rackstash/filter/rename'
require 'rackstash/filter/replace'
require 'rackstash/filter/truncate_message'
require 'rackstash/class_registry'
module Rackstash
# Filters are part of a {Flow} where they can alter the log event before it is
@ -29,57 +23,55 @@ module Rackstash
# A filter can be any object responding to `call`, e.g. a Proc or a concrete
# class inside this module.
module Filter
# Create a new filter instance from the specified class and the given
# arguments. The class can be given as an actual class or as the name of a
# filter in which case we are resolving it to a class defined inside the
# {Rackstash::Filter} namespace.
#
# @param klass [Class, Symbol, String, #call] a description of the class
# from which we are creating a new filter object. When giving a `Class`,
# we are using it as is. When giving a `String` or `Symbol`, we are
# determining the associated class from the {Rackstash::Filter} module
# and create an instance of that. When giving an object which responds to
# `call` already, 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 `class`
# @raise [NameError] if we could not find a filter class for the specified
# class name
# @return [Object] a new filter object
def self.build(klass, *args, &block)
case klass
when Class
klass.new(*args, &block)
when Symbol, String
filter_class_name = klass.to_s
.sub(/^[a-z\d]*/) { $&.capitalize }
.gsub(/(?:_)([a-z\d]*)/) { $1.capitalize }
.to_sym
filter_class = const_get(filter_class_name, false)
filter_class.new(*args, &block)
when ->(filter) { filter.respond_to?(:call) }
klass
else
raise TypeError, "Can not build filter for #{klass.inspect}"
class << self
# @param filter_class [Class] a class from which a new filter can be
# created. Filter objects must respond to `call` and accept an event
# hash.
# @param filter_names [Array<String,Symbol>] one or more names for the
# registered `filter_class`. Using these names, the user can create a
# new filter object from the registered class in {.build}.
# @raise [TypeError] if objects of type were passed
# @return [Class] the passed `filter_class`
def register(filter_class, *filter_names)
filter_names.flatten.each do |name|
registry[name] = filter_class
end
filter_class
end
end
# @return [Hash<Symbol => Class>] a Hash with names of filters and their
# respective classes which can be used with {Filter.build} to create a
# new filter object
def self.known
constants.each_with_object({}) do |const, known|
filter_class = const_get(const, false)
next unless filter_class.is_a?(Class)
# @return [ClassRegistry] the registry object which allows to register and
# retrieve available filter classes
def registry
@registry ||= Rackstash::ClassRegistry.new('filter'.freeze)
end
filter_class_name = const.to_s
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
.downcase
.to_sym
known[filter_class_name] = filter_class
# Create a new filter instance from the specified class and the given
# arguments. The class can be given as an actual class or as the name of a
# filter in which case we are resolving it to a class registered to the
# {.registry}.
#
# @param filter_spec [Class, Symbol, String, #call] 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(filter_spec, *args, &block)
case filter_spec
when ->(filter) { filter.respond_to?(:call) }
filter_spec
else
registry[filter_spec].new(*args, &block)
end
end
end
end

View File

@ -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/filter'
module Rackstash
module Filter
# Remove all ANSI color codes from the `"message"` field of the given event
@ -40,5 +42,7 @@ module Rackstash
event
end
end
register ClearColor, :clear_color
end
end

View File

@ -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/filter'
module Rackstash
module Filter
# The {DefaultFields} filter allows to define fields which should be added
@ -56,5 +58,7 @@ module Rackstash
event.merge!(fields, &resolver)
end
end
register DefaultFields, :default_fields
end
end

View File

@ -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/filter'
module Rackstash
module Filter
# The {DefaultTags} filter allows to define tags which should be added
@ -50,5 +52,7 @@ module Rackstash
event
end
end
register DefaultTags, :default_tags
end
end

View File

@ -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/filter'
module Rackstash
module Filter
# Skip the further processing of the event if the provided condition is
@ -50,5 +52,7 @@ module Rackstash
event
end
end
register DropIf, :drop_if
end
end

View File

@ -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/filter'
module Rackstash
module Filter
# Rename one or more fields in the given event.
@ -38,5 +40,7 @@ module Rackstash
event
end
end
register Rename, :rename
end
end

View File

@ -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/filter'
module Rackstash
module Filter
# Replace fields in the given event with new values. A new value can be
@ -51,5 +53,7 @@ module Rackstash
event
end
end
register Replace, :replace
end
end

View File

@ -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/filter'
module Rackstash
module Filter
# The Truncate filter can be used to restrict the size of the emitted
@ -150,5 +152,7 @@ module Rackstash
messages.inject(0) { |sum, msg| sum + msg.size }
end
end
register TruncateMessage, :truncate_message
end
end

View File

@ -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/filter'
module Rackstash
module Filter
# Update fields in the given event with new values. A new value can be
@ -53,5 +55,7 @@ module Rackstash
event
end
end
register Update, :update
end
end

View File

@ -142,8 +142,9 @@ describe Rackstash::FilterChain do
expect { filter_chain.append(false) }.to raise_error TypeError
expect { filter_chain.append(42) }.to raise_error TypeError
expect { filter_chain.append(:foo) }.to raise_error NameError
expect { filter_chain.append('Foo') }.to raise_error NameError
# Registered filter was not found
expect { filter_chain.append(:foo) }.to raise_error KeyError
expect { filter_chain.append('Foo') }.to raise_error KeyError
expect { filter_chain.append }.to raise_error ArgumentError
end
@ -341,8 +342,9 @@ describe Rackstash::FilterChain do
expect { filter_chain.insert_before(1, false) }.to raise_error TypeError
expect { filter_chain.insert_before(1, 42) }.to raise_error TypeError
expect { filter_chain.insert_before(1, :foo) }.to raise_error NameError
expect { filter_chain.insert_before(1, 'Foo') }.to raise_error NameError
# Registered filter was not found
expect { filter_chain.insert_before(1, :foo) }.to raise_error KeyError
expect { filter_chain.insert_before(1, 'Foo') }.to raise_error KeyError
expect { filter_chain.insert_before(1) }.to raise_error ArgumentError
end
@ -398,8 +400,9 @@ describe Rackstash::FilterChain do
expect { filter_chain.insert_after(1, false) }.to raise_error TypeError
expect { filter_chain.insert_after(1, 42) }.to raise_error TypeError
expect { filter_chain.insert_after(1, :foo) }.to raise_error NameError
expect { filter_chain.insert_after(1, 'Foo') }.to raise_error NameError
# Registered filter was not found
expect { filter_chain.insert_after(1, :foo) }.to raise_error KeyError
expect { filter_chain.insert_after(1, 'Foo') }.to raise_error KeyError
expect { filter_chain.insert_after(1) }.to raise_error ArgumentError
end
@ -455,8 +458,9 @@ describe Rackstash::FilterChain do
expect { filter_chain.unshift(false) }.to raise_error TypeError
expect { filter_chain.unshift(42) }.to raise_error TypeError
expect { filter_chain.unshift(:foo) }.to raise_error NameError
expect { filter_chain.unshift('Foo') }.to raise_error NameError
# Registered filter was not found
expect { filter_chain.unshift(:foo) }.to raise_error KeyError
expect { filter_chain.unshift('Foo') }.to raise_error KeyError
expect { filter_chain.unshift }.to raise_error ArgumentError
end

View File

@ -11,17 +11,23 @@ require 'securerandom'
require 'rackstash/filter'
describe Rackstash::Filter do
let(:filter_class) { Class.new }
let(:random) { SecureRandom.hex(6) }
let(:filter_class_name) { :"FilterClass#{random}" }
let(:registry) { Rackstash::ClassRegistry.new('filter') }
around(:each) do |example|
described_class.const_set(filter_class_name, filter_class)
example.run
described_class.send(:remove_const, filter_class_name)
end
let(:filter_class) {
Class.new do
def call(event)
'filtered'
end
end
}
let(:filter_name) { :"filter_class_#{SecureRandom.hex(6)}" }
describe '.build' do
before do
allow(described_class).to receive(:registry).and_return(registry)
described_class.register(filter_class, filter_name)
end
it 'builds a filter from a class' do
args = ['arg1', foo: 'bar']
expect(filter_class).to receive(:new).with(*args)
@ -33,45 +39,44 @@ describe Rackstash::Filter do
args = ['arg1', foo: 'bar']
expect(filter_class).to receive(:new).with(*args)
described_class.build(:"filter_class#{random}", *args)
described_class.build(filter_name.to_sym, *args)
end
it 'builds a filter from a String' do
args = ['arg1', foo: 'bar']
expect(filter_class).to receive(:new).with(*args)
described_class.build("filter_class#{random}", *args)
described_class.build(filter_name.to_s, *args)
end
it 'returns an existing filter' do
filter = -> {}
expect(described_class.build(filter)).to equal filter
expect(described_class.build(filter, :ignored, 42)).to equal filter
end
it 'raises a TypeError with different arguments' do
expect { described_class.build(123) }.to raise_error(TypeError)
expect { described_class.build(nil) }.to raise_error(TypeError)
expect { described_class.build(true) }.to raise_error(TypeError)
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 filters')
expect { described_class.build(nil) }
.to raise_error(TypeError, 'nil can not be used to describe filters')
expect { described_class.build(true) }
.to raise_error(TypeError, 'true can not be used to describe filters')
end
expect { described_class.build('MissingFilter') }.to raise_error(NameError)
expect { described_class.build(:missing_filter) }.to raise_error(NameError)
it 'raises a KeyError for undefined filters' do
expect { described_class.build('MissingFilter') }
.to raise_error(KeyError, 'No filter was registered for "MissingFilter"')
expect { described_class.build(:missing_filter) }
.to raise_error(KeyError, 'No filter was registered for :missing_filter')
end
end
describe '.known' do
it 'returns a Hash with known filters' do
expect(described_class.known).not_to be_empty
expect(described_class.known.keys).to all(
be_a(Symbol)
.and match(/\A[a-z0-9_]+\z/)
)
expect(described_class.known.values).to all be_a(Class)
end
it 'includes Filter classes' do
expect(described_class.known[:"filter_class#{random}"]).to equal filter_class
describe 'registry' do
it 'returns the filter registry' do
expect(described_class.registry).to be_instance_of Rackstash::ClassRegistry
expect(described_class.registry.object_type).to eql 'filter'
end
end
end