From e0809e643a01e11e88de33b87f7931dd50580bcf Mon Sep 17 00:00:00 2001 From: Brandon Keepers Date: Wed, 8 Jun 2022 13:27:43 -0400 Subject: [PATCH 1/4] Configure instrumenter globally, add Flipper.instrument --- examples/api/custom_memoized.ru | 15 ++++---- examples/api/memoized.ru | 7 ++-- examples/instrumentation.rb | 15 +++----- examples/instrumentation_last_accessed_at.rb | 5 +-- lib/flipper.rb | 3 ++ lib/flipper/adapters/instrumented.rb | 27 +++++-------- lib/flipper/adapters/sync.rb | 2 - lib/flipper/adapters/sync/synchronizer.rb | 6 +-- lib/flipper/cloud/configuration.rb | 19 ++-------- lib/flipper/cloud/dsl.rb | 2 +- lib/flipper/cloud/engine.rb | 5 +-- lib/flipper/cloud/instrumenter.rb | 7 ++-- lib/flipper/configuration.rb | 17 ++++++++- lib/flipper/dsl.rb | 7 +--- lib/flipper/feature.rb | 11 +----- lib/flipper/instrumentation/log_subscriber.rb | 1 + lib/flipper/instrumentation/statsd.rb | 1 + lib/flipper/railtie.rb | 3 +- spec/flipper/adapters/dual_write_spec.rb | 1 + spec/flipper/adapters/failover_spec.rb | 8 ++-- spec/flipper/adapters/instrumented_spec.rb | 6 ++- .../adapters/sync/synchronizer_spec.rb | 6 ++- spec/flipper/adapters/sync_spec.rb | 1 + spec/flipper/cloud/configuration_spec.rb | 6 --- spec/flipper/cloud/engine_spec.rb | 2 +- spec/flipper/cloud_spec.rb | 10 ----- spec/flipper/configuration_spec.rb | 18 +++++++++ spec/flipper/dsl_spec.rb | 38 ++----------------- spec/flipper/feature_spec.rb | 17 +-------- .../instrumentation/log_subscriber_spec.rb | 6 +-- .../instrumentation/statsd_subscriber_spec.rb | 6 +-- spec/flipper/railtie_spec.rb | 2 +- spec/spec_helper.rb | 4 -- 33 files changed, 110 insertions(+), 174 deletions(-) diff --git a/examples/api/custom_memoized.ru b/examples/api/custom_memoized.ru index b22649032..0df3d1cfe 100644 --- a/examples/api/custom_memoized.ru +++ b/examples/api/custom_memoized.ru @@ -10,15 +10,15 @@ # require 'bundler/setup' -require "active_support/notifications" +require 'active_support' +require 'active_support/notifications' require "flipper/api" require "flipper/adapters/pstore" -adapter = Flipper::Adapters::Instrumented.new( - Flipper::Adapters::PStore.new, - instrumenter: ActiveSupport::Notifications, -) -flipper = Flipper.new(adapter) +Flipper.configure do |config| + config.instrumenter ActiveSupport::Notifications + config.adapter { Flipper::Adapters::Instrumented.new(Flipper::Adapters::PStore.new) } +end ActiveSupport::Notifications.subscribe(/.*/, ->(*args) { name, start, finish, id, data = args @@ -31,7 +31,6 @@ ActiveSupport::Notifications.subscribe(/.*/, ->(*args) { # You can uncomment this to get some default data: # flipper[:logging].enable_percentage_of_time 5 -run Flipper::Api.app(flipper) { |builder| - builder.use Flipper::Middleware::SetupEnv, flipper +run Flipper::Api.app { |builder| builder.use Flipper::Middleware::Memoizer, preload: true } diff --git a/examples/api/memoized.ru b/examples/api/memoized.ru index ff3540b92..35c7c03ce 100644 --- a/examples/api/memoized.ru +++ b/examples/api/memoized.ru @@ -10,16 +10,15 @@ # require 'bundler/setup' +require 'active_support' require "active_support/notifications" require "flipper/api" require "flipper/adapters/pstore" Flipper.configure do |config| + config.instrumenter ActiveSupport::Notifications config.adapter { - Flipper::Adapters::Instrumented.new( - Flipper::Adapters::PStore.new, - instrumenter: ActiveSupport::Notifications, - ) + Flipper::Adapters::Instrumented.new(Flipper::Adapters::PStore.new) } end diff --git a/examples/instrumentation.rb b/examples/instrumentation.rb index 76941e39f..1e0993e1c 100644 --- a/examples/instrumentation.rb +++ b/examples/instrumentation.rb @@ -1,5 +1,6 @@ require 'bundler/setup' require 'securerandom' +require 'active_support' require 'active_support/notifications' class FlipperSubscriber @@ -14,17 +15,13 @@ def call(*args) require 'flipper' require 'flipper/adapters/instrumented' -# pick an adapter -adapter = Flipper::Adapters::Memory.new - -# instrument it if you want, if not you still get the feature instrumentation -instrumented = Flipper::Adapters::Instrumented.new(adapter, :instrumenter => ActiveSupport::Notifications) - -# get a handy dsl instance -flipper = Flipper.new(instrumented, :instrumenter => ActiveSupport::Notifications) +Flipper.configure do |config| + config.instrumenter ActiveSupport::Notifications + config.adapter { Flipper::Adapters::Instrumented.new(Flipper::Adapters::Memory.new) } +end # grab a feature -search = flipper[:search] +search = Flipper[:search] perform = lambda do # check if that feature is enabled diff --git a/examples/instrumentation_last_accessed_at.rb b/examples/instrumentation_last_accessed_at.rb index 19b18a1d6..216970d45 100644 --- a/examples/instrumentation_last_accessed_at.rb +++ b/examples/instrumentation_last_accessed_at.rb @@ -1,6 +1,7 @@ # Quick example of how to keep track of when a feature was last checked. require 'bundler/setup' require 'securerandom' +require 'active_support' require 'active_support/notifications' require 'flipper' @@ -20,9 +21,7 @@ def call(name, start, finish, id, payload) end Flipper.configure do |config| - config.default { - Flipper.new(config.adapter, instrumenter: ActiveSupport::Notifications) - } + config.instrumenter ActiveSupport::Notifications end Flipper.enabled?(:search) diff --git a/lib/flipper.rb b/lib/flipper.rb index f7b256424..e90a67752 100644 --- a/lib/flipper.rb +++ b/lib/flipper.rb @@ -68,6 +68,9 @@ def instance=(flipper) :memoize=, :memoizing?, :sync, :sync_secret # For Flipper::Cloud. Will error for OSS Flipper. + def_delegators :configuration, :instrumenter, :instrumenter= + def_delegators :instrumenter, :instrument + # Public: Use this to register a group by name. # # name - The Symbol name of the group. diff --git a/lib/flipper/adapters/instrumented.rb b/lib/flipper/adapters/instrumented.rb index f102769c9..83b1cd297 100644 --- a/lib/flipper/adapters/instrumented.rb +++ b/lib/flipper/adapters/instrumented.rb @@ -10,9 +10,6 @@ class Instrumented < SimpleDelegator # Private: The name of instrumentation events. InstrumentationName = "adapter_operation.#{InstrumentationNamespace}".freeze - # Private: What is used to instrument all the things. - attr_reader :instrumenter - # Public: The name of the adapter. attr_reader :name @@ -20,14 +17,10 @@ class Instrumented < SimpleDelegator # # adapter - Vanilla adapter instance to wrap. # - # options - The Hash of options. - # :instrumenter - What to use to instrument all the things. - # - def initialize(adapter, options = {}) + def initialize(adapter) super(adapter) @adapter = adapter @name = :instrumented - @instrumenter = options.fetch(:instrumenter, Instrumenters::Noop) end # Public @@ -37,7 +30,7 @@ def features adapter_name: @adapter.name, } - @instrumenter.instrument(InstrumentationName, default_payload) do |payload| + Flipper.instrument(InstrumentationName, default_payload) do |payload| payload[:result] = @adapter.features end end @@ -50,7 +43,7 @@ def add(feature) feature_name: feature.name, } - @instrumenter.instrument(InstrumentationName, default_payload) do |payload| + Flipper.instrument(InstrumentationName, default_payload) do |payload| payload[:result] = @adapter.add(feature) end end @@ -63,7 +56,7 @@ def remove(feature) feature_name: feature.name, } - @instrumenter.instrument(InstrumentationName, default_payload) do |payload| + Flipper.instrument(InstrumentationName, default_payload) do |payload| payload[:result] = @adapter.remove(feature) end end @@ -76,7 +69,7 @@ def clear(feature) feature_name: feature.name, } - @instrumenter.instrument(InstrumentationName, default_payload) do |payload| + Flipper.instrument(InstrumentationName, default_payload) do |payload| payload[:result] = @adapter.clear(feature) end end @@ -89,7 +82,7 @@ def get(feature) feature_name: feature.name, } - @instrumenter.instrument(InstrumentationName, default_payload) do |payload| + Flipper.instrument(InstrumentationName, default_payload) do |payload| payload[:result] = @adapter.get(feature) end end @@ -101,7 +94,7 @@ def get_multi(features) feature_names: features.map(&:name), } - @instrumenter.instrument(InstrumentationName, default_payload) do |payload| + Flipper.instrument(InstrumentationName, default_payload) do |payload| payload[:result] = @adapter.get_multi(features) end end @@ -112,7 +105,7 @@ def get_all adapter_name: @adapter.name, } - @instrumenter.instrument(InstrumentationName, default_payload) do |payload| + Flipper.instrument(InstrumentationName, default_payload) do |payload| payload[:result] = @adapter.get_all end end @@ -127,7 +120,7 @@ def enable(feature, gate, thing) thing_value: thing.value, } - @instrumenter.instrument(InstrumentationName, default_payload) do |payload| + Flipper.instrument(InstrumentationName, default_payload) do |payload| payload[:result] = @adapter.enable(feature, gate, thing) end end @@ -142,7 +135,7 @@ def disable(feature, gate, thing) thing_value: thing.value, } - @instrumenter.instrument(InstrumentationName, default_payload) do |payload| + Flipper.instrument(InstrumentationName, default_payload) do |payload| payload[:result] = @adapter.disable(feature, gate, thing) end end diff --git a/lib/flipper/adapters/sync.rb b/lib/flipper/adapters/sync.rb index 89f59d7a6..bc0de7339 100644 --- a/lib/flipper/adapters/sync.rb +++ b/lib/flipper/adapters/sync.rb @@ -29,8 +29,6 @@ def initialize(local, remote, options = {}) sync_options = { raise: false, } - instrumenter = options[:instrumenter] - sync_options[:instrumenter] = instrumenter if instrumenter synchronizer = Synchronizer.new(@local, @remote, sync_options) IntervalSynchronizer.new(synchronizer, interval: options[:interval]) end diff --git a/lib/flipper/adapters/sync/synchronizer.rb b/lib/flipper/adapters/sync/synchronizer.rb index c202ce7f6..e03f90a67 100644 --- a/lib/flipper/adapters/sync/synchronizer.rb +++ b/lib/flipper/adapters/sync/synchronizer.rb @@ -14,18 +14,16 @@ class Synchronizer # remote - The Flipper adapter that is source of truth that the local # adapter should be brought in line with. # options - The Hash of options. - # :instrumenter - The instrumenter used to instrument. # :raise - Should errors be raised (default: true). def initialize(local, remote, options = {}) @local = local @remote = remote - @instrumenter = options.fetch(:instrumenter, Instrumenters::Noop) @raise = options.fetch(:raise, true) end # Public: Forces a sync. def call - @instrumenter.instrument("synchronizer_call.flipper") { sync } + Flipper.instrument("synchronizer_call.flipper") { sync } end private @@ -54,7 +52,7 @@ def sync nil rescue => exception - @instrumenter.instrument("synchronizer_exception.flipper", exception: exception) + Flipper.instrument("synchronizer_exception.flipper", exception: exception) raise if @raise end end diff --git a/lib/flipper/cloud/configuration.rb b/lib/flipper/cloud/configuration.rb index 45eb5ad3f..b1d64d11c 100644 --- a/lib/flipper/cloud/configuration.rb +++ b/lib/flipper/cloud/configuration.rb @@ -41,14 +41,6 @@ class Configuration # configuration.debug_output = STDOUT attr_accessor :debug_output - # Public: Instrumenter to use for the Flipper instance returned by - # Flipper::Cloud.new (default: Flipper::Instrumenters::Noop). - # - # # for example, to use active support notifications you could do: - # configuration = Flipper::Cloud::Configuration.new - # configuration.instrumenter = ActiveSupport::Notifications - attr_accessor :instrumenter - # Public: Local adapter that all reads should go to in order to ensure # latency is low and resiliency is high. This adapter is automatically # kept in sync with cloud. @@ -88,14 +80,11 @@ def initialize(options = {}) @adapter_block = ->(adapter) { adapter } self.url = options.fetch(:url) { ENV.fetch("FLIPPER_CLOUD_URL", DEFAULT_URL) } - instrumenter = options.fetch(:instrumenter, Instrumenters::Noop) - # This is alpha. Don't use this unless you are me. And you are not me. cloud_instrument = options.fetch(:cloud_instrument) { ENV["FLIPPER_CLOUD_INSTRUMENT"] == "1" } - @instrumenter = if cloud_instrument - Instrumenter.new(brow: brow, instrumenter: instrumenter) - else - instrumenter + if cloud_instrument + # FIXME: replace with a subscriber to AS::Notifications + Flipper.instrumenter Instrumenter.new(brow: brow, instrumenter: Flipper.instrumenter) end end @@ -123,7 +112,6 @@ def adapter(&block) def sync Flipper::Adapters::Sync::Synchronizer.new(local_adapter, http_adapter, { - instrumenter: instrumenter, interval: sync_interval, }).call end @@ -168,7 +156,6 @@ def dual_write_adapter def sync_adapter Flipper::Adapters::Sync.new(local_adapter, http_adapter, { - instrumenter: instrumenter, interval: sync_interval, }) end diff --git a/lib/flipper/cloud/dsl.rb b/lib/flipper/cloud/dsl.rb index c9ceb2c1c..85f94ebd5 100644 --- a/lib/flipper/cloud/dsl.rb +++ b/lib/flipper/cloud/dsl.rb @@ -7,7 +7,7 @@ class DSL < SimpleDelegator def initialize(cloud_configuration) @cloud_configuration = cloud_configuration - super Flipper.new(@cloud_configuration.adapter, instrumenter: @cloud_configuration.instrumenter) + super Flipper.new(@cloud_configuration.adapter) end def sync diff --git a/lib/flipper/cloud/engine.rb b/lib/flipper/cloud/engine.rb index 60f2636a8..691781f00 100644 --- a/lib/flipper/cloud/engine.rb +++ b/lib/flipper/cloud/engine.rb @@ -13,10 +13,7 @@ class Engine < Rails::Engine Flipper.configure do |config| config.default do if ENV["FLIPPER_CLOUD_TOKEN"] - Flipper::Cloud.new( - local_adapter: config.adapter, - instrumenter: app.config.flipper.instrumenter - ) + Flipper::Cloud.new(local_adapter: config.adapter) else warn "Missing FLIPPER_CLOUD_TOKEN environment variable. Disabling Flipper::Cloud." Flipper.new(config.adapter) diff --git a/lib/flipper/cloud/instrumenter.rb b/lib/flipper/cloud/instrumenter.rb index 8d059111b..647a38a8a 100644 --- a/lib/flipper/cloud/instrumenter.rb +++ b/lib/flipper/cloud/instrumenter.rb @@ -4,14 +4,13 @@ module Flipper module Cloud class Instrumenter < SimpleDelegator - def initialize(options = {}) + def initialize(instrumenter: Flipper.instrumenter) @brow = options.fetch(:brow) - @instrumenter = options.fetch(:instrumenter, Instrumenters::Noop) - super @instrumenter + super instrumenter end def instrument(name, payload = {}, &block) - result = @instrumenter.instrument(name, payload, &block) + result = Flipper.instrument(name, payload, &block) push name, payload result end diff --git a/lib/flipper/configuration.rb b/lib/flipper/configuration.rb index 53241768e..dc7f6c2e0 100644 --- a/lib/flipper/configuration.rb +++ b/lib/flipper/configuration.rb @@ -1,8 +1,11 @@ module Flipper class Configuration + attr_accessor :instrumenter + def initialize(options = {}) - @default = -> { Flipper.new(adapter) } - @adapter = -> { Flipper::Adapters::Memory.new } + adapter { Flipper::Adapters::Memory.new } + default { Flipper.new(adapter) } + instrumenter Flipper::Instrumenters::Noop end # The default adapter to use. @@ -54,5 +57,15 @@ def default(&block) @default.call end end + + # Configure or fetch the current instrumenter + # + # Flipper.configure do |config| + # config.instrumenter ActiveSupport::Notifications + # end + def instrumenter(new_value = @instrumenter) + @instrumenter = new_value + end + alias :instrumenter= :instrumenter end end diff --git a/lib/flipper/dsl.rb b/lib/flipper/dsl.rb index 88b82b045..abcfe99c6 100644 --- a/lib/flipper/dsl.rb +++ b/lib/flipper/dsl.rb @@ -7,19 +7,14 @@ class DSL # Private attr_reader :adapter - # Private: What is being used to instrument all the things. - attr_reader :instrumenter - def_delegators :@adapter, :memoize=, :memoizing? # Public: Returns a new instance of the DSL. # # adapter - The adapter that this DSL instance should use. # options - The Hash of options. - # :instrumenter - What should be used to instrument all the things. # :memoize - Should adapter be wrapped by memoize adapter or not. def initialize(adapter, options = {}) - @instrumenter = options.fetch(:instrumenter, Instrumenters::Noop) memoize = options.fetch(:memoize, true) adapter = Adapters::Memoizable.new(adapter) if memoize @adapter = adapter @@ -181,7 +176,7 @@ def feature(name) raise ArgumentError, "#{name} must be a String or Symbol" end - @memoized_features[name.to_sym] ||= Feature.new(name, @adapter, instrumenter: instrumenter) + @memoized_features[name.to_sym] ||= Feature.new(name, @adapter) end # Public: Preload the features with the given names. diff --git a/lib/flipper/feature.rb b/lib/flipper/feature.rb index a43e212d1..84395de99 100644 --- a/lib/flipper/feature.rb +++ b/lib/flipper/feature.rb @@ -18,21 +18,14 @@ class Feature # Private: The adapter this feature should use. attr_reader :adapter - # Private: What is being used to instrument all the things. - attr_reader :instrumenter - # Internal: Initializes a new feature instance. # # name - The Symbol or String name of the feature. # adapter - The adapter that will be used to store details about this feature. # - # options - The Hash of options. - # :instrumenter - What to use to instrument all the things. - # - def initialize(name, adapter, options = {}) + def initialize(name, adapter) @name = name @key = name.to_s - @instrumenter = options.fetch(:instrumenter, Instrumenters::Noop) @adapter = adapter end @@ -369,7 +362,7 @@ def gate_for(thing) # Private: Instrument a feature operation. def instrument(operation) - @instrumenter.instrument(InstrumentationName) do |payload| + Flipper.instrument(InstrumentationName) do |payload| payload[:feature_name] = name payload[:operation] = operation payload[:result] = yield(payload) if block_given? diff --git a/lib/flipper/instrumentation/log_subscriber.rb b/lib/flipper/instrumentation/log_subscriber.rb index df06252a9..9ac77f64f 100644 --- a/lib/flipper/instrumentation/log_subscriber.rb +++ b/lib/flipper/instrumentation/log_subscriber.rb @@ -1,4 +1,5 @@ require 'securerandom' +require 'active_support' require 'active_support/notifications' require 'active_support/log_subscriber' diff --git a/lib/flipper/instrumentation/statsd.rb b/lib/flipper/instrumentation/statsd.rb index d88cc8137..15fafe8c0 100644 --- a/lib/flipper/instrumentation/statsd.rb +++ b/lib/flipper/instrumentation/statsd.rb @@ -1,4 +1,5 @@ require 'securerandom' +require 'active_support' require 'active_support/notifications' require 'flipper/instrumentation/statsd_subscriber' diff --git a/lib/flipper/railtie.rb b/lib/flipper/railtie.rb index 9a020248b..f8e6cbc28 100644 --- a/lib/flipper/railtie.rb +++ b/lib/flipper/railtie.rb @@ -18,8 +18,9 @@ class Railtie < Rails::Railtie initializer "flipper.default", before: :load_config_initializers do |app| Flipper.configure do |config| + config.instrumenter app.config.flipper.instrumenter config.default do - Flipper.new(config.adapter, instrumenter: app.config.flipper.instrumenter) + Flipper.new(config.adapter) end end end diff --git a/spec/flipper/adapters/dual_write_spec.rb b/spec/flipper/adapters/dual_write_spec.rb index 56cb0c6ff..cf6602b88 100644 --- a/spec/flipper/adapters/dual_write_spec.rb +++ b/spec/flipper/adapters/dual_write_spec.rb @@ -1,5 +1,6 @@ require 'flipper/adapters/dual_write' require 'flipper/adapters/operation_logger' +require 'active_support' require 'active_support/notifications' RSpec.describe Flipper::Adapters::DualWrite do diff --git a/spec/flipper/adapters/failover_spec.rb b/spec/flipper/adapters/failover_spec.rb index 34d1dd4cc..cc17c8a4e 100644 --- a/spec/flipper/adapters/failover_spec.rb +++ b/spec/flipper/adapters/failover_spec.rb @@ -84,21 +84,19 @@ context 'when primary is instrumented and fails' do before do allow(memory_adapter).to receive(:get).and_raise(Net::ReadTimeout) + Flipper.instrumenter = instrumenter end let(:memory_adapter) { Flipper::Adapters::Memory.new } let(:primary) do - Flipper::Adapters::Instrumented.new( - memory_adapter, - instrumenter: instrumenter, - ) + Flipper::Adapters::Instrumented.new(memory_adapter) end let(:instrumenter) { Flipper::Instrumenters::Memory.new } it 'logs the raised exception' do flipper[:flag].enabled? - expect(instrumenter.events.count).to be 1 + expect(instrumenter.events.count).to be(2) payload = instrumenter.events[0].payload expect(payload.keys).to include(:exception, :exception_object) diff --git a/spec/flipper/adapters/instrumented_spec.rb b/spec/flipper/adapters/instrumented_spec.rb index bfa71f534..f91108a78 100644 --- a/spec/flipper/adapters/instrumented_spec.rb +++ b/spec/flipper/adapters/instrumented_spec.rb @@ -10,8 +10,12 @@ let(:gate) { feature.gate(:percentage_of_actors) } let(:thing) { flipper.actors(22) } + before do + Flipper.instrumenter = instrumenter + end + subject do - described_class.new(adapter, instrumenter: instrumenter) + described_class.new(adapter) end it_should_behave_like 'a flipper adapter' diff --git a/spec/flipper/adapters/sync/synchronizer_spec.rb b/spec/flipper/adapters/sync/synchronizer_spec.rb index 37049bcca..9ae563020 100644 --- a/spec/flipper/adapters/sync/synchronizer_spec.rb +++ b/spec/flipper/adapters/sync/synchronizer_spec.rb @@ -9,7 +9,11 @@ let(:remote_flipper) { Flipper.new(remote) } let(:instrumenter) { Flipper::Instrumenters::Memory.new } - subject { described_class.new(local, remote, instrumenter: instrumenter) } + subject { described_class.new(local, remote) } + + before do + Flipper.instrumenter = instrumenter + end it "instruments call" do subject.call diff --git a/spec/flipper/adapters/sync_spec.rb b/spec/flipper/adapters/sync_spec.rb index db4b8da82..91b9c5a4c 100644 --- a/spec/flipper/adapters/sync_spec.rb +++ b/spec/flipper/adapters/sync_spec.rb @@ -1,5 +1,6 @@ require 'flipper/adapters/sync' require 'flipper/adapters/operation_logger' +require 'active_support' require 'active_support/notifications' RSpec.describe Flipper::Adapters::Sync do diff --git a/spec/flipper/cloud/configuration_spec.rb b/spec/flipper/cloud/configuration_spec.rb index 5599b7917..0241fa2e3 100644 --- a/spec/flipper/cloud/configuration_spec.rb +++ b/spec/flipper/cloud/configuration_spec.rb @@ -17,12 +17,6 @@ expect(instance.token).to eq("from_env") end - it "can set instrumenter" do - instrumenter = Object.new - instance = described_class.new(required_options.merge(instrumenter: instrumenter)) - expect(instance.instrumenter).to be(instrumenter) - end - it "can set read_timeout" do instance = described_class.new(required_options.merge(read_timeout: 5)) expect(instance.read_timeout).to eq(5) diff --git a/spec/flipper/cloud/engine_spec.rb b/spec/flipper/cloud/engine_spec.rb index 76ebeed9d..ca74640b2 100644 --- a/spec/flipper/cloud/engine_spec.rb +++ b/spec/flipper/cloud/engine_spec.rb @@ -32,7 +32,7 @@ application.initialize! expect(Flipper.instance).to be_a(Flipper::Cloud::DSL) - expect(Flipper.instance.instrumenter).to be(ActiveSupport::Notifications) + expect(Flipper.instrumenter).to be(ActiveSupport::Notifications) end context "with CLOUD_SYNC_SECRET" do diff --git a/spec/flipper/cloud_spec.rb b/spec/flipper/cloud_spec.rb index c0f2c203e..6a7beb56b 100644 --- a/spec/flipper/cloud_spec.rb +++ b/spec/flipper/cloud_spec.rb @@ -41,10 +41,6 @@ headers = @http_client.instance_variable_get('@headers') expect(headers['Flipper-Cloud-Token']).to eq(token) end - - it 'uses noop instrumenter' do - expect(@instance.instrumenter).to be(Flipper::Instrumenters::Noop) - end end context 'initialize with token and options' do @@ -71,12 +67,6 @@ expect(described_class.new).to be_instance_of(Flipper::Cloud::DSL) end - it 'can set instrumenter' do - instrumenter = Flipper::Instrumenters::Memory.new - instance = described_class.new(token: 'asdf', instrumenter: instrumenter) - expect(instance.instrumenter).to be(instrumenter) - end - it 'allows wrapping adapter with another adapter like the instrumenter' do instance = described_class.new(token: 'asdf') do |config| config.adapter do |adapter| diff --git a/spec/flipper/configuration_spec.rb b/spec/flipper/configuration_spec.rb index 63c322f71..4f6b77ab3 100644 --- a/spec/flipper/configuration_spec.rb +++ b/spec/flipper/configuration_spec.rb @@ -30,4 +30,22 @@ expect(subject.default).to be(instance) end end + + describe '#instrumenter' do + it 'defaults to noop' do + expect(subject.instrumenter).to be(Flipper::Instrumenters::Noop) + end + + it 'overrides default instrumenter' do + instrumenter = double('Instrumentor', instrument: nil) + subject.instrumenter instrumenter + expect(subject.instrumenter).to be(instrumenter) + end + + it 'overrides instrumenter with =' do + instrumenter = double('Instrumentor', instrument: nil) + subject.instrumenter = instrumenter + expect(subject.instrumenter).to be(instrumenter) + end + end end diff --git a/spec/flipper/dsl_spec.rb b/spec/flipper/dsl_spec.rb index 071e2fbed..3f8a752be 100644 --- a/spec/flipper/dsl_spec.rb +++ b/spec/flipper/dsl_spec.rb @@ -20,34 +20,18 @@ expect(dsl.adapter).to be(adapter) end end - - it 'defaults instrumenter to noop' do - dsl = described_class.new(adapter) - expect(dsl.instrumenter).to be(Flipper::Instrumenters::Noop) - end - - context 'with overriden instrumenter' do - let(:instrumenter) { double('Instrumentor', instrument: nil) } - - it 'overrides default instrumenter' do - dsl = described_class.new(adapter, instrumenter: instrumenter) - expect(dsl.instrumenter).to be(instrumenter) - end - end end describe '#feature' do it_should_behave_like 'a DSL feature' do let(:method_name) { :feature } - let(:instrumenter) { double('Instrumentor', instrument: nil) } let(:feature) { dsl.send(method_name, :stats) } - let(:dsl) { described_class.new(adapter, instrumenter: instrumenter) } + let(:dsl) { described_class.new(adapter) } end end describe '#preload' do - let(:instrumenter) { double('Instrumentor', instrument: nil) } - let(:dsl) { described_class.new(adapter, instrumenter: instrumenter) } + let(:dsl) { described_class.new(adapter) } let(:names) { %i(stats shiny) } let(:features) { dsl.preload(names) } @@ -65,12 +49,6 @@ end end - it 'sets instrumenter' do - features.each do |feature| - expect(feature.instrumenter).to eq(dsl.instrumenter) - end - end - it 'memoizes the feature' do features.each do |feature| expect(dsl.feature(feature.name)).to equal(feature) @@ -79,10 +57,9 @@ end describe '#preload_all' do - let(:instrumenter) { double('Instrumentor', instrument: nil) } let(:dsl) do names.each { |name| adapter.add subject[name] } - described_class.new(adapter, instrumenter: instrumenter) + described_class.new(adapter) end let(:names) { %i(stats shiny) } let(:features) { dsl.preload_all } @@ -101,12 +78,6 @@ end end - it 'sets instrumenter' do - features.each do |feature| - expect(feature.instrumenter).to eq(dsl.instrumenter) - end - end - it 'memoizes the feature' do features.each do |feature| expect(dsl.feature(feature.name)).to equal(feature) @@ -117,9 +88,8 @@ describe '#[]' do it_should_behave_like 'a DSL feature' do let(:method_name) { :[] } - let(:instrumenter) { double('Instrumentor', instrument: nil) } let(:feature) { dsl.send(method_name, :stats) } - let(:dsl) { described_class.new(adapter, instrumenter: instrumenter) } + let(:dsl) { described_class.new(adapter) } end end diff --git a/spec/flipper/feature_spec.rb b/spec/flipper/feature_spec.rb index c3b6ad451..090c130fc 100644 --- a/spec/flipper/feature_spec.rb +++ b/spec/flipper/feature_spec.rb @@ -16,20 +16,6 @@ feature = described_class.new(:search, adapter) expect(feature.adapter).to eq(adapter) end - - it 'defaults instrumenter' do - feature = described_class.new(:search, adapter) - expect(feature.instrumenter).to be(Flipper::Instrumenters::Noop) - end - - context 'with overriden instrumenter' do - let(:instrumenter) { double('Instrumentor', instrument: nil) } - - it 'overrides default instrumenter' do - feature = described_class.new(:search, adapter, instrumenter: instrumenter) - expect(feature.instrumenter).to be(instrumenter) - end - end end describe '#to_s' do @@ -144,7 +130,8 @@ let(:instrumenter) { Flipper::Instrumenters::Memory.new } subject do - described_class.new(:search, adapter, instrumenter: instrumenter) + Flipper.instrumenter = instrumenter + described_class.new(:search, adapter) end it 'is recorded for enable' do diff --git a/spec/flipper/instrumentation/log_subscriber_spec.rb b/spec/flipper/instrumentation/log_subscriber_spec.rb index 8c542e671..92ff4cf77 100644 --- a/spec/flipper/instrumentation/log_subscriber_spec.rb +++ b/spec/flipper/instrumentation/log_subscriber_spec.rb @@ -4,14 +4,14 @@ RSpec.describe Flipper::Instrumentation::LogSubscriber do let(:adapter) do - memory = Flipper::Adapters::Memory.new - Flipper::Adapters::Instrumented.new(memory, instrumenter: ActiveSupport::Notifications) + Flipper::Adapters::Instrumented.new(Flipper::Adapters::Memory.new) end let(:flipper) do - Flipper.new(adapter, instrumenter: ActiveSupport::Notifications) + Flipper.new(adapter) end before do + Flipper.instrumenter = ActiveSupport::Notifications Flipper.register(:admins) do |thing| thing.respond_to?(:admin?) && thing.admin? end diff --git a/spec/flipper/instrumentation/statsd_subscriber_spec.rb b/spec/flipper/instrumentation/statsd_subscriber_spec.rb index 30cd665e1..8c9f73ba3 100644 --- a/spec/flipper/instrumentation/statsd_subscriber_spec.rb +++ b/spec/flipper/instrumentation/statsd_subscriber_spec.rb @@ -6,16 +6,16 @@ let(:statsd_client) { Statsd.new } let(:socket) { FakeUDPSocket.new } let(:adapter) do - memory = Flipper::Adapters::Memory.new - Flipper::Adapters::Instrumented.new(memory, instrumenter: ActiveSupport::Notifications) + Flipper::Adapters::Instrumented.new(Flipper::Adapters::Memory.new) end let(:flipper) do - Flipper.new(adapter, instrumenter: ActiveSupport::Notifications) + Flipper.new(adapter) end let(:user) { user = Flipper::Actor.new('1') } before do + Flipper.instrumenter = ActiveSupport::Notifications described_class.client = statsd_client Thread.current[:statsd_socket] = socket end diff --git a/spec/flipper/railtie_spec.rb b/spec/flipper/railtie_spec.rb index abce0ae4a..f67877ab0 100644 --- a/spec/flipper/railtie_spec.rb +++ b/spec/flipper/railtie_spec.rb @@ -27,7 +27,7 @@ it "configures instrumentor on default instance" do subject # initialize - expect(Flipper.instance.instrumenter).to eq(ActiveSupport::Notifications) + expect(Flipper.instrumenter).to eq(ActiveSupport::Notifications) end it 'uses Memoizer middleware if config.memoize = true' do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d89742c25..8c2b8cd09 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -77,10 +77,6 @@ expect(feature.adapter.name).to eq(dsl.adapter.name) end - it 'sets instrumenter' do - expect(feature.instrumenter).to eq(dsl.instrumenter) - end - it 'memoizes the feature' do expect(dsl.send(method_name, :stats)).to equal(feature) end From f605c8610d88162c9b22836064c5930ddc07a03d Mon Sep 17 00:00:00 2001 From: Brandon Keepers Date: Wed, 8 Jun 2022 13:57:45 -0400 Subject: [PATCH 2/4] Refactor cloud to use subscriber instead of custom instrumenter --- lib/flipper/cloud/configuration.rb | 9 ++++++--- .../cloud_subscriber.rb} | 17 ++++++----------- lib/flipper/instrumenters/memory.rb | 5 +++++ lib/flipper/instrumenters/noop.rb | 4 ++++ 4 files changed, 21 insertions(+), 14 deletions(-) rename lib/flipper/{cloud/instrumenter.rb => instrumentation/cloud_subscriber.rb} (64%) diff --git a/lib/flipper/cloud/configuration.rb b/lib/flipper/cloud/configuration.rb index b1d64d11c..a8a690662 100644 --- a/lib/flipper/cloud/configuration.rb +++ b/lib/flipper/cloud/configuration.rb @@ -3,7 +3,6 @@ require "flipper/adapters/memory" require "flipper/adapters/dual_write" require "flipper/adapters/sync" -require "flipper/cloud/instrumenter" require "flipper/cloud/registry" require "brow" @@ -83,8 +82,12 @@ def initialize(options = {}) # This is alpha. Don't use this unless you are me. And you are not me. cloud_instrument = options.fetch(:cloud_instrument) { ENV["FLIPPER_CLOUD_INSTRUMENT"] == "1" } if cloud_instrument - # FIXME: replace with a subscriber to AS::Notifications - Flipper.instrumenter Instrumenter.new(brow: brow, instrumenter: Flipper.instrumenter) + require 'flipper/instrumentation/cloud_subscriber' + + Flipper.instrumenter.subscribe( + Flipper::Feature::InstrumentationName, + Flipper::Instrumentation::CloudSubscriber.new(brow) + ) end end diff --git a/lib/flipper/cloud/instrumenter.rb b/lib/flipper/instrumentation/cloud_subscriber.rb similarity index 64% rename from lib/flipper/cloud/instrumenter.rb rename to lib/flipper/instrumentation/cloud_subscriber.rb index 647a38a8a..40a61ce22 100644 --- a/lib/flipper/cloud/instrumenter.rb +++ b/lib/flipper/instrumentation/cloud_subscriber.rb @@ -1,18 +1,13 @@ -require "delegate" -require "flipper/instrumenters/noop" - module Flipper - module Cloud - class Instrumenter < SimpleDelegator - def initialize(instrumenter: Flipper.instrumenter) - @brow = options.fetch(:brow) - super instrumenter + module Instrumentation + # Report the result of feature checks to Flipper Cloud. + class CloudSubscriber + def initialize(brow) + @brow = brow end - def instrument(name, payload = {}, &block) - result = Flipper.instrument(name, payload, &block) + def call(name, start, finish, id, payload) push name, payload - result end private diff --git a/lib/flipper/instrumenters/memory.rb b/lib/flipper/instrumenters/memory.rb index b605f1b68..6c40783ed 100644 --- a/lib/flipper/instrumenters/memory.rb +++ b/lib/flipper/instrumenters/memory.rb @@ -26,6 +26,11 @@ def instrument(name, payload = {}) @events << Event.new(name, payload, result) end + def self.subscribe(_name, _subscriber) + # noop + end + + def events_by_name(name) @events.select { |event| event.name == name } end diff --git a/lib/flipper/instrumenters/noop.rb b/lib/flipper/instrumenters/noop.rb index cd252431a..02c2d810f 100644 --- a/lib/flipper/instrumenters/noop.rb +++ b/lib/flipper/instrumenters/noop.rb @@ -4,6 +4,10 @@ class Noop def self.instrument(_name, payload = {}) yield payload if block_given? end + + def self.subscribe(_name, _subscriber) + # noop + end end end end From b995c129365c8f0414233696acf50c2c26faa568 Mon Sep 17 00:00:00 2001 From: Brandon Keepers Date: Wed, 8 Jun 2022 14:40:53 -0400 Subject: [PATCH 3/4] Properly deprecate instrumenter option --- lib/flipper.rb | 9 +++++++++ lib/flipper/adapters/instrumented.rb | 4 +++- lib/flipper/adapters/sync.rb | 2 ++ lib/flipper/adapters/sync/synchronizer.rb | 3 +++ lib/flipper/cloud/configuration.rb | 3 +++ lib/flipper/configuration.rb | 2 -- lib/flipper/deprecated_instrumenter.rb | 18 ++++++++++++++++++ lib/flipper/dsl.rb | 2 ++ lib/flipper/feature.rb | 5 ++++- lib/flipper/instrumenters/memory.rb | 3 +-- lib/flipper/instrumenters/noop.rb | 2 +- .../flipper/adapters/sync/synchronizer_spec.rb | 5 +---- spec/spec_helper.rb | 8 ++++++++ 13 files changed, 55 insertions(+), 11 deletions(-) create mode 100644 lib/flipper/deprecated_instrumenter.rb diff --git a/lib/flipper.rb b/lib/flipper.rb index e90a67752..fca5df103 100644 --- a/lib/flipper.rb +++ b/lib/flipper.rb @@ -68,7 +68,15 @@ def instance=(flipper) :memoize=, :memoizing?, :sync, :sync_secret # For Flipper::Cloud. Will error for OSS Flipper. + # Public: Get or set the configured instrumenter. def_delegators :configuration, :instrumenter, :instrumenter= + + # Public: Instrument a call using the configured instrumenter. + # + # Flipper.instrument(name, payload) do |payload| + # payload[:result] = something + # end + # def_delegators :instrumenter, :instrument # Public: Use this to register a group by name. @@ -143,6 +151,7 @@ def groups_registry=(registry) end end +require 'flipper/deprecated_instrumenter' require 'flipper/actor' require 'flipper/adapter' require 'flipper/adapters/memoizable' diff --git a/lib/flipper/adapters/instrumented.rb b/lib/flipper/adapters/instrumented.rb index 83b1cd297..8e93a9888 100644 --- a/lib/flipper/adapters/instrumented.rb +++ b/lib/flipper/adapters/instrumented.rb @@ -6,6 +6,7 @@ module Adapters # operations. class Instrumented < SimpleDelegator include ::Flipper::Adapter + include DeprecatedInstrumenter # Private: The name of instrumentation events. InstrumentationName = "adapter_operation.#{InstrumentationNamespace}".freeze @@ -17,8 +18,9 @@ class Instrumented < SimpleDelegator # # adapter - Vanilla adapter instance to wrap. # - def initialize(adapter) + def initialize(adapter, options = {}) super(adapter) + deprecated_instrumenter_option options @adapter = adapter @name = :instrumented end diff --git a/lib/flipper/adapters/sync.rb b/lib/flipper/adapters/sync.rb index bc0de7339..f3bdb14f5 100644 --- a/lib/flipper/adapters/sync.rb +++ b/lib/flipper/adapters/sync.rb @@ -7,6 +7,7 @@ module Adapters # rather than in the main thread only when reads happen. class Sync include ::Flipper::Adapter + include DeprecatedInstrumenter # Public: The name of the adapter. attr_reader :name @@ -22,6 +23,7 @@ class Sync # interval - The Float or Integer number of seconds between syncs from # remote to local. Default value is set in IntervalSynchronizer. def initialize(local, remote, options = {}) + deprecated_instrumenter_option options @name = :sync @local = local @remote = remote diff --git a/lib/flipper/adapters/sync/synchronizer.rb b/lib/flipper/adapters/sync/synchronizer.rb index e03f90a67..6403700f7 100644 --- a/lib/flipper/adapters/sync/synchronizer.rb +++ b/lib/flipper/adapters/sync/synchronizer.rb @@ -8,6 +8,8 @@ class Sync # Public: Given a local and remote adapter, it can update the local to # match the remote doing only the necessary enable/disable operations. class Synchronizer + include DeprecatedInstrumenter + # Public: Initializes a new synchronizer. # # local - The Flipper adapter to get in sync with the remote. @@ -16,6 +18,7 @@ class Synchronizer # options - The Hash of options. # :raise - Should errors be raised (default: true). def initialize(local, remote, options = {}) + deprecated_instrumenter_option options @local = local @remote = remote @raise = options.fetch(:raise, true) diff --git a/lib/flipper/cloud/configuration.rb b/lib/flipper/cloud/configuration.rb index a8a690662..2e6137141 100644 --- a/lib/flipper/cloud/configuration.rb +++ b/lib/flipper/cloud/configuration.rb @@ -9,6 +9,8 @@ module Flipper module Cloud class Configuration + include DeprecatedInstrumenter + # The set of valid ways that syncing can happpen. VALID_SYNC_METHODS = Set[ :poll, @@ -58,6 +60,7 @@ class Configuration attr_accessor :sync_secret def initialize(options = {}) + deprecated_instrumenter_option options @token = options.fetch(:token) { ENV["FLIPPER_CLOUD_TOKEN"] } if @token.nil? diff --git a/lib/flipper/configuration.rb b/lib/flipper/configuration.rb index dc7f6c2e0..e5a497dff 100644 --- a/lib/flipper/configuration.rb +++ b/lib/flipper/configuration.rb @@ -1,7 +1,5 @@ module Flipper class Configuration - attr_accessor :instrumenter - def initialize(options = {}) adapter { Flipper::Adapters::Memory.new } default { Flipper.new(adapter) } diff --git a/lib/flipper/deprecated_instrumenter.rb b/lib/flipper/deprecated_instrumenter.rb new file mode 100644 index 000000000..705672f14 --- /dev/null +++ b/lib/flipper/deprecated_instrumenter.rb @@ -0,0 +1,18 @@ +module Flipper + # Private: Deprecation warnings for instrumenter option + module DeprecatedInstrumenter + def deprecated_instrumenter_option options + if options.has_key?(:instrumenter) + warn "The `:instrumenter` option is deprecated and has no effect. Set `Flipper.instrumenter` globally." + warn caller[1] + end + end + + def instrumenter + warn "`#instrumenter` is deprecated. Use `Flipper.instrument` or `Flipper.instrumenter` instead." + warn caller[0] + + Flipper.instrumenter + end + end +end diff --git a/lib/flipper/dsl.rb b/lib/flipper/dsl.rb index abcfe99c6..5d3303c66 100644 --- a/lib/flipper/dsl.rb +++ b/lib/flipper/dsl.rb @@ -3,6 +3,7 @@ module Flipper class DSL extend Forwardable + include DeprecatedInstrumenter # Private attr_reader :adapter @@ -15,6 +16,7 @@ class DSL # options - The Hash of options. # :memoize - Should adapter be wrapped by memoize adapter or not. def initialize(adapter, options = {}) + deprecated_instrumenter_option options memoize = options.fetch(:memoize, true) adapter = Adapters::Memoizable.new(adapter) if memoize @adapter = adapter diff --git a/lib/flipper/feature.rb b/lib/flipper/feature.rb index 84395de99..918210e85 100644 --- a/lib/flipper/feature.rb +++ b/lib/flipper/feature.rb @@ -6,6 +6,8 @@ module Flipper class Feature + include DeprecatedInstrumenter + # Private: The name of feature instrumentation events. InstrumentationName = "feature_operation.#{InstrumentationNamespace}".freeze @@ -23,7 +25,8 @@ class Feature # name - The Symbol or String name of the feature. # adapter - The adapter that will be used to store details about this feature. # - def initialize(name, adapter) + def initialize(name, adapter, options = {}) + deprecated_instrumenter_option options @name = name @key = name.to_s @adapter = adapter diff --git a/lib/flipper/instrumenters/memory.rb b/lib/flipper/instrumenters/memory.rb index 6c40783ed..4ac7b238c 100644 --- a/lib/flipper/instrumenters/memory.rb +++ b/lib/flipper/instrumenters/memory.rb @@ -26,11 +26,10 @@ def instrument(name, payload = {}) @events << Event.new(name, payload, result) end - def self.subscribe(_name, _subscriber) + def self.subscribe(_name, _callback = nil, &_block) # noop end - def events_by_name(name) @events.select { |event| event.name == name } end diff --git a/lib/flipper/instrumenters/noop.rb b/lib/flipper/instrumenters/noop.rb index 02c2d810f..c488a741e 100644 --- a/lib/flipper/instrumenters/noop.rb +++ b/lib/flipper/instrumenters/noop.rb @@ -5,7 +5,7 @@ def self.instrument(_name, payload = {}) yield payload if block_given? end - def self.subscribe(_name, _subscriber) + def self.subscribe(_name, _callback = nil, &_block) # noop end end diff --git a/spec/flipper/adapters/sync/synchronizer_spec.rb b/spec/flipper/adapters/sync/synchronizer_spec.rb index 9ae563020..6d0ff5cb7 100644 --- a/spec/flipper/adapters/sync/synchronizer_spec.rb +++ b/spec/flipper/adapters/sync/synchronizer_spec.rb @@ -32,10 +32,7 @@ context "when raise disabled" do subject do - options = { - instrumenter: instrumenter, - raise: false, - } + options = { raise: false } described_class.new(local, remote, options) end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8c2b8cd09..03c438b29 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -27,6 +27,14 @@ Flipper.configuration = nil end + config.after(:each) do + # Remove all subscribers once AS::Notifications is loaded + if defined?(ActiveSupport::Notifications) + ActiveSupport::Notifications.unsubscribe /\.flipper$/ + end + end + + config.disable_monkey_patching! config.filter_run focus: true From 29452a707813d1726e7965b981559e4a28be9549 Mon Sep 17 00:00:00 2001 From: Brandon Keepers Date: Wed, 8 Jun 2022 14:46:25 -0400 Subject: [PATCH 4/4] Update changelog for instrumenter change --- Changelog.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Changelog.md b/Changelog.md index b97369249..91c77ec81 100644 --- a/Changelog.md +++ b/Changelog.md @@ -3,6 +3,17 @@ ### Additions/Changes * Added failsafe adapter (https://github.com/jnunemaker/flipper/pull/626) +* Deprecate `instrumenter:` option everywhere in favor of global config. If you are manually configuring the instrumenter, you will need to update your configuration: + ```diff + # config/initializers/flipper.rb + Flipper.configure do |config| + config.adapter do + - Flipper::Adapters::Instrumented.new(my_adapter, instrumenter: MyInstrumenter) + + Flipper::Adapters::Instrumented.new(my_adapter) + end + + config.instrumenter MyInstrumenter + end + ``` ## 0.24.1