appsignal/appsignal

View on GitHub
lib/appsignal/rack/sinatra_instrumentation.rb

Summary

Maintainability
A
1 hr
Test Coverage
# frozen_string_literal: true

require "rack"

module Appsignal
  module Rack
    # Stub old middleware. Prevents Sinatra middleware being loaded twice.
    # This can happen when users use the old method of including
    # `use Appsignal::Rack::SinatraInstrumentation` in their modular Sinatra
    # applications. This is no longer needed. Instead Appsignal now includes
    # `use Appsignal::Rack::SinatraBaseInstrumentation` automatically.
    #
    # @api private
    class SinatraInstrumentation
      def initialize(app, options = {})
        @app = app
        @options = options
        Appsignal.logger.warn "Please remove Appsignal::Rack::SinatraInstrumentation "\
          "from your Sinatra::Base class. This is no longer needed."
      end

      def call(env)
        @app.call(env)
      end

      def settings
        @app.settings
      end
    end

    class SinatraBaseInstrumentation
      attr_reader :raise_errors_on

      def initialize(app, options = {})
        Appsignal.logger.debug "Initializing Appsignal::Rack::SinatraBaseInstrumentation"
        @app = app
        @options = options
        @raise_errors_on = raise_errors?(@app)
      end

      def call(env)
        if Appsignal.active?
          call_with_appsignal_monitoring(env)
        else
          @app.call(env)
        end
      end

      def call_with_appsignal_monitoring(env)
        if @options[:params_method]
          env[:params_method] = @options[:params_method]
        end
        request = @options.fetch(:request_class, Sinatra::Request).new(env)
        transaction = Appsignal::Transaction.create(
          SecureRandom.uuid,
          Appsignal::Transaction::HTTP_REQUEST,
          request,
          :force => @options.include?(:force) && @options[:force]
        )
        begin
          Appsignal.instrument("process_action.sinatra") do
            @app.call(env)
          end
        rescue Exception => error # rubocop:disable Lint/RescueException
          transaction.set_error(error)
          raise error
        ensure
          # If raise_error is off versions of Sinatra don't raise errors, but store
          # them in the sinatra.error env var.
          if !raise_errors_on && env["sinatra.error"] && !env["sinatra.skip_appsignal_error"]
            transaction.set_error(env["sinatra.error"])
          end
          transaction.set_action_if_nil(action_name(env))
          transaction.set_metadata("path", request.path)
          transaction.set_metadata("method", request.request_method)
          transaction.set_http_or_background_queue_start
          Appsignal::Transaction.complete_current!
        end
      end

      def action_name(env)
        return unless env["sinatra.route"]

        if env["SCRIPT_NAME"]
          method, route = env["sinatra.route"].split(" ")
          "#{method} #{env["SCRIPT_NAME"]}#{route}"
        else
          env["sinatra.route"]
        end
      end

      private

      def raise_errors?(app)
        app.respond_to?(:settings) &&
          app.settings.respond_to?(:raise_errors) &&
          app.settings.raise_errors
      end
    end
  end
end