appmap 0.99.2 → 0.100.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c0d8aa81209b5f8d33fc480c3ff3b7f6cd61027dc51fad74c9ad4f19ba6c7faf
4
- data.tar.gz: 5d8ac162d8cf5eacfc045ff81a16c2b473df79ae0619ddaface5b4bab8d422cb
3
+ metadata.gz: 5f52fa6b2d5e7414553f16fbcf018b4d32214bdfcda4afd2567d9bef11203979
4
+ data.tar.gz: 0d456c7e95dc01ae731bbe9def89ba1585651412d6d8a35eaa2f9c4f83580d2f
5
5
  SHA512:
6
- metadata.gz: 34c7b6b0fd6e45c33158776e3c3f1a8725031913ad49856f24aa5af50cbc41efd2e047face5c3c1f02a9c20670af08be43492e5bd665a17323578c735b07900b
7
- data.tar.gz: 0ea3f5be3436ea8eec1d8e14cd959b67e6e4547d03de6401c6e21250b3ba91b521b57f665298e1e4ec3be9616a1fbcf0a2f11b8ff09a5c53cd0d60c2d4a358f6
6
+ metadata.gz: a07b7341627a96c8ef21930d21a095e9cf849cf06b882d980ec670ce07003dc28e68d2a30e930fb203bf4315ca7aba1f60d43e5d7053d2e56e598ed7d6709302
7
+ data.tar.gz: 691c992e647a2760de0ae0ec75c00efb6824b008abd85f6506c31d34175e52d178d5e3297ab3414eb918fc3e083bba208b0b04c9c74f763330bc4333eb078ce1
data/CHANGELOG.md CHANGED
@@ -1,3 +1,25 @@
1
+ # [0.100.0](https://github.com/getappmap/appmap-ruby/compare/v0.99.4...v0.100.0) (2023-07-11)
2
+
3
+
4
+ ### Features
5
+
6
+ * Add `appmap-agent-config` command to bootstrap appmap.yml ([568acc3](https://github.com/getappmap/appmap-ruby/commit/568acc3948871f736ba72244a04fd54d8be67615))
7
+
8
+ ## [0.99.4](https://github.com/getappmap/appmap-ruby/compare/v0.99.3...v0.99.4) (2023-05-15)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * More robust extraction of test failures ([3851f7c](https://github.com/getappmap/appmap-ruby/commit/3851f7cdf3daf27767f3eb161c69126b607b8a51))
14
+ * 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)
15
+
16
+ ## [0.99.3](https://github.com/getappmap/appmap-ruby/compare/v0.99.2...v0.99.3) (2023-05-10)
17
+
18
+
19
+ ### Bug Fixes
20
+
21
+ * 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)
22
+
1
23
  ## [0.99.2](https://github.com/getappmap/appmap-ruby/compare/v0.99.1...v0.99.2) (2023-05-10)
2
24
 
3
25
 
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'optparse'
5
+ require 'appmap'
6
+ require 'appmap/command/agent_setup/config'
7
+
8
+ @options = { config_file: AppMap::DEFAULT_CONFIG_FILE_PATH, force: false }
9
+
10
+ OptionParser.new do |parser|
11
+ parser.banner = 'Usage: appmap-agent-config [options]'
12
+
13
+ description = "AppMap configuration file path (default: #{AppMap::DEFAULT_CONFIG_FILE_PATH})"
14
+ parser.on('-c', '--config=FILEPATH', description) do |filepath|
15
+ @options[:config_file] = filepath
16
+ end
17
+
18
+ parser.on('-f', '--force', 'Overwrite existing configuration file') do
19
+ @options[:force] = true
20
+ end
21
+ end.parse!
22
+
23
+ begin
24
+ AppMap::Command::AgentSetup::Config.new(@options[:config_file], @options[:force]).perform
25
+
26
+ puts "AppMap configuration file created at #{@options[:config_file]}"
27
+ rescue AppMap::Command::AgentSetup::Config::FileExistsError
28
+ puts "AppMap configuration file already exists at #{@options[:config_file]}"
29
+ puts 'Use the --force option to overwrite.'
30
+ exit 1
31
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'appmap/service/guesser'
5
+
6
+ module AppMap
7
+ module Command
8
+ module AgentSetup
9
+ ConfigStruct = Struct.new(:config_file, :overwrite)
10
+
11
+ class Config < ConfigStruct
12
+ class FileExistsError < StandardError; end
13
+
14
+ def perform
15
+ raise FileExistsError unless overwrite || !File.exist?(config_file)
16
+
17
+ config = {
18
+ 'name' => Service::Guesser.guess_name,
19
+ 'packages' => Service::Guesser.guess_paths.map { |path| { 'path' => path } },
20
+ 'language' => 'ruby',
21
+ 'appmap_dir' => 'tmp/appmap'
22
+ }
23
+
24
+ File.write(config_file, YAML.dump(config))
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -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
@@ -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.2'
6
+ VERSION = '0.100.0'
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.2
4
+ version: 0.100.0
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-05-10 00:00:00.000000000 Z
11
+ date: 2023-07-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: method_source
@@ -308,6 +308,7 @@ description:
308
308
  email:
309
309
  - kgilpin@gmail.com
310
310
  executables:
311
+ - appmap-agent-config
311
312
  - appmap-agent-init
312
313
  - appmap-agent-status
313
314
  - appmap-agent-validate
@@ -347,6 +348,7 @@ files:
347
348
  - examples/mock_webapp/lib/mock_webapp/controller.rb
348
349
  - examples/mock_webapp/lib/mock_webapp/request.rb
349
350
  - examples/mock_webapp/lib/mock_webapp/user.rb
351
+ - exe/appmap-agent-config
350
352
  - exe/appmap-agent-init
351
353
  - exe/appmap-agent-status
352
354
  - exe/appmap-agent-validate
@@ -365,6 +367,7 @@ files:
365
367
  - lib/appmap/builtin_hooks/psych.yml
366
368
  - lib/appmap/builtin_hooks/ruby.yml
367
369
  - lib/appmap/class_map.rb
370
+ - lib/appmap/command/agent_setup/config.rb
368
371
  - lib/appmap/command/agent_setup/init.rb
369
372
  - lib/appmap/command/agent_setup/status.rb
370
373
  - lib/appmap/command/agent_setup/validate.rb
@@ -403,6 +406,7 @@ files:
403
406
  - lib/appmap/handler/marshal_load_handler.rb
404
407
  - lib/appmap/handler/net_http_handler.rb
405
408
  - lib/appmap/handler/open_ssl_handler.rb
409
+ - lib/appmap/handler/rails/context.rb
406
410
  - lib/appmap/handler/rails/render_handler.rb
407
411
  - lib/appmap/handler/rails/request_handler.rb
408
412
  - lib/appmap/handler/rails/sql_handler.rb