diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 4a12379922..5812142f9c 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -16,7 +16,7 @@ Metrics/BlockNesting: # Offense count: 4 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 246 + Max: 252 # Offense count: 23 Metrics/CyclomaticComplexity: diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a62941162..a2f68c1501 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Next Release * [#1047](/~https://github.com/intridea/grape/pull/1047): Adds `given` to DSL::Parameters, allowing for dependent params - [@rnubel](/~https://github.com/rnubel). * [#1064](/~https://github.com/intridea/grape/pull/1064): Add public `Grape::Exception::ValidationErrors#full_messages` - [@romanlehnert](/~https://github.com/romanlehnert). * [#1079](/~https://github.com/intridea/grape/pull/1079): Added `stream` method to take advantage of `Rack::Chunked` [@zbelzer](/~https://github.com/zbelzer). +* [#1086](/~https://github.com/ruby-grape/grape/pull/1086): Added ActiveSupport::Notifications instrumentation - [@wagenet](/~https://github.com/wagenet). * Your contribution here! #### Fixes diff --git a/README.md b/README.md index 2ee737e785..09a133c9e9 100644 --- a/README.md +++ b/README.md @@ -2628,6 +2628,34 @@ See [StackOverflow #3282655](http://stackoverflow.com/questions/3282655/ruby-on- ## Performance Monitoring +### Active Support Instrumentation + +Grape has built-in support for [ActiveSupport::Notifications](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) which provides simple hook points to instrument key parts of your application. + +The following are currently supported: + +#### endpoint_run.grape + +The main execution of an endpoint, includes filters and rendering. + +* *endpoint* - The endpoint instance + +#### endpoint_render.grape + +The execution of the main content block of the endpoint. + +* *endpoint* - The endpoint instance + +#### endpoint_run_filters.grape + +* *endpoint* - The endpoint instance +* *filters* - The filters being executed +* *type* - The type of filters (before, before_validation, after_validation, after) + +See the [ActiveSupport::Notifications documentation](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html] for information on how to subscripe to these events. + +### Monitoring Products + Grape integrates with NewRelic via the [newrelic-grape](/~https://github.com/flyerhzm/newrelic-grape) gem, and with Librato Metrics with the [grape-librato](/~https://github.com/seanmoon/grape-librato) gem. diff --git a/lib/grape.rb b/lib/grape.rb index 32928ed48a..1148a9128f 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -15,6 +15,7 @@ require 'active_support/core_ext/hash/deep_merge' require 'active_support/core_ext/hash/except' require 'active_support/dependencies/autoload' +require 'active_support/notifications' require 'multi_json' require 'multi_xml' require 'virtus' diff --git a/lib/grape/endpoint.rb b/lib/grape/endpoint.rb index 3aa65e6d9b..48050fccbe 100644 --- a/lib/grape/endpoint.rb +++ b/lib/grape/endpoint.rb @@ -41,10 +41,16 @@ def generate_api_method(method_name, &block) if instance_methods.include?(method_name.to_sym) || instance_methods.include?(method_name.to_s) fail NameError.new("method #{method_name.inspect} already exists and cannot be used as an unbound method name") end + define_method(method_name, &block) method = instance_method(method_name) remove_method(method_name) - proc { |endpoint_instance| method.bind(endpoint_instance).call } + + proc do |endpoint_instance| + ActiveSupport::Notifications.instrument('endpoint_render.grape', endpoint: self) do + method.bind(endpoint_instance).call + end + end end end @@ -210,47 +216,49 @@ def equals?(e) protected def run(env) - @env = env - @header = {} + ActiveSupport::Notifications.instrument('endpoint_run.grape', endpoint: self, env: env) do + @env = env + @header = {} - @request = Grape::Request.new(env) - @params = @request.params - @headers = @request.headers + @request = Grape::Request.new(env) + @params = @request.params + @headers = @request.headers - cookies.read(@request) + cookies.read(@request) - self.class.before_each.call(self) if self.class.before_each + self.class.before_each.call(self) if self.class.before_each - run_filters befores + run_filters befores, :before - run_filters before_validations + run_filters before_validations, :before_validation - # Retrieve validations from this namespace and all parent namespaces. - validation_errors = [] + # Retrieve validations from this namespace and all parent namespaces. + validation_errors = [] - # require 'pry-byebug'; binding.pry + # require 'pry-byebug'; binding.pry - route_setting(:saved_validations).each do |validator| - begin - validator.validate!(params) - rescue Grape::Exceptions::Validation => e - validation_errors << e + route_setting(:saved_validations).each do |validator| + begin + validator.validate!(params) + rescue Grape::Exceptions::Validation => e + validation_errors << e + end end - end - if validation_errors.any? - fail Grape::Exceptions::ValidationErrors, errors: validation_errors, headers: header - end + if validation_errors.any? + fail Grape::Exceptions::ValidationErrors, errors: validation_errors, headers: header + end - run_filters after_validations + run_filters after_validations, :after_validation - response_object = @block ? @block.call(self) : nil - run_filters afters - cookies.write(header) + response_object = @block ? @block.call(self) : nil + run_filters afters, :after + cookies.write(header) - # The Body commonly is an Array of Strings, the application instance itself, or a File-like object. - response_object = file || [body || response_object] - [status, header, response_object] + # The Body commonly is an Array of Strings, the application instance itself, or a File-like object. + response_object = file || [body || response_object] + [status, header, response_object] + end end def build_middleware @@ -305,9 +313,11 @@ def helpers mod end - def run_filters(filters) - (filters || []).each do |filter| - instance_eval(&filter) + def run_filters(filters, type=:other) + ActiveSupport::Notifications.instrument("endpoint_run_filters.grape", endpoint: self, filters: filters, type: type) do + (filters || []).each do |filter| + instance_eval(&filter) + end end end