appmap 0.99.2 → 0.100.0

Sign up to get free protection for your applications and to get access to all the features.
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