diff --git a/.gitignore b/.gitignore index b04a8c8..7b5e907 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,8 @@ /spec/reports/ /tmp/ +#macos +.DS_Store + # rspec failure tracking .rspec_status diff --git a/CHANGELOG.md b/CHANGELOG.md index 15a6513..2af7d2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Opera Changelog +### 0.3.5 - February 24, 2025 + +- Simplify instrumentation configuration +- Allow instrumentation logic to be defined at the source + ### 0.3.4 - February 19, 2025 - Add support for `benchmark` label diff --git a/Gemfile.lock b/Gemfile.lock index 0bcaa70..206d20b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - opera (0.3.4) + opera (0.3.5) GEM remote: https://rubygems.org/ diff --git a/lib/opera/operation/base.rb b/lib/opera/operation/base.rb index 627334c..ac7e5ef 100644 --- a/lib/opera/operation/base.rb +++ b/lib/opera/operation/base.rb @@ -33,7 +33,7 @@ class << self def call(args = {}) operation = new(params: args.fetch(:params, {}), dependencies: args.fetch(:dependencies, {})) executor = Executor.new(operation) - Instrumentation.new(config).instrument(name: self.name, level: :operation) do + Instrumentation.new(operation).instrument(name: self.name, level: :operation) do executor.evaluate_instructions(instructions) end executor.result diff --git a/lib/opera/operation/config.rb b/lib/opera/operation/config.rb index 65343e6..6d3da6e 100644 --- a/lib/opera/operation/config.rb +++ b/lib/opera/operation/config.rb @@ -6,8 +6,8 @@ class Config DEVELOPMENT_MODE = :development PRODUCTION_MODE = :production - attr_accessor :transaction_class, :transaction_method, :transaction_options, - :instrumentation_class, :instrumentation_method, :instrumentation_options, :mode, :reporter + attr_accessor :transaction_class, :transaction_method, :transaction_options, :instrumentation_class, + :mode, :reporter def initialize @transaction_class = self.class.transaction_class @@ -15,8 +15,6 @@ def initialize @transaction_options = self.class.transaction_options @instrumentation_class = self.class.instrumentation_class - @instrumentation_method = self.class.instrumentation_method || :instrument - @instrumentation_options = self.class.instrumentation_options || {} @mode = self.class.mode || DEVELOPMENT_MODE @reporter = custom_reporter || self.class.reporter @@ -41,8 +39,8 @@ def validate! end class << self - attr_accessor :transaction_class, :transaction_method, :transaction_options, - :instrumentation_class, :instrumentation_method, :instrumentation_options, :mode, :reporter + attr_accessor :transaction_class, :transaction_method, :transaction_options, :instrumentation_class, + :mode, :reporter def configure yield self diff --git a/lib/opera/operation/instructions/executors/step.rb b/lib/opera/operation/instructions/executors/step.rb index b4f992f..0e1ea00 100644 --- a/lib/opera/operation/instructions/executors/step.rb +++ b/lib/opera/operation/instructions/executors/step.rb @@ -8,7 +8,7 @@ class Step < Executor def call(instruction) method = instruction[:method] - Instrumentation.new(config).instrument(name: "##{method}", level: :step) do + Instrumentation.new(operation).instrument(name: "##{method}", level: :step) do operation.result.add_execution(method) unless production_mode? operation.send(method) end diff --git a/lib/opera/operation/instrumentation.rb b/lib/opera/operation/instrumentation.rb index f62e15a..086021a 100644 --- a/lib/opera/operation/instrumentation.rb +++ b/lib/opera/operation/instrumentation.rb @@ -3,29 +3,31 @@ module Opera module Operation class Instrumentation - attr_reader :config + class Base + def self.instrument(operation, name:, level: :operation) + raise NotImplementedError, "#{self.class} must implement #instrument" + end + end + + attr_reader :operation - def initialize(config) - @config = config + def initialize(operation) + @operation = operation end def instrument(name:, level: :operation) return yield if !instrumentation_enabled? - return yield if level == :step && instrumentation_level != :step + return yield if !instrumentation_compatible? - instrumentation_class.send(instrumentation_method, name, **instrumentation_options.except(:level)) do + instrumentation_class.instrument(operation, name: name, level: level) do yield end end private - def instrumentation_options - config.instrumentation_options - end - - def instrumentation_method - config.instrumentation_method + def config + operation.config end def instrumentation_class @@ -36,8 +38,8 @@ def instrumentation_enabled? !!config.instrumentation_class end - def instrumentation_level - instrumentation_options[:level] || :operation + def instrumentation_compatible? + config.instrumentation_class.ancestors.include?(Opera::Operation::Instrumentation::Base) end end end diff --git a/lib/opera/version.rb b/lib/opera/version.rb index 39e2abf..31d8663 100644 --- a/lib/opera/version.rb +++ b/lib/opera/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Opera - VERSION = '0.3.4' + VERSION = '0.3.5' end diff --git a/spec/opera/operation/base_spec.rb b/spec/opera/operation/base_spec.rb index 280d254..ab65df0 100644 --- a/spec/opera/operation/base_spec.rb +++ b/spec/opera/operation/base_spec.rb @@ -1044,10 +1044,11 @@ def step_3 end context 'for instrumentation' do - let(:instrumentation_class) do - Class.new do - def self.trace(name) - puts "Trace #{name}" + let(:instrumentation_wrapper) do + Class.new(Opera::Operation::Instrumentation::Base) do + + def self.instrument(operation, name:, level:) + puts "Trace #{name} with level #{level}" yield end end @@ -1083,42 +1084,25 @@ def step_5 end end - context 'for operation level' do - before do - Operation::Config.configure do |config| - config.instrumentation_class = instrumentation_class - config.instrumentation_method = :trace - end - end - - it 'evaluates all steps' do - expect(subject.executions).to match_array(%i[step_1 step_2 step_3 step_4 step_5]) - end - - it 'calls instrumentation with correct params' do - expect(instrumentation_class).to receive(:trace).with('Opera::FakeName').and_call_original - subject + before do + Operation::Config.configure do |config| + config.instrumentation_class = instrumentation_wrapper end end - context 'for step level' do - before do - Operation::Config.configure do |config| - config.instrumentation_class = instrumentation_class - config.instrumentation_method = :trace - config.instrumentation_options = { level: :step } - end - end - - it 'evaluates all steps' do - expect(subject.executions).to match_array(%i[step_1 step_2 step_3 step_4 step_5]) - end + it 'evaluates all steps' do + expect(subject.executions).to match_array(%i[step_1 step_2 step_3 step_4 step_5]) + end - it 'calls instrumentation with correct params' do - expect(instrumentation_class).to receive(:trace).exactly(6).times.and_call_original + it 'calls instrumentation with correct params' do + expect(instrumentation_wrapper).to receive(:instrument).with(anything, name: 'Opera::FakeName', level: :operation).once.and_call_original + expect(instrumentation_wrapper).to receive(:instrument).with(anything, name: '#step_1', level: :step).once.and_call_original + expect(instrumentation_wrapper).to receive(:instrument).with(anything, name: '#step_2', level: :step).once.and_call_original + expect(instrumentation_wrapper).to receive(:instrument).with(anything, name: '#step_3', level: :step).once.and_call_original + expect(instrumentation_wrapper).to receive(:instrument).with(anything, name: '#step_4', level: :step).once.and_call_original + expect(instrumentation_wrapper).to receive(:instrument).with(anything, name: '#step_5', level: :step).once - subject - end + subject end end