appmap 0.99.1 → 0.99.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ff070f04eeeb2d79a08d4f93462a84e277dfad345bffd1d6b87bd063a63b8f00
4
- data.tar.gz: 27e47f4e5a85fce938cbd259bf76cce952490851cfee98315fddf6d6b1c798d3
3
+ metadata.gz: eda0d23ff548aaa0e6e61eef0e7734a542660eeeb45e899f54a7287665baaf7e
4
+ data.tar.gz: 84dc9f162eeae76f58f0c6015ec47686509aabf9ac4cac727016957b69421daf
5
5
  SHA512:
6
- metadata.gz: 97db1ff69373241c3e2ca4633fcca4c800d72b60734bde3b32aae2d3765e86f484e490a4148b98c9e90c40b3fa68cc9b734e56ce9175d1e26ec9bfc767b9707e
7
- data.tar.gz: 074b036daecfc348fbe13510ab7c6cca46fe8d2d630bd81e28808ef4f05e15b47dedda31e9d3a2e423f426ba0980ea04e50439b3533b8b7690c9047be199a1a9
6
+ metadata.gz: af594c4d511623a7e9b37bc3bc45d5d2460de1410e9177f544e900c1d1cd5a555be04bef870c8c5e18a05f46fb242afd6dd9ce26646fc700f071c7a4d917da5a
7
+ data.tar.gz: 49a5e81dd6f504c1e5998f8dc2af53e1b657ddcef387005cd6f313f6c299eb46cc27b7a1c470356de8170435e459d3b1f5e06ef65a1d72f92cea5c0eed797607
data/CHANGELOG.md CHANGED
@@ -1,3 +1,25 @@
1
+ ## [0.99.4](https://github.com/getappmap/appmap-ruby/compare/v0.99.3...v0.99.4) (2023-05-15)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * More robust extraction of test failures ([3851f7c](https://github.com/getappmap/appmap-ruby/commit/3851f7cdf3daf27767f3eb161c69126b607b8a51))
7
+ * Use a copy of rack environment to probe the route ([7ec89a8](https://github.com/getappmap/appmap-ruby/commit/7ec89a8412dc8b9209322ebe9e246f22531b68ab)), closes [#329](https://github.com/getappmap/appmap-ruby/issues/329)
8
+
9
+ ## [0.99.3](https://github.com/getappmap/appmap-ruby/compare/v0.99.2...v0.99.3) (2023-05-10)
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * Capture HTTP requests in Rails on Rack level if possible ([e50a280](https://github.com/getappmap/appmap-ruby/commit/e50a280829cd102b8eecbb83a4e2a76247ee6270)), closes [#323](https://github.com/getappmap/appmap-ruby/issues/323)
15
+
16
+ ## [0.99.2](https://github.com/getappmap/appmap-ruby/compare/v0.99.1...v0.99.2) (2023-05-10)
17
+
18
+
19
+ ### Bug Fixes
20
+
21
+ * Ensure that signature hash key consists of strings ([acb5db9](https://github.com/getappmap/appmap-ruby/commit/acb5db9ecb0a6cdf40de83bf506f45f5283f641b))
22
+
1
23
  ## [0.99.1](https://github.com/getappmap/appmap-ruby/compare/v0.99.0...v0.99.1) (2023-04-24)
2
24
 
3
25
 
data/lib/appmap/config.rb CHANGED
@@ -499,6 +499,11 @@ module AppMap
499
499
  end
500
500
 
501
501
  def never_hook?(cls, method)
502
+ unless method
503
+ HookLog.log "method is nil" if HookLog.enabled?
504
+ return true
505
+ end
506
+
502
507
  _, separator, = ::AppMap::Hook.qualify_method_name(method)
503
508
  if exclude.member?(cls.name) || exclude.member?([ cls.name, separator, method.name ].join)
504
509
  HookLog.log "Hooking of #{method} disabled by configuration" if HookLog.enabled?
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'appmap/handler'
4
+
5
+ module AppMap
6
+ module Handler
7
+ module Rails
8
+ # Context of a rails request tracking.
9
+ # Mostly a utility class to clean up and deduplicate request handler code.
10
+ class Context
11
+ def initialize(environment = nil)
12
+ environment[REQUEST_CONTEXT] = self if environment
13
+ @thread = Thread.current
14
+ end
15
+
16
+ def self.from(environment)
17
+ environment[REQUEST_CONTEXT]
18
+ end
19
+
20
+ def self.create(environment)
21
+ return if from environment
22
+
23
+ new environment
24
+ end
25
+
26
+ def self.remove(env)
27
+ env[REQUEST_CONTEXT] = nil
28
+ end
29
+
30
+ def find_template_render_value
31
+ @thread[TEMPLATE_RENDER_VALUE].tap do
32
+ @thread[TEMPLATE_RENDER_VALUE] = nil
33
+ end
34
+ end
35
+
36
+ # context is set on the rack environment to make sure a single request is only recorded once
37
+ # even if ActionDispatch::Executor is entered more than once (as can happen with engines)
38
+ REQUEST_CONTEXT = 'appmap.handler.request.context'
39
+ end
40
+ end
41
+ end
42
+ end
@@ -3,6 +3,7 @@
3
3
  require 'appmap/event'
4
4
  require 'appmap/hook'
5
5
  require 'appmap/util'
6
+ require 'appmap/handler/rails/context'
6
7
 
7
8
  module AppMap
8
9
  module Handler
@@ -57,6 +58,8 @@ module AppMap
57
58
  private
58
59
 
59
60
  def normalized_path(request, router = ::Rails.application.routes.router)
61
+ # use a cloned environment because the router can modify it
62
+ request = ActionDispatch::Request.new request.env.clone
60
63
  router.recognize request do |route, _|
61
64
  app = route.app
62
65
  next unless app.matches? request
@@ -101,8 +104,11 @@ module AppMap
101
104
  protected
102
105
 
103
106
  def before_hook(receiver, *)
107
+ req = receiver.request
108
+ return unless Context.create req.env
109
+
104
110
  before_hook_start_time = AppMap::Util.gettime()
105
- call_event = HTTPServerRequest.new(receiver.request)
111
+ call_event = HTTPServerRequest.new(req)
106
112
  call_event.call_elapsed_instrumentation = (AppMap::Util.gettime() - before_hook_start_time)
107
113
  # http_server_request events are i/o and do not require a package name.
108
114
  AppMap.tracing.record_event call_event, defined_class: defined_class, method: hook_method
@@ -111,13 +117,56 @@ module AppMap
111
117
 
112
118
  def after_hook(receiver, call_event, elapsed, *)
113
119
  after_hook_start_time = AppMap::Util.gettime()
114
- return_value = Thread.current[TEMPLATE_RENDER_VALUE]
115
- Thread.current[TEMPLATE_RENDER_VALUE] = nil
116
- return_event = HTTPServerResponse.build_from_invocation call_event.id, return_value, elapsed, receiver.response
120
+ return_event = HTTPServerResponse.build_from_invocation \
121
+ call_event.id, Context.new.find_template_render_value, elapsed, receiver.response
117
122
  return_event.elapsed_instrumentation = (AppMap::Util.gettime() - after_hook_start_time) + call_event.call_elapsed_instrumentation
118
123
  call_event.call_elapsed_instrumentation = nil # to stay consistent with elapsed_instrumentation only being stored in return
119
124
  AppMap.tracing.record_event return_event
125
+ Context.remove receiver.request.env
126
+ end
127
+ end
128
+
129
+ # Additional hook for the Rack stack in Rails applications.
130
+ #
131
+ # Hooking just in ActionController can be inaccurate if there's a middleware that
132
+ # intercepts the response and modifies it, or catches an exception
133
+ # or an object and does some other processing.
134
+ # For example, Devise catches a throw from warden on authentication error, then runs
135
+ # ActionController stack AGAIN to render a login page, which it then modifies to change
136
+ # the HTTP status code.
137
+ # ActionDispatch::Executor seems a good place to hook as the central entry point
138
+ # in a Rails application; there are a couple middleware that sometimes sit on top of it
139
+ # but they're usually inconsequential. One issue is that the executor can be entered several
140
+ # times in the stack (especially if Rails engines are used). To handle that, we set
141
+ # a context in the request environment the first time we enter it.
142
+ class RackHook < AppMap::Hook::Method
143
+ def initialize
144
+ super(nil, ActionDispatch::Executor, ActionDispatch::Executor.instance_method(:call))
145
+ end
146
+
147
+ protected
148
+
149
+ def before_hook(_receiver, env)
150
+ return unless Context.create env
151
+
152
+ before_hook_start_time = AppMap::Util.gettime
153
+ call_event = HTTPServerRequest.new ActionDispatch::Request.new(env)
154
+ # http_server_request events are i/o and do not require a package name.
155
+ AppMap.tracing.record_event call_event, defined_class: defined_class, method: hook_method
156
+ [call_event, (AppMap::Util.gettime - before_hook_start_time)]
157
+ end
158
+
159
+ # NOTE: this method smells of :reek:LongParameterList and :reek:UtilityFunction
160
+ # because of the interface it implements.
161
+ # rubocop:disable Metrics/ParameterLists
162
+ def after_hook(_receiver, call_event, elapsed_before, elapsed, after_hook_start_time, rack_return, _exception)
163
+ # TODO: handle exceptions
164
+ return_event = HTTPServerResponse.build_from_invocation \
165
+ call_event.id, Context.new.find_template_render_value, elapsed, ActionDispatch::Response.new(*rack_return)
166
+ return_event.elapsed_instrumentation = (AppMap::Util.gettime - after_hook_start_time) + elapsed_before
167
+ AppMap.tracing.record_event return_event
120
168
  end
169
+ # rubocop:enable Metrics/ParameterLists
121
170
  end
122
171
 
123
172
  # RequestListener listens to the 'start_processing.action_controller' notification as a
@@ -125,6 +174,8 @@ module AppMap
125
174
  # Rails >= 7 due to the hooked methods visibility dropping to private.
126
175
  class RequestListener
127
176
  def self.begin_request(_name, _started, _finished, _unique_id, payload)
177
+ return unless Context.create payload[:request].env
178
+
128
179
  RequestListener.new(payload)
129
180
  end
130
181
 
@@ -149,17 +200,16 @@ module AppMap
149
200
  return unless @request_id == payload[:request].request_id
150
201
 
151
202
  after_hook_start_time = AppMap::Util.gettime()
152
- return_value = Thread.current[TEMPLATE_RENDER_VALUE]
153
- Thread.current[TEMPLATE_RENDER_VALUE] = nil
154
203
  return_event = HTTPServerResponse.build_from_invocation(
155
204
  @call_event.id,
156
- return_value,
205
+ Context.new.find_template_render_value,
157
206
  finished - started,
158
207
  payload[:response] || payload
159
208
  )
160
209
  return_event.elapsed_instrumentation = (AppMap::Util.gettime() - after_hook_start_time) + @call_event.call_elapsed_instrumentation
161
210
 
162
211
  AppMap.tracing.record_event return_event
212
+ Context.remove payload[:request].env
163
213
  ActiveSupport::Notifications.unsubscribe(@subscriber)
164
214
  end
165
215
  end
@@ -4,11 +4,23 @@ require 'appmap/util'
4
4
 
5
5
  module AppMap
6
6
  class Hook
7
+ class << self
8
+ def method_hash_key(cls, method)
9
+ [ cls, method.name ].hash
10
+ rescue TypeError => e
11
+ warn "Error building hash key for #{cls}, #{method}: #{e}"
12
+ end
13
+ end
14
+
15
+
7
16
  SIGNATURES = {}
8
17
  LOOKUP_SIGNATURE = lambda do |id|
9
18
  method = super(id)
10
19
 
11
- signature = SIGNATURES[[ method.owner, method.name ]]
20
+ hash_key = Hook.method_hash_key(method.owner, method)
21
+ return method unless hash_key
22
+
23
+ signature = SIGNATURES[hash_key]
12
24
  if signature
13
25
  method.singleton_class.module_eval do
14
26
  define_method(:parameters) do
@@ -48,7 +60,8 @@ module AppMap
48
60
  end
49
61
 
50
62
  hook_method_parameters = hook_method.parameters.dup.freeze
51
- SIGNATURES[[ hook_class, hook_method.name ]] = hook_method_parameters
63
+ hash_key = Hook.method_hash_key(hook_class, hook_method)
64
+ SIGNATURES[hash_key] = hook_method_parameters if hash_key
52
65
 
53
66
  # irb(main):001:0> Kernel.public_instance_method(:system)
54
67
  # (irb):1:in `public_instance_method': method `system' for module `Kernel' is private (NameError)
@@ -32,6 +32,8 @@ module AppMap
32
32
  AppMap::Handler::Rails::RequestHandler::RequestListener.method(:begin_request)
33
33
  )
34
34
  end
35
+
36
+ AppMap::Handler::Rails::RequestHandler::RackHook.new.activate
35
37
  end
36
38
  end
37
39
  end if AppMap.recording_enabled?
data/lib/appmap/util.rb CHANGED
@@ -161,7 +161,9 @@ module AppMap
161
161
  return unless exception
162
162
 
163
163
  { message: exception.message }.tap do |test_failure|
164
- first_location = exception.backtrace_locations&.find { |location| !Pathname.new(normalize_path(location.absolute_path)).absolute? }
164
+ first_location = exception.backtrace_locations&.find do |location|
165
+ !Pathname.new(normalize_path(location.absolute_path || location.path)).absolute?
166
+ end
165
167
  test_failure[:location] = [ normalize_path(first_location.path), first_location.lineno ].join(':') if first_location
166
168
  end
167
169
  end
@@ -3,7 +3,7 @@
3
3
  module AppMap
4
4
  URL = 'https://github.com/applandinc/appmap-ruby'
5
5
 
6
- VERSION = '0.99.1'
6
+ VERSION = '0.99.4'
7
7
 
8
8
  APPMAP_FORMAT_VERSION = '1.12.0'
9
9
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appmap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.99.1
4
+ version: 0.99.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Gilpin
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-04-24 00:00:00.000000000 Z
11
+ date: 2023-05-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: method_source
@@ -403,6 +403,7 @@ files:
403
403
  - lib/appmap/handler/marshal_load_handler.rb
404
404
  - lib/appmap/handler/net_http_handler.rb
405
405
  - lib/appmap/handler/open_ssl_handler.rb
406
+ - lib/appmap/handler/rails/context.rb
406
407
  - lib/appmap/handler/rails/render_handler.rb
407
408
  - lib/appmap/handler/rails/request_handler.rb
408
409
  - lib/appmap/handler/rails/sql_handler.rb