# encoding: utf-8 require 'one_apm/inst/transaction_base' require 'one_apm/inst/framework/sinatra/transaction_namer' require 'one_apm/inst/framework/sinatra/ignorer' LibraryDetection.defer do @name = :sinatra depends_on do !OneApm::Manager.config[:disable_sinatra] && defined?(::Sinatra) && defined?(::Sinatra::Base) && Sinatra::Base.private_method_defined?(:dispatch!) && Sinatra::Base.private_method_defined?(:process_route) && Sinatra::Base.private_method_defined?(:route_eval) end executes do OneApm::Manager.logger.info 'Installing Sinatra instrumentation' end executes do ::Sinatra::Base.class_eval do include OneApm::Agent::Instrumentation::Sinatra alias dispatch_without_oneapm dispatch! alias dispatch! dispatch_with_oneapm alias process_route_without_oneapm process_route alias process_route process_route_with_oneapm alias route_eval_without_oneapm route_eval alias route_eval route_eval_with_oneapm register OneApm::Agent::Instrumentation::Sinatra::Ignorer end module ::Sinatra register OneApm::Agent::Instrumentation::Sinatra::Ignorer end end executes do if Sinatra::Base.respond_to?(:build) # These requires are inside an executes block because they require rack, and # we can't be sure that rack is available when this file is first required. require 'one_apm/rack/middleware_hooks' require 'one_apm/rack/browser_monitoring' ::Sinatra::Base.class_eval do class << self alias build_without_oneapm build alias build build_with_oneapm end end else OneApm::Manager.logger.info("Skipping auto-injection of middleware for Sinatra - requires Sinatra 1.2.1+") end end end module OneApm module Agent module Instrumentation # OneApm instrumentation for Sinatra applications. Sinatra actions will # appear in the UI similar to controller actions, and have breakdown charts # and transaction traces. # # The actions in the UI will correspond to the pattern expression used # to match them, not directly to full URL's. module Sinatra include ::OneApm::Agent::Instrumentation::TransactionBase # Expected method for supporting TransactionBase def oneapm_request_headers(_) request.env end def self.included(clazz) clazz.extend(ClassMethods) end module ClassMethods def oneapm_middlewares middlewares = [OneApm::Rack::BrowserMonitoring] if OneApm::Rack::MiddlewareHooks.needed? middlewares << OneApm::Rack::MiddlewareHooks end middlewares end def build_with_oneapm(*args, &block) unless OneApm::Manager.config[:disable_sinatra_auto_middleware] oneapm_middlewares.each do |middleware_class| try_to_use(self, middleware_class) end end build_without_oneapm(*args, &block) end def try_to_use(app, clazz) has_middleware = app.middleware.any? { |info| info[0] == clazz } app.use(clazz) unless has_middleware end end # Capture last route we've seen. Will set for transaction on route_eval def process_route_with_oneapm(*args, &block) begin env["oneapm.last_route"] = args[0] rescue => e OneApm::Manager.logger.debug("Failed determining last route in Sinatra", e) end process_route_without_oneapm(*args, &block) end # If a transaction name is already set, this call will tromple over it. # This is intentional, as typically passing to a separate route is like # an entirely separate transaction, so we pick up the new name. # # If we're ignored, this remains safe, since set_transaction_name # care for the gating on the transaction's existence for us. def route_eval_with_oneapm(*args, &block) begin txn_name = TransactionNamer.transaction_name_for_route(env, request) unless txn_name.nil? ::OneApm::Transaction.set_default_transaction_name( "#{self.class.name}/#{txn_name}", :sinatra) end rescue => e OneApm::Manager.logger.debug("Failed during route_eval to set transaction name", e) end route_eval_without_oneapm(*args, &block) end def dispatch_with_oneapm request_params = get_request_params filtered_params = OneApm::Support::ParameterFiltering::apply_filters(request.env, request_params || {}) name = TransactionNamer.initial_transaction_name(request) perform_action_with_oneapm_trace(:category => :sinatra, :name => name, :params => filtered_params) do dispatch_and_notice_errors_with_oneapm end end def get_request_params begin @request.params rescue => e OneApm::Manager.logger.debug("Failed to get params from Rack request.", e) nil end end def dispatch_and_notice_errors_with_oneapm dispatch_without_oneapm ensure # Will only see an error raised if :show_exceptions is true, but # will always see them in the env hash if they occur had_error = env.has_key?('sinatra.error') OneApm::Manager.notice_error(env['sinatra.error']) if had_error end def do_not_trace? Ignorer.should_ignore?(self, :routes) end # Overrides TransactionBase implementation def ignore_apdex? Ignorer.should_ignore?(self, :apdex) end # Overrides TransactionBase implementation def ignore_enduser? Ignorer.should_ignore?(self, :enduser) end end end end end