lib/appsignal/rack/sinatra_instrumentation.rb
# 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