appmap 0.102.2 → 0.103.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -0
- data/.standard.yml +1 -0
- data/.travis.yml +2 -2
- data/CHANGELOG.md +17 -0
- data/appmap.gemspec +35 -36
- data/lib/appmap/agent.rb +28 -24
- data/lib/appmap/class_map.rb +36 -31
- data/lib/appmap/config.rb +132 -110
- data/lib/appmap/cucumber.rb +25 -25
- data/lib/appmap/detect_enabled.rb +30 -28
- data/lib/appmap/gem_hooks/activejob.yml +0 -1
- data/lib/appmap/hook/method/ruby2.rb +19 -8
- data/lib/appmap/hook/method/ruby3.rb +20 -13
- data/lib/appmap/hook/method.rb +27 -27
- data/lib/appmap/hook/record_around.rb +77 -0
- data/lib/appmap/hook.rb +74 -44
- data/lib/appmap/metadata.rb +6 -18
- data/lib/appmap/middleware/remote_recording.rb +26 -27
- data/lib/appmap/minitest.rb +27 -27
- data/lib/appmap/rspec.rb +37 -37
- data/lib/appmap/trace.rb +14 -12
- data/lib/appmap/util.rb +71 -61
- data/lib/appmap/version.rb +1 -1
- metadata +5 -16
| @@ -1,4 +1,4 @@ | |
| 1 | 
            -
            require_relative  | 
| 1 | 
            +
            require_relative "recording_methods"
         | 
| 2 2 |  | 
| 3 3 | 
             
            module AppMap
         | 
| 4 4 | 
             
              # Detects whether AppMap recording should be enabled. This test can be performed generally, or for
         | 
| @@ -9,27 +9,29 @@ module AppMap | |
| 9 9 | 
             
                @@detected_for_method = {}
         | 
| 10 10 |  | 
| 11 11 | 
             
                class << self
         | 
| 12 | 
            +
                  # rubocop:disable Metrics/MethodLength
         | 
| 12 13 | 
             
                  def discourage_conflicting_recording_methods(recording_method)
         | 
| 13 | 
            -
                    return if ENV[ | 
| 14 | 
            +
                    return if ENV["APPMAP_DISCOURAGE_CONFLICTING_RECORDING_METHODS"] == "false"
         | 
| 14 15 |  | 
| 15 16 | 
             
                    return unless enabled?(recording_method.to_sym) && enabled?(:requests)
         | 
| 16 17 |  | 
| 17 18 | 
             
                    warn Util.color <<~MSG, :yellow
         | 
| 18 | 
            -
            AppMap recording is enabled for both 'requests' and '#{recording_method}'. This is not recommended
         | 
| 19 | 
            -
            because the recordings will contain duplicitive information, and in some case may conflict with each other.
         | 
| 19 | 
            +
                      AppMap recording is enabled for both 'requests' and '#{recording_method}'. This is not recommended
         | 
| 20 | 
            +
                      because the recordings will contain duplicitive information, and in some case may conflict with each other.
         | 
| 20 21 | 
             
                    MSG
         | 
| 21 22 |  | 
| 22 | 
            -
                    return unless ENV[ | 
| 23 | 
            +
                    return unless ENV["APPMAP"] == "true"
         | 
| 23 24 |  | 
| 24 25 | 
             
                    warn Util.color <<~MSG, :yellow
         | 
| 25 | 
            -
            The environment contains APPMAP=true, which is not recommended in this application environment because
         | 
| 26 | 
            -
            it enables all recording methods. Consider letting AppMap detect the appropriate recording method,
         | 
| 27 | 
            -
            or explicitly enabling only the recording methods you want to use using environment variables like
         | 
| 28 | 
            -
            APPMAP_RECORD_REQUESTS, APPMAP_RECORD_RSPEC, etc.
         | 
| 29 | 
            -
             | 
| 30 | 
            -
            See https://appmap.io/docs/reference/appmap-ruby.html#advanced-runtime-options for more information.
         | 
| 26 | 
            +
                      The environment contains APPMAP=true, which is not recommended in this application environment because
         | 
| 27 | 
            +
                      it enables all recording methods. Consider letting AppMap detect the appropriate recording method,
         | 
| 28 | 
            +
                      or explicitly enabling only the recording methods you want to use using environment variables like
         | 
| 29 | 
            +
                      APPMAP_RECORD_REQUESTS, APPMAP_RECORD_RSPEC, etc.
         | 
| 30 | 
            +
                      
         | 
| 31 | 
            +
                      See https://appmap.io/docs/reference/appmap-ruby.html#advanced-runtime-options for more information.
         | 
| 31 32 | 
             
                    MSG
         | 
| 32 33 | 
             
                  end
         | 
| 34 | 
            +
                  # rubocop:enable Metrics/MethodLength
         | 
| 33 35 |  | 
| 34 36 | 
             
                  def enabled?(recording_method)
         | 
| 35 37 | 
             
                    new(recording_method).enabled?
         | 
| @@ -57,7 +59,7 @@ See https://appmap.io/docs/reference/appmap-ruby.html#advanced-runtime-options f | |
| 57 59 |  | 
| 58 60 | 
             
                  if @recording_method && (enabled && enabled_by_app_env?)
         | 
| 59 61 | 
             
                    warn AppMap::Util.color(
         | 
| 60 | 
            -
                      "AppMap #{@recording_method.nil? ?  | 
| 62 | 
            +
                      "AppMap #{@recording_method.nil? ? "" : "#{@recording_method} "}recording is enabled because #{message}", :magenta
         | 
| 61 63 | 
             
                    )
         | 
| 62 64 | 
             
                  end
         | 
| 63 65 |  | 
| @@ -77,63 +79,63 @@ See https://appmap.io/docs/reference/appmap-ruby.html#advanced-runtime-options f | |
| 77 79 | 
             
                  message, enabled = []
         | 
| 78 80 | 
             
                  message, enabled = method(detection_functions.shift).call while enabled.nil? && !detection_functions.empty?
         | 
| 79 81 |  | 
| 80 | 
            -
                  return [ | 
| 82 | 
            +
                  return ["it is not enabled by any configuration or framework", false, false] if enabled.nil?
         | 
| 81 83 |  | 
| 82 84 | 
             
                  _, enabled_by_env = enabled_by_app_env?
         | 
| 83 | 
            -
                  [ | 
| 85 | 
            +
                  [message, enabled, enabled_by_env]
         | 
| 84 86 | 
             
                end
         | 
| 85 87 |  | 
| 86 88 | 
             
                def enabled_by_testing?
         | 
| 87 89 | 
             
                  return unless %i[rspec minitest cucumber].member?(@recording_method)
         | 
| 88 90 |  | 
| 89 | 
            -
                  [ | 
| 91 | 
            +
                  ["running tests with #{@recording_method}", true]
         | 
| 90 92 | 
             
                end
         | 
| 91 93 |  | 
| 92 94 | 
             
                def enabled_by_app_env?
         | 
| 93 95 | 
             
                  env_name, app_env = detect_app_env
         | 
| 94 | 
            -
                  return [ | 
| 96 | 
            +
                  return ["#{env_name} is '#{app_env}'", true] if @recording_method.nil? && %w[test development].member?(app_env)
         | 
| 95 97 |  | 
| 96 98 | 
             
                  return unless %i[remote requests].member?(@recording_method)
         | 
| 97 | 
            -
                   | 
| 99 | 
            +
                  ["#{env_name} is '#{app_env}'", true] if app_env == "development"
         | 
| 98 100 | 
             
                end
         | 
| 99 101 |  | 
| 100 102 | 
             
                def detect_app_env
         | 
| 101 103 | 
             
                  if rails_env
         | 
| 102 | 
            -
                    [ | 
| 103 | 
            -
                  elsif ENV[ | 
| 104 | 
            -
                    [ | 
| 104 | 
            +
                    ["RAILS_ENV", rails_env]
         | 
| 105 | 
            +
                  elsif ENV["APP_ENV"]
         | 
| 106 | 
            +
                    ["APP_ENV", ENV["APP_ENV"]]
         | 
| 105 107 | 
             
                  end
         | 
| 106 108 | 
             
                end
         | 
| 107 109 |  | 
| 108 110 | 
             
                def globally_enabled?
         | 
| 109 111 | 
             
                  # Don't auto-enable request recording in the 'test' environment, because users probably don't want
         | 
| 110 112 | 
             
                  # AppMaps of both test cases and requests. Requests recording can always be enabled by APPMAP_RECORD_REQUESTS=true.
         | 
| 111 | 
            -
                  requests_recording_in_test = -> { [ | 
| 112 | 
            -
                  [ | 
| 113 | 
            +
                  requests_recording_in_test = -> { [:requests].member?(@recording_method) && detect_app_env == "test" }
         | 
| 114 | 
            +
                  ["APPMAP=true", true] if ENV["APPMAP"] == "true" && !requests_recording_in_test.call
         | 
| 113 115 | 
             
                end
         | 
| 114 116 |  | 
| 115 117 | 
             
                def globally_disabled?
         | 
| 116 | 
            -
                  [ | 
| 118 | 
            +
                  ["APPMAP=false", false] if ENV["APPMAP"] == "false"
         | 
| 117 119 | 
             
                end
         | 
| 118 120 |  | 
| 119 121 | 
             
                def recording_method_disabled?
         | 
| 120 122 | 
             
                  return false unless @recording_method
         | 
| 121 123 |  | 
| 122 | 
            -
                  env_var = [ | 
| 123 | 
            -
                  [ | 
| 124 | 
            +
                  env_var = ["APPMAP", "RECORD", @recording_method.upcase].join("_")
         | 
| 125 | 
            +
                  ["#{["APPMAP", "RECORD", @recording_method.upcase].join("_")}=false", false] if ENV[env_var] == "false"
         | 
| 124 126 | 
             
                end
         | 
| 125 127 |  | 
| 126 128 | 
             
                def recording_method_enabled?
         | 
| 127 129 | 
             
                  return false unless @recording_method
         | 
| 128 130 |  | 
| 129 | 
            -
                  env_var = [ | 
| 130 | 
            -
                  [ | 
| 131 | 
            +
                  env_var = ["APPMAP", "RECORD", @recording_method.upcase].join("_")
         | 
| 132 | 
            +
                  ["#{["APPMAP", "RECORD", @recording_method.upcase].join("_")}=true", true] if ENV[env_var] == "true"
         | 
| 131 133 | 
             
                end
         | 
| 132 134 |  | 
| 133 135 | 
             
                def rails_env
         | 
| 134 136 | 
             
                  return Rails.env if defined?(::Rails::Railtie)
         | 
| 135 137 |  | 
| 136 | 
            -
                  ENV.fetch( | 
| 138 | 
            +
                  ENV.fetch("RAILS_ENV", nil)
         | 
| 137 139 | 
             
                end
         | 
| 138 140 | 
             
              end
         | 
| 139 141 | 
             
            end
         | 
| @@ -1,16 +1,23 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require  | 
| 3 | 
            +
            require "appmap/util"
         | 
| 4 | 
            +
            require_relative "../record_around"
         | 
| 4 5 |  | 
| 5 | 
            -
             | 
| 6 | 
            +
            unless respond_to?(:ruby2_keywords, true)
         | 
| 7 | 
            +
              def ruby2_keywords(*)
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
            end
         | 
| 6 10 |  | 
| 7 11 | 
             
            module AppMap
         | 
| 8 12 | 
             
              class Hook
         | 
| 9 13 | 
             
                # Delegation methods for Ruby 2.
         | 
| 10 14 | 
             
                # cf. https://eregon.me/blog/2019/11/10/the-delegation-challenge-of-ruby27.html
         | 
| 11 15 | 
             
                class Method
         | 
| 16 | 
            +
                  include RecordAround
         | 
| 17 | 
            +
             | 
| 12 18 | 
             
                  ruby2_keywords def call(receiver, *args, &block)
         | 
| 13 19 | 
             
                    call_event = false
         | 
| 20 | 
            +
                    record_around_before
         | 
| 14 21 | 
             
                    if trace?
         | 
| 15 22 | 
             
                      call_event, elapsed_before = with_disabled_hook { before_hook receiver, *args }
         | 
| 16 23 | 
             
                    end
         | 
| @@ -22,7 +29,7 @@ module AppMap | |
| 22 29 | 
             
                  protected
         | 
| 23 30 |  | 
| 24 31 | 
             
                  def before_hook(receiver, *args)
         | 
| 25 | 
            -
                    before_hook_start_time = AppMap::Util.gettime | 
| 32 | 
            +
                    before_hook_start_time = AppMap::Util.gettime
         | 
| 26 33 | 
             
                    call_event = handle_call(receiver, args)
         | 
| 27 34 | 
             
                    if call_event
         | 
| 28 35 | 
             
                      AppMap.tracing.record_event \
         | 
| @@ -31,10 +38,11 @@ module AppMap | |
| 31 38 | 
             
                        defined_class: defined_class,
         | 
| 32 39 | 
             
                        method: hook_method
         | 
| 33 40 | 
             
                    end
         | 
| 34 | 
            -
                    [call_event, AppMap::Util.gettime | 
| 41 | 
            +
                    [call_event, AppMap::Util.gettime - before_hook_start_time]
         | 
| 35 42 | 
             
                  end
         | 
| 36 43 |  | 
| 37 44 | 
             
                  ruby2_keywords def do_call(receiver, *args, &block)
         | 
| 45 | 
            +
                    # Do not allow this to change to bind_call, it's not defined for Ruby 2.
         | 
| 38 46 | 
             
                    hook_method.bind(receiver).call(*args, &block)
         | 
| 39 47 | 
             
                  end
         | 
| 40 48 |  | 
| @@ -42,16 +50,19 @@ module AppMap | |
| 42 50 | 
             
                  ruby2_keywords def trace_call(call_event, elapsed_before, receiver, *args, &block)
         | 
| 43 51 | 
             
                    return do_call(receiver, *args, &block) unless call_event
         | 
| 44 52 |  | 
| 45 | 
            -
                    start_time = AppMap::Util.gettime | 
| 53 | 
            +
                    start_time = AppMap::Util.gettime
         | 
| 46 54 | 
             
                    begin
         | 
| 47 55 | 
             
                      return_value = do_call(receiver, *args, &block)
         | 
| 48 56 | 
             
                    rescue # rubocop:disable Style/RescueStandardError
         | 
| 49 57 | 
             
                      exception = $ERROR_INFO
         | 
| 50 58 | 
             
                      raise
         | 
| 51 59 | 
             
                    ensure
         | 
| 52 | 
            -
                      after_start_time = AppMap::Util.gettime | 
| 53 | 
            -
                       | 
| 54 | 
            -
                         | 
| 60 | 
            +
                      after_start_time = AppMap::Util.gettime
         | 
| 61 | 
            +
                      begin
         | 
| 62 | 
            +
                        with_disabled_hook { after_hook receiver, call_event, elapsed_before, after_start_time - start_time, after_start_time, return_value, exception }
         | 
| 63 | 
            +
                      ensure
         | 
| 64 | 
            +
                        record_around_after
         | 
| 65 | 
            +
                      end
         | 
| 55 66 | 
             
                    end
         | 
| 56 67 | 
             
                  end
         | 
| 57 68 | 
             
                  # rubocop:enable Metrics/MethodLength
         | 
| @@ -1,17 +1,21 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require  | 
| 3 | 
            +
            require "appmap/util"
         | 
| 4 | 
            +
            require_relative "../record_around"
         | 
| 4 5 |  | 
| 5 6 | 
             
            module AppMap
         | 
| 6 7 | 
             
              class Hook
         | 
| 7 8 | 
             
                # Delegation methods for Ruby 3.
         | 
| 8 9 | 
             
                class Method
         | 
| 10 | 
            +
                  include RecordAround
         | 
| 11 | 
            +
             | 
| 9 12 | 
             
                  def call(receiver, *args, **kwargs, &block)
         | 
| 10 13 | 
             
                    call_event = false
         | 
| 14 | 
            +
                    record_around_before
         | 
| 11 15 | 
             
                    if trace?
         | 
| 12 16 | 
             
                      call_event, elapsed_before = with_disabled_hook { before_hook receiver, *args, **kwargs }
         | 
| 13 17 | 
             
                    end
         | 
| 14 | 
            -
                    #  | 
| 18 | 
            +
                    # NOTE: we can't short-circuit directly to do_call because then the call stack
         | 
| 15 19 | 
             
                    # depth changes and eval handler doesn't work correctly
         | 
| 16 20 | 
             
                    trace_call call_event, elapsed_before, receiver, *args, **kwargs, &block
         | 
| 17 21 | 
             
                  end
         | 
| @@ -19,7 +23,7 @@ module AppMap | |
| 19 23 | 
             
                  protected
         | 
| 20 24 |  | 
| 21 25 | 
             
                  def before_hook(receiver, *args, **kwargs)
         | 
| 22 | 
            -
                    before_hook_start_time = AppMap::Util.gettime | 
| 26 | 
            +
                    before_hook_start_time = AppMap::Util.gettime
         | 
| 23 27 | 
             
                    args = [*args, kwargs] if !kwargs.empty? || keyrest?
         | 
| 24 28 | 
             
                    call_event = handle_call(receiver, args)
         | 
| 25 29 | 
             
                    if call_event
         | 
| @@ -29,31 +33,34 @@ module AppMap | |
| 29 33 | 
             
                        defined_class: defined_class,
         | 
| 30 34 | 
             
                        method: hook_method
         | 
| 31 35 | 
             
                    end
         | 
| 32 | 
            -
                    [call_event, AppMap::Util.gettime | 
| 36 | 
            +
                    [call_event, AppMap::Util.gettime - before_hook_start_time]
         | 
| 33 37 | 
             
                  end
         | 
| 34 38 |  | 
| 35 39 | 
             
                  def keyrest?
         | 
| 36 40 | 
             
                    @keyrest ||= parameters.map(&:last).include? :keyrest
         | 
| 37 41 | 
             
                  end
         | 
| 38 42 |  | 
| 39 | 
            -
                  def do_call(receiver,  | 
| 40 | 
            -
                    hook_method.bind_call(receiver,  | 
| 43 | 
            +
                  def do_call(receiver, ...)
         | 
| 44 | 
            +
                    hook_method.bind_call(receiver, ...)
         | 
| 41 45 | 
             
                  end
         | 
| 42 46 |  | 
| 43 47 | 
             
                  # rubocop:disable Metrics/MethodLength
         | 
| 44 | 
            -
                  def trace_call(call_event, elapsed_before, receiver,  | 
| 45 | 
            -
                    return do_call(receiver,  | 
| 48 | 
            +
                  def trace_call(call_event, elapsed_before, receiver, ...)
         | 
| 49 | 
            +
                    return do_call(receiver, ...) unless call_event
         | 
| 46 50 |  | 
| 47 | 
            -
                    start_time = AppMap::Util.gettime | 
| 51 | 
            +
                    start_time = AppMap::Util.gettime
         | 
| 48 52 | 
             
                    begin
         | 
| 49 | 
            -
                      return_value = do_call(receiver,  | 
| 53 | 
            +
                      return_value = do_call(receiver, ...)
         | 
| 50 54 | 
             
                    rescue # rubocop:disable Style/RescueStandardError
         | 
| 51 55 | 
             
                      exception = $ERROR_INFO
         | 
| 52 56 | 
             
                      raise
         | 
| 53 57 | 
             
                    ensure
         | 
| 54 | 
            -
                      after_start_time = AppMap::Util.gettime | 
| 55 | 
            -
                       | 
| 56 | 
            -
                         | 
| 58 | 
            +
                      after_start_time = AppMap::Util.gettime
         | 
| 59 | 
            +
                      begin
         | 
| 60 | 
            +
                        with_disabled_hook { after_hook receiver, call_event, elapsed_before, after_start_time - start_time, after_start_time, return_value, exception }
         | 
| 61 | 
            +
                      ensure
         | 
| 62 | 
            +
                        record_around_after
         | 
| 63 | 
            +
                      end
         | 
| 57 64 | 
             
                    end
         | 
| 58 65 | 
             
                  end
         | 
| 59 66 | 
             
                  # rubocop:enable Metrics/MethodLength
         | 
    
        data/lib/appmap/hook/method.rb
    CHANGED
    
    | @@ -1,17 +1,16 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require  | 
| 3 | 
            +
            require "appmap/util"
         | 
| 4 4 |  | 
| 5 5 | 
             
            module AppMap
         | 
| 6 6 | 
             
              class Hook
         | 
| 7 7 | 
             
                class << self
         | 
| 8 8 | 
             
                  def method_hash_key(cls, method)
         | 
| 9 | 
            -
                    [ | 
| 9 | 
            +
                    [cls, method.name].hash
         | 
| 10 10 | 
             
                  rescue TypeError => e
         | 
| 11 11 | 
             
                    warn "Error building hash key for #{cls}, #{method}: #{e}"
         | 
| 12 12 | 
             
                  end
         | 
| 13 13 | 
             
                end
         | 
| 14 | 
            -
                
         | 
| 15 14 |  | 
| 16 15 | 
             
                SIGNATURES = {}
         | 
| 17 16 | 
             
                LOOKUP_SIGNATURE = lambda do |id|
         | 
| @@ -32,19 +31,20 @@ module AppMap | |
| 32 31 | 
             
                  method
         | 
| 33 32 | 
             
                end
         | 
| 34 33 |  | 
| 35 | 
            -
                RUBY_MAJOR_VERSION, RUBY_MINOR_VERSION, _ = RUBY_VERSION.split( | 
| 34 | 
            +
                RUBY_MAJOR_VERSION, RUBY_MINOR_VERSION, _ = RUBY_VERSION.split(".").map(&:to_i)
         | 
| 36 35 |  | 
| 37 36 | 
             
                # Single hooked method.
         | 
| 38 37 | 
             
                # Call #activate to override the original.
         | 
| 39 38 | 
             
                class Method
         | 
| 40 | 
            -
                  attr_reader :hook_package, :hook_class, :hook_method, :parameters, :arity
         | 
| 39 | 
            +
                  attr_reader :hook_package, :hook_class, :hook_method, :record_around, :parameters, :arity
         | 
| 41 40 |  | 
| 42 | 
            -
                  HOOK_DISABLE_KEY =  | 
| 41 | 
            +
                  HOOK_DISABLE_KEY = "AppMap::Hook.disable"
         | 
| 43 42 |  | 
| 44 | 
            -
                  def initialize(hook_package, hook_class, hook_method)
         | 
| 43 | 
            +
                  def initialize(hook_package, hook_class, hook_method, record_around: false)
         | 
| 45 44 | 
             
                    @hook_package = hook_package
         | 
| 46 45 | 
             
                    @hook_class = hook_class
         | 
| 47 46 | 
             
                    @hook_method = hook_method
         | 
| 47 | 
            +
                    @record_around = record_around
         | 
| 48 48 | 
             
                    @parameters = hook_method.parameters
         | 
| 49 49 | 
             
                    @arity = hook_method.arity
         | 
| 50 50 | 
             
                  end
         | 
| @@ -52,11 +52,11 @@ module AppMap | |
| 52 52 | 
             
                  def activate
         | 
| 53 53 | 
             
                    if HookLog.enabled?
         | 
| 54 54 | 
             
                      msg = if method_display_name
         | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 57 | 
            -
             | 
| 58 | 
            -
             | 
| 59 | 
            -
                      HookLog.log "Hooking #{msg} at line #{(hook_method.source_location || []).join( | 
| 55 | 
            +
                        "#{method_display_name}"
         | 
| 56 | 
            +
                      else
         | 
| 57 | 
            +
                        "#{hook_method.name} (class resolution deferred)"
         | 
| 58 | 
            +
                      end
         | 
| 59 | 
            +
                      HookLog.log "Hooking #{msg} at line #{(hook_method.source_location || []).join(":")}"
         | 
| 60 60 | 
             
                    end
         | 
| 61 61 |  | 
| 62 62 | 
             
                    hook_method_parameters = hook_method.parameters.dup.freeze
         | 
| @@ -79,13 +79,13 @@ module AppMap | |
| 79 79 |  | 
| 80 80 | 
             
                  def defining_class(hook_class)
         | 
| 81 81 | 
             
                    cls = if RUBY_MAJOR_VERSION == 2 && RUBY_MINOR_VERSION <= 5
         | 
| 82 | 
            -
             | 
| 83 | 
            -
             | 
| 84 | 
            -
             | 
| 85 | 
            -
             | 
| 86 | 
            -
             | 
| 87 | 
            -
             | 
| 88 | 
            -
             | 
| 82 | 
            +
                      hook_class
         | 
| 83 | 
            +
                        .ancestors
         | 
| 84 | 
            +
                        .select { |cls| cls.method_defined?(hook_method.name) }
         | 
| 85 | 
            +
                        .find { |cls| cls.instance_method(hook_method.name).owner == cls }
         | 
| 86 | 
            +
                    else
         | 
| 87 | 
            +
                      hook_class.ancestors.find { |cls| cls.method_defined?(hook_method.name, false) }
         | 
| 88 | 
            +
                    end
         | 
| 89 89 |  | 
| 90 90 | 
             
                    return cls if cls
         | 
| 91 91 |  | 
| @@ -93,7 +93,7 @@ module AppMap | |
| 93 93 | 
             
                  end
         | 
| 94 94 |  | 
| 95 95 | 
             
                  def trace?
         | 
| 96 | 
            -
                    return false unless AppMap.tracing_enabled?
         | 
| 96 | 
            +
                    return false unless AppMap.tracing_enabled?(thread: Thread.current)
         | 
| 97 97 | 
             
                    return false if Thread.current[HOOK_DISABLE_KEY]
         | 
| 98 98 | 
             
                    return false if hook_package&.shallow? && AppMap.tracing.last_package_for_current_thread == hook_package
         | 
| 99 99 |  | 
| @@ -103,7 +103,7 @@ module AppMap | |
| 103 103 | 
             
                  def method_display_name
         | 
| 104 104 | 
             
                    return @method_display_name if @method_display_name
         | 
| 105 105 |  | 
| 106 | 
            -
                    return @method_display_name = [defined_class,  | 
| 106 | 
            +
                    return @method_display_name = [defined_class, "#", hook_method.name].join if defined_class
         | 
| 107 107 |  | 
| 108 108 | 
             
                    "#{hook_method.name} (class resolution deferred)"
         | 
| 109 109 | 
             
                  end
         | 
| @@ -114,7 +114,7 @@ module AppMap | |
| 114 114 |  | 
| 115 115 | 
             
                  def after_hook(_receiver, call_event, elapsed_before, elapsed, after_start_time, return_value, exception)
         | 
| 116 116 | 
             
                    return_event = handle_return(call_event.id, elapsed, return_value, exception)
         | 
| 117 | 
            -
                    return_event.elapsed_instrumentation = elapsed_before + (AppMap::Util.gettime | 
| 117 | 
            +
                    return_event.elapsed_instrumentation = elapsed_before + (AppMap::Util.gettime - after_start_time)
         | 
| 118 118 | 
             
                    AppMap.tracing.record_event(return_event) if return_event
         | 
| 119 119 | 
             
                  end
         | 
| 120 120 |  | 
| @@ -141,20 +141,20 @@ module AppMap | |
| 141 141 | 
             
              end
         | 
| 142 142 | 
             
            end
         | 
| 143 143 |  | 
| 144 | 
            -
            unless ENV[ | 
| 144 | 
            +
            unless ENV["APPMAP_NO_PATCH_OBJECT"] == "true"
         | 
| 145 145 | 
             
              class Object
         | 
| 146 146 | 
             
                prepend AppMap::ObjectMethods
         | 
| 147 147 | 
             
              end
         | 
| 148 148 | 
             
            end
         | 
| 149 149 |  | 
| 150 | 
            -
            unless ENV[ | 
| 150 | 
            +
            unless ENV["APPMAP_NO_PATCH_MODULE"] == "true"
         | 
| 151 151 | 
             
              class Module
         | 
| 152 152 | 
             
                prepend AppMap::ModuleMethods
         | 
| 153 153 | 
             
              end
         | 
| 154 154 | 
             
            end
         | 
| 155 155 |  | 
| 156 | 
            -
            if RUBY_VERSION <  | 
| 157 | 
            -
              require  | 
| 156 | 
            +
            if RUBY_VERSION < "3"
         | 
| 157 | 
            +
              require "appmap/hook/method/ruby2"
         | 
| 158 158 | 
             
            else
         | 
| 159 | 
            -
              require  | 
| 159 | 
            +
              require "appmap/hook/method/ruby3"
         | 
| 160 160 | 
             
            end
         | 
| @@ -0,0 +1,77 @@ | |
| 1 | 
            +
            module AppMap
         | 
| 2 | 
            +
              class Hook
         | 
| 3 | 
            +
                # Start and stop a recording around a Hook::Method.
         | 
| 4 | 
            +
                module RecordAround
         | 
| 5 | 
            +
                  APPMAP_OUTPUT_DIR = File.join(AppMap.output_dir, "requests")
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  # Context for a recording.
         | 
| 8 | 
            +
                  class Context
         | 
| 9 | 
            +
                    attr_reader :hook_method
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    def initialize(hook_method)
         | 
| 12 | 
            +
                      @hook_method = hook_method
         | 
| 13 | 
            +
                      @start_time = DateTime.now
         | 
| 14 | 
            +
                      @tracer = AppMap.tracing.trace(thread: Thread.current)
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    # Finish recording the AppMap by collecting the events and classMap and writing the output file.
         | 
| 18 | 
            +
                    # rubocop:disable Metrics/MethodLength
         | 
| 19 | 
            +
                    # rubocop:disable Metrics/AbcSize
         | 
| 20 | 
            +
                    def finish
         | 
| 21 | 
            +
                      return unless @tracer
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                      tracer = @tracer
         | 
| 24 | 
            +
                      @tracer = nil
         | 
| 25 | 
            +
                      AppMap.tracing.delete(tracer)
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                      events = tracer.events.map(&:to_h)
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                      timestamp = DateTime.now
         | 
| 30 | 
            +
                      appmap_name = "#{hook_method.name} (#{Thread.current.object_id}) - #{timestamp.strftime("%T.%L")}"
         | 
| 31 | 
            +
                      appmap_file_name = AppMap::Util.scenario_filename([timestamp.to_f, hook_method.name, Thread.current.object_id].join("_"))
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                      metadata = AppMap.detect_metadata.tap do |metadata|
         | 
| 34 | 
            +
                        metadata[:name] = appmap_name
         | 
| 35 | 
            +
                        metadata[:source_location] = hook_method.source_location
         | 
| 36 | 
            +
                        metadata[:recorder] = {
         | 
| 37 | 
            +
                          name: "command",
         | 
| 38 | 
            +
                          type: "requests"
         | 
| 39 | 
            +
                        }
         | 
| 40 | 
            +
                      end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                      appmap = {
         | 
| 43 | 
            +
                        version: AppMap::APPMAP_FORMAT_VERSION,
         | 
| 44 | 
            +
                        classMap: AppMap.class_map(tracer.event_methods),
         | 
| 45 | 
            +
                        metadata: metadata,
         | 
| 46 | 
            +
                        events: events
         | 
| 47 | 
            +
                      }
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                      AppMap::Util.write_appmap(File.join(APPMAP_OUTPUT_DIR, appmap_file_name), appmap)
         | 
| 50 | 
            +
                    end
         | 
| 51 | 
            +
                    # rubocop:enable Metrics/MethodLength
         | 
| 52 | 
            +
                    # rubocop:enable Metrics/AbcSize
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  # If requests recording is enabled, and we encounter a method which should always be recorded
         | 
| 56 | 
            +
                  # when requests recording is on, and there is no other recording in progress, then start a
         | 
| 57 | 
            +
                  # new recording and end it when the method returns.
         | 
| 58 | 
            +
                  def record_around?
         | 
| 59 | 
            +
                    (record_around && AppMap.recording_enabled?(:requests) && !AppMap.tracing_enabled?(thread: Thread.current))
         | 
| 60 | 
            +
                  end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                  def record_around_before
         | 
| 63 | 
            +
                    return unless record_around?
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                    @record_around_context = Context.new(hook_method)
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  def record_around_after
         | 
| 69 | 
            +
                    return unless @record_around_context
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                    context = @record_around_context
         | 
| 72 | 
            +
                    @record_around_context = nil
         | 
| 73 | 
            +
                    context.finish
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
              end
         | 
| 77 | 
            +
            end
         |