lib/elastic_apm/spies.rb



# Licensed to Elasticsearch B.V. under one or more contributor
# license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright
# ownership. Elasticsearch B.V. licenses this file to you under
# the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

# frozen_string_literal: true

require 'elastic_apm/util/inflector'

module ElasticAPM
  # @api private
  module Spies
    # @api private
    class Registration
      extend Forwardable

      def initialize(const_name, require_paths, spy)
        @const_name = const_name
        @require_paths = Array(require_paths)
        @spy = spy
      end

      attr_reader :const_name, :require_paths

      def_delegator :@spy, :install
    end

    def self.require_hooks
      @require_hooks ||= {}
    end

    def self.installed
      @installed ||= {}
    end

    def self.register(*args)
      registration = Registration.new(*args)

      if safe_defined?(registration.const_name)
        registration.install
        installed[registration.const_name] = registration
      else
        register_require_hook registration
      end
    end

    def self.without_faraday
      return yield unless defined?(FaradaySpy)

      # rubocop:disable Style/ExplicitBlockArgument
      ElasticAPM::Spies::FaradaySpy.disable_in do
        yield
      end
      # rubocop:enable Style/ExplicitBlockArgument
    end

    def self.without_net_http
      return yield unless defined?(NetHTTPSpy)

      # rubocop:disable Style/ExplicitBlockArgument
      ElasticAPM::Spies::NetHTTPSpy.disable_in do
        yield
      end
      # rubocop:enable Style/ExplicitBlockArgument
    end

    def self.register_require_hook(registration)
      registration.require_paths.each do |path|
        require_hooks[path] = registration
      end
    end

    def self.hook_into(name)
      return unless (registration = require_hooks[name])
      return unless safe_defined?(registration.const_name)

      installed[registration.const_name] = registration
      registration.install

      registration.require_paths.each do |path|
        require_hooks.delete path
      end
    end

    def self.safe_defined?(const_name)
      Util::Inflector.safe_constantize(const_name)
    end
  end
end

unless ENV['ELASTIC_APM_SKIP_REQUIRE_PATCH'] == '1'
  # @api private
  module Kernel
    private

    alias require_without_apm require

    def require(path)
      res = require_without_apm(path)

      begin
        ElasticAPM::Spies.hook_into(path)
      rescue ::Exception => e
        puts "Failed hooking into '#{path}'. Please report this at " \
          'github.com/elastic/apm-agent-ruby'
        puts e.backtrace.join("\n")
      end

      res
    end
  end
end