• Paco Guzman's avatar
    Instrument Grape Endpoint with Metrics::RackMiddleware · 509082ba
    Paco Guzman authored
    Generating the following tags
    
    Grape#GET /projects/:id/archive
    
    from Grape::Route objects like
    
    { :path => /:version/projects/:id/archive(.:format)
      :version => “v3”,
      :method => “GET” }
    
    Use an instance variable to cache raw_path transformations.
    This variable is only going to growth to the number of 
    endpoints of the API, not with exact different requests
    
    We can store this cache as an instance variable because 
    middleware are initialised only once
    509082ba
rack_middleware.rb 1.82 KB
module Gitlab
  module Metrics
    # Rack middleware for tracking Rails and Grape requests.
    class RackMiddleware
      CONTROLLER_KEY = 'action_controller.instance'
      ENDPOINT_KEY   = 'api.endpoint'

      def initialize(app)
        @app = app
      end

      # env - A Hash containing Rack environment details.
      def call(env)
        trans  = transaction_from_env(env)
        retval = nil

        begin
          retval = trans.run { @app.call(env) }

        # Even in the event of an error we want to submit any metrics we
        # might've gathered up to this point.
        ensure
          if env[CONTROLLER_KEY]
            tag_controller(trans, env)
          elsif env[ENDPOINT_KEY]
            tag_endpoint(trans, env)
          end

          trans.finish
        end

        retval
      end

      def transaction_from_env(env)
        trans = Transaction.new

        trans.set(:request_uri, env['REQUEST_URI'])
        trans.set(:request_method, env['REQUEST_METHOD'])

        trans
      end

      def tag_controller(trans, env)
        controller   = env[CONTROLLER_KEY]
        trans.action = "#{controller.class.name}##{controller.action_name}"
      end

      def tag_endpoint(trans, env)
        endpoint = env[ENDPOINT_KEY]
        path = endpoint_paths_cache[endpoint.route.route_method][endpoint.route.route_path]
        trans.action = "Grape##{endpoint.route.route_method} #{path}"
      end

      private

      def endpoint_paths_cache
        @endpoint_paths_cache ||= Hash.new do |hash, http_method|
          hash[http_method] = Hash.new do |inner_hash, raw_path|
            inner_hash[raw_path] = endpoint_instrumentable_path(raw_path)
          end
        end
      end

      def endpoint_instrumentable_path(raw_path)
        raw_path.sub('(.:format)', '').sub('/:version', '')
      end
    end
  end
end