appmap 0.45.1 → 0.46.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/.travis.yml +10 -0
- data/CHANGELOG.md +7 -0
- data/README.md +10 -11
- data/lib/appmap.rb +0 -1
- data/lib/appmap/class_map.rb +7 -15
- data/lib/appmap/config.rb +15 -8
- data/lib/appmap/event.rb +29 -28
- data/lib/appmap/handler/function.rb +1 -1
- data/lib/appmap/handler/rails/request_handler.rb +124 -0
- data/lib/appmap/handler/rails/sql_handler.rb +152 -0
- data/lib/appmap/handler/rails/template.rb +149 -0
- data/lib/appmap/hook/method.rb +1 -1
- data/lib/appmap/railtie.rb +5 -5
- data/lib/appmap/trace.rb +46 -6
- data/lib/appmap/version.rb +1 -1
- data/package-lock.json +3 -3
- data/spec/abstract_controller_base_spec.rb +65 -6
- data/spec/class_map_spec.rb +3 -3
- data/spec/hook_spec.rb +2 -2
- metadata +5 -4
- data/lib/appmap/rails/request_handler.rb +0 -122
- data/lib/appmap/rails/sql_handler.rb +0 -150
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b563a80bb77c42dd44d9fd299ed9b1a273abaf9d43256e23a0856fc06849f74c
|
4
|
+
data.tar.gz: 25dead3dd3c057ee4585c8af0e85e8d495e689a18f3aa02d850bdf470c98d5ff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6bc94e6ee51daf178d9db411f2393538e91e4cc6a985c9b1d28ccc08feda7b6b59708d9f5f0f2cbd2184b058ff39d245c2c84e4cb25a64d5e6ddac96587c2a76
|
7
|
+
data.tar.gz: 252d3955b6daedfb687091c8fb735c3f61d4cca3d7fa6ac44182c9c87f2185f53e886fa4e96f3896c35a7b5bb67e3302120dfa4e978324783b72d446f531b630
|
data/.travis.yml
CHANGED
@@ -17,6 +17,16 @@ before_script:
|
|
17
17
|
cache:
|
18
18
|
bundler: true
|
19
19
|
|
20
|
+
before_install:
|
21
|
+
# see https://blog.travis-ci.com/docker-rate-limits
|
22
|
+
# and also https://www.docker.com/blog/what-you-need-to-know-about-upcoming-docker-hub-rate-limiting/
|
23
|
+
# if we do not use authorized account,
|
24
|
+
# the pulls-per-IP quota is shared with other Travis users
|
25
|
+
- >
|
26
|
+
if [ ! -z "$DOCKERHUB_PASSWORD" ] && [ ! -z "$DOCKERHUB_USERNAME" ]; then
|
27
|
+
echo "$DOCKERHUB_PASSWORD" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin ;
|
28
|
+
fi
|
29
|
+
|
20
30
|
|
21
31
|
# GEM_ALTERNATIVE_NAME only needed for deployment
|
22
32
|
jobs:
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
# [0.46.0](https://github.com/applandinc/appmap-ruby/compare/v0.45.1...v0.46.0) (2021-05-12)
|
2
|
+
|
3
|
+
|
4
|
+
### Features
|
5
|
+
|
6
|
+
* Record view template rendering events and template paths ([973b258](https://github.com/applandinc/appmap-ruby/commit/973b2581b6e2d4e15a1b93331e4e95a88678faae))
|
7
|
+
|
1
8
|
## [0.45.1](https://github.com/applandinc/appmap-ruby/compare/v0.45.0...v0.45.1) (2021-05-04)
|
2
9
|
|
3
10
|
|
data/README.md
CHANGED
@@ -9,7 +9,6 @@
|
|
9
9
|
- [Minitest](#minitest)
|
10
10
|
- [Cucumber](#cucumber)
|
11
11
|
- [Remote recording](#remote-recording)
|
12
|
-
- [Server process recording](#server-process-recording)
|
13
12
|
- [AppMap for VSCode](#appmap-for-vscode)
|
14
13
|
- [AppMap Swagger](#appmap-swagger)
|
15
14
|
- [Uploading AppMaps](#uploading-appmaps)
|
@@ -326,25 +325,25 @@ if defined?(AppMap)
|
|
326
325
|
end
|
327
326
|
```
|
328
327
|
|
329
|
-
2. Download and unpack the [AppLand browser extension](https://github.com/applandinc/appland-browser-extension). Install into Chrome using `chrome://extensions/`. Turn on "Developer Mode" and then load the extension using the "Load unpacked" button.
|
328
|
+
2. (optional) Download and unpack the [AppLand browser extension](https://github.com/applandinc/appland-browser-extension). Install into Chrome using `chrome://extensions/`. Turn on "Developer Mode" and then load the extension using the "Load unpacked" button.
|
330
329
|
|
331
|
-
3. Start your Rails application server
|
330
|
+
3. Start your Rails application server, with `APPMAP_RECORD=true`. For example:
|
332
331
|
|
333
332
|
```sh-session
|
334
|
-
$ bundle exec rails server
|
333
|
+
$ APPMAP_RECORD=true bundle exec rails server
|
335
334
|
```
|
336
335
|
|
337
|
-
4.
|
336
|
+
4. Start the recording
|
338
337
|
|
339
|
-
|
340
|
-
|
341
|
-
6. Open the AppLand browser extension and push `Stop`. The recording will be transferred to the AppLand website and opened in your browser.
|
338
|
+
Option 1: Open the AppLand browser extension and push `Start`.
|
339
|
+
Option 2: `curl -XPOST localhost:3000/_appmap/record` (be sure and get the port number right)
|
342
340
|
|
343
|
-
|
341
|
+
5. Use your app. For example, perform a login flow, or run through a manual UI test.
|
344
342
|
|
345
|
-
|
343
|
+
6. Finish the recording.
|
346
344
|
|
347
|
-
|
345
|
+
Option 1: Open the AppLand browser extension and push `Stop`. The recording will be transferred to the AppLand website and opened in your browser.
|
346
|
+
Option 2: `curl -XDELETE localhost:3000/_appmap/record > recording.appmap.json` - Saves the recording as a local file.
|
348
347
|
|
349
348
|
|
350
349
|
# AppMap for VSCode
|
data/lib/appmap.rb
CHANGED
data/lib/appmap/class_map.rb
CHANGED
@@ -82,16 +82,13 @@ module AppMap
|
|
82
82
|
protected
|
83
83
|
|
84
84
|
def add_function(root, method)
|
85
|
-
package = method.package
|
86
|
-
static = method.static
|
87
|
-
|
88
85
|
object_infos = [
|
89
86
|
{
|
90
|
-
name: package
|
87
|
+
name: method.package,
|
91
88
|
type: 'package'
|
92
89
|
}
|
93
90
|
]
|
94
|
-
object_infos += method.
|
91
|
+
object_infos += method.class_name.split('::').map do |name|
|
95
92
|
{
|
96
93
|
name: name,
|
97
94
|
type: 'class'
|
@@ -100,7 +97,7 @@ module AppMap
|
|
100
97
|
function_info = {
|
101
98
|
name: method.name,
|
102
99
|
type: 'function',
|
103
|
-
static: static
|
100
|
+
static: method.static
|
104
101
|
}
|
105
102
|
location = method.source_location
|
106
103
|
|
@@ -108,20 +105,15 @@ module AppMap
|
|
108
105
|
if location
|
109
106
|
location_file, lineno = location
|
110
107
|
location_file = location_file[Dir.pwd.length + 1..-1] if location_file.index(Dir.pwd) == 0
|
111
|
-
[ location_file, lineno ].join(':')
|
108
|
+
[ location_file, lineno ].compact.join(':')
|
112
109
|
else
|
113
|
-
[ method.
|
110
|
+
[ method.class_name, method.static ? '.' : '#', method.name ].join
|
114
111
|
end
|
115
112
|
|
116
|
-
comment =
|
117
|
-
method.comment
|
118
|
-
rescue MethodSource::SourceNotFoundError
|
119
|
-
nil
|
120
|
-
end
|
121
|
-
|
113
|
+
comment = method.comment
|
122
114
|
function_info[:comment] = comment unless comment.blank?
|
123
115
|
|
124
|
-
function_info[:labels] = parse_labels(comment) + (
|
116
|
+
function_info[:labels] = parse_labels(comment) + (method.labels || [])
|
125
117
|
object_infos << function_info
|
126
118
|
|
127
119
|
parent = root
|
data/lib/appmap/config.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'appmap/handler/net_http'
|
4
|
+
require 'appmap/handler/rails/template'
|
5
|
+
|
3
6
|
module AppMap
|
4
7
|
class Config
|
5
8
|
# Specifies a code +path+ to be mapped.
|
@@ -120,15 +123,19 @@ module AppMap
|
|
120
123
|
# Methods that should always be hooked, with their containing
|
121
124
|
# package and labels that should be applied to them.
|
122
125
|
HOOKED_METHODS = {
|
123
|
-
'ActionView::Renderer' => TargetMethods.new(:render, Package.build_from_gem('actionview', package_name: 'action_view', labels: %w[mvc.view], optional: true)
|
124
|
-
|
125
|
-
|
126
|
-
'
|
127
|
-
|
128
|
-
|
126
|
+
'ActionView::Renderer' => TargetMethods.new(:render, Package.build_from_gem('actionview', shallow: false, package_name: 'action_view', labels: %w[mvc.view], optional: true).tap do |package|
|
127
|
+
package.handler_class = AppMap::Handler::Rails::Template::RenderHandler if package
|
128
|
+
end),
|
129
|
+
'ActionView::Resolver' => TargetMethods.new(%i[find_all find_all_anywhere], Package.build_from_gem('actionview', shallow: false, package_name: 'action_view', labels: %w[mvc.template.resolver], optional: true).tap do |package|
|
130
|
+
package.handler_class = AppMap::Handler::Rails::Template::ResolverHandler if package
|
131
|
+
end),
|
132
|
+
'ActionDispatch::Request::Session' => TargetMethods.new(%i[destroy [] dig values []= clear update delete fetch merge], Package.build_from_gem('actionpack', shallow: false, package_name: 'action_dispatch', labels: %w[http.session], optional: true)),
|
133
|
+
'ActionDispatch::Cookies::CookieJar' => TargetMethods.new(%i[[]= clear update delete recycle], Package.build_from_gem('actionpack', shallow: false, package_name: 'action_dispatch', labels: %w[http.cookie], optional: true)),
|
134
|
+
'ActionDispatch::Cookies::EncryptedCookieJar' => TargetMethods.new(%i[[]=], Package.build_from_gem('actionpack', shallow: false, package_name: 'action_dispatch', labels: %w[http.cookie crypto.encrypt], optional: true)),
|
135
|
+
'CanCan::ControllerAdditions' => TargetMethods.new(%i[authorize! can? cannot?], Package.build_from_gem('cancancan', shallow: false, labels: %w[security.authorization], optional: true)),
|
136
|
+
'CanCan::Ability' => TargetMethods.new(%i[authorize!], Package.build_from_gem('cancancan', shallow: false, labels: %w[security.authorization], optional: true)),
|
129
137
|
'ActionController::Instrumentation' => [
|
130
|
-
TargetMethods.new(%i[process_action send_file send_data redirect_to], Package.build_from_gem('actionpack', package_name: 'action_controller', labels: %w[mvc.controller], optional: true))
|
131
|
-
TargetMethods.new(%i[render], Package.build_from_gem('actionpack', package_name: 'action_controller', labels: %w[mvc.view], optional: true)),
|
138
|
+
TargetMethods.new(%i[process_action send_file send_data redirect_to], Package.build_from_gem('actionpack', shallow: false, package_name: 'action_controller', labels: %w[mvc.controller], optional: true))
|
132
139
|
]
|
133
140
|
}.freeze
|
134
141
|
|
data/lib/appmap/event.rb
CHANGED
@@ -21,10 +21,10 @@ module AppMap
|
|
21
21
|
LIMIT = 100
|
22
22
|
|
23
23
|
class << self
|
24
|
-
def build_from_invocation(
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
def build_from_invocation(event_type, event:)
|
25
|
+
event.id = AppMap::Event.next_id_counter
|
26
|
+
event.event = event_type
|
27
|
+
event.thread_id = Thread.current.object_id
|
28
28
|
end
|
29
29
|
|
30
30
|
# Gets a display string for a value. This is not meant to be a machine deserializable value.
|
@@ -48,8 +48,6 @@ module AppMap
|
|
48
48
|
nil
|
49
49
|
end
|
50
50
|
|
51
|
-
protected
|
52
|
-
|
53
51
|
# Heuristic for dynamically defined class whose name can be nil
|
54
52
|
def best_class_name(value)
|
55
53
|
value_cls = value.class
|
@@ -103,19 +101,20 @@ module AppMap
|
|
103
101
|
attr_accessor :defined_class, :method_id, :path, :lineno, :parameters, :receiver, :static
|
104
102
|
|
105
103
|
class << self
|
106
|
-
def build_from_invocation(
|
104
|
+
def build_from_invocation(defined_class, method, receiver, arguments, event: MethodCall.new)
|
105
|
+
event ||= MethodCall.new
|
107
106
|
defined_class ||= 'Class'
|
108
|
-
|
107
|
+
event.tap do
|
109
108
|
static = receiver.is_a?(Module)
|
110
|
-
|
111
|
-
|
109
|
+
event.defined_class = defined_class
|
110
|
+
event.method_id = method.name.to_s
|
112
111
|
if method.source_location
|
113
112
|
path = method.source_location[0]
|
114
113
|
path = path[Dir.pwd.length + 1..-1] if path.index(Dir.pwd) == 0
|
115
|
-
|
116
|
-
|
114
|
+
event.path = path
|
115
|
+
event.lineno = method.source_location[1]
|
117
116
|
else
|
118
|
-
|
117
|
+
event.path = [ defined_class, static ? '.' : '#', method.name ].join
|
119
118
|
end
|
120
119
|
|
121
120
|
# Check if the method has key parameters. If there are any they'll always be last.
|
@@ -123,7 +122,7 @@ module AppMap
|
|
123
122
|
has_key = [[:dummy], *method.parameters].last.first.to_s.start_with?('key') && arguments[-1].is_a?(Hash)
|
124
123
|
kwargs = has_key && arguments[-1].dup || {}
|
125
124
|
|
126
|
-
|
125
|
+
event.parameters = method.parameters.map.with_index do |method_param, idx|
|
127
126
|
param_type, param_name = method_param
|
128
127
|
param_name ||= 'arg'
|
129
128
|
value = case param_type
|
@@ -144,13 +143,13 @@ module AppMap
|
|
144
143
|
kind: param_type
|
145
144
|
}
|
146
145
|
end
|
147
|
-
|
146
|
+
event.receiver = {
|
148
147
|
class: best_class_name(receiver),
|
149
148
|
object_id: receiver.__id__,
|
150
149
|
value: display_string(receiver)
|
151
150
|
}
|
152
|
-
|
153
|
-
MethodEvent.build_from_invocation(
|
151
|
+
event.static = static
|
152
|
+
MethodEvent.build_from_invocation(:call, event: event)
|
154
153
|
end
|
155
154
|
end
|
156
155
|
end
|
@@ -175,11 +174,12 @@ module AppMap
|
|
175
174
|
attr_accessor :parent_id, :elapsed
|
176
175
|
|
177
176
|
class << self
|
178
|
-
def build_from_invocation(
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
177
|
+
def build_from_invocation(parent_id, elapsed: nil, event: MethodReturnIgnoreValue.new)
|
178
|
+
event ||= MethodReturnIgnoreValue.new
|
179
|
+
event.tap do |_|
|
180
|
+
event.parent_id = parent_id
|
181
|
+
event.elapsed = elapsed
|
182
|
+
MethodEvent.build_from_invocation(:return, event: event)
|
183
183
|
end
|
184
184
|
end
|
185
185
|
end
|
@@ -187,7 +187,7 @@ module AppMap
|
|
187
187
|
def to_h
|
188
188
|
super.tap do |h|
|
189
189
|
h[:parent_id] = parent_id
|
190
|
-
h[:elapsed] = elapsed
|
190
|
+
h[:elapsed] = elapsed if elapsed
|
191
191
|
end
|
192
192
|
end
|
193
193
|
end
|
@@ -196,10 +196,11 @@ module AppMap
|
|
196
196
|
attr_accessor :return_value, :exceptions
|
197
197
|
|
198
198
|
class << self
|
199
|
-
def build_from_invocation(
|
200
|
-
|
199
|
+
def build_from_invocation(parent_id, return_value, exception, elapsed: nil, event: MethodReturn.new)
|
200
|
+
event ||= MethodReturn.new
|
201
|
+
event.tap do |_|
|
201
202
|
if return_value
|
202
|
-
|
203
|
+
event.return_value = {
|
203
204
|
class: best_class_name(return_value),
|
204
205
|
value: display_string(return_value),
|
205
206
|
object_id: return_value.__id__
|
@@ -220,9 +221,9 @@ module AppMap
|
|
220
221
|
next_exception = next_exception.cause
|
221
222
|
end
|
222
223
|
|
223
|
-
|
224
|
+
event.exceptions = exceptions
|
224
225
|
end
|
225
|
-
MethodReturnIgnoreValue.build_from_invocation(
|
226
|
+
MethodReturnIgnoreValue.build_from_invocation(parent_id, elapsed: elapsed, event: event)
|
226
227
|
end
|
227
228
|
end
|
228
229
|
end
|
@@ -11,7 +11,7 @@ module AppMap
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def handle_return(call_event_id, elapsed, return_value, exception)
|
14
|
-
AppMap::Event::MethodReturn.build_from_invocation(call_event_id,
|
14
|
+
AppMap::Event::MethodReturn.build_from_invocation(call_event_id, return_value, exception, elapsed: elapsed)
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'appmap/event'
|
4
|
+
require 'appmap/hook'
|
5
|
+
|
6
|
+
module AppMap
|
7
|
+
module Handler
|
8
|
+
module Rails
|
9
|
+
module RequestHandler
|
10
|
+
class HTTPServerRequest < AppMap::Event::MethodEvent
|
11
|
+
attr_accessor :normalized_path_info, :request_method, :path_info, :params, :mime_type, :headers, :authorization
|
12
|
+
|
13
|
+
def initialize(request)
|
14
|
+
super AppMap::Event.next_id_counter, :call, Thread.current.object_id
|
15
|
+
|
16
|
+
self.request_method = request.request_method
|
17
|
+
self.normalized_path_info = normalized_path(request)
|
18
|
+
self.mime_type = request.headers['Content-Type']
|
19
|
+
self.headers = AppMap::Util.select_headers(request.env)
|
20
|
+
self.authorization = request.headers['Authorization']
|
21
|
+
self.path_info = request.path_info.split('?')[0]
|
22
|
+
# ActionDispatch::Http::ParameterFilter is deprecated
|
23
|
+
parameter_filter_cls = \
|
24
|
+
if defined?(ActiveSupport::ParameterFilter)
|
25
|
+
ActiveSupport::ParameterFilter
|
26
|
+
else
|
27
|
+
ActionDispatch::Http::ParameterFilter
|
28
|
+
end
|
29
|
+
self.params = parameter_filter_cls.new(::Rails.application.config.filter_parameters).filter(request.params)
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_h
|
33
|
+
super.tap do |h|
|
34
|
+
h[:http_server_request] = {
|
35
|
+
request_method: request_method,
|
36
|
+
path_info: path_info,
|
37
|
+
mime_type: mime_type,
|
38
|
+
normalized_path_info: normalized_path_info,
|
39
|
+
authorization: authorization,
|
40
|
+
headers: headers,
|
41
|
+
}.compact
|
42
|
+
|
43
|
+
unless params.blank?
|
44
|
+
h[:message] = params.keys.map do |key|
|
45
|
+
val = params[key]
|
46
|
+
{
|
47
|
+
name: key,
|
48
|
+
class: val.class.name,
|
49
|
+
value: self.class.display_string(val),
|
50
|
+
object_id: val.__id__,
|
51
|
+
}.tap do |message|
|
52
|
+
properties = object_properties(val)
|
53
|
+
message[:properties] = properties if properties
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def normalized_path(request, router = ::Rails.application.routes.router)
|
63
|
+
router.recognize request do |route, _|
|
64
|
+
app = route.app
|
65
|
+
next unless app.matches? request
|
66
|
+
return normalized_path request, app.rack_app.routes.router if app.engine?
|
67
|
+
|
68
|
+
return route.path.spec.to_s
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class HTTPServerResponse < AppMap::Event::MethodReturnIgnoreValue
|
74
|
+
attr_accessor :status, :mime_type, :headers
|
75
|
+
|
76
|
+
def initialize(response, parent_id, elapsed)
|
77
|
+
super AppMap::Event.next_id_counter, :return, Thread.current.object_id
|
78
|
+
|
79
|
+
self.status = response.status
|
80
|
+
self.mime_type = response.headers['Content-Type']
|
81
|
+
self.parent_id = parent_id
|
82
|
+
self.elapsed = elapsed
|
83
|
+
self.headers = AppMap::Util.select_headers(response.headers)
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_h
|
87
|
+
super.tap do |h|
|
88
|
+
h[:http_server_response] = {
|
89
|
+
status_code: status,
|
90
|
+
mime_type: mime_type,
|
91
|
+
headers: headers
|
92
|
+
}.compact
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class HookMethod < AppMap::Hook::Method
|
98
|
+
def initialize
|
99
|
+
# ActionController::Instrumentation has issued start_processing.action_controller and
|
100
|
+
# process_action.action_controller since Rails 3. Therefore it's a stable place to hook
|
101
|
+
# the request. Rails controller notifications can't be used directly because they don't
|
102
|
+
# provide response headers, and we want the Content-Type.
|
103
|
+
super(nil, ActionController::Instrumentation, ActionController::Instrumentation.instance_method(:process_action))
|
104
|
+
end
|
105
|
+
|
106
|
+
protected
|
107
|
+
|
108
|
+
def before_hook(receiver, defined_class, _) # args
|
109
|
+
call_event = HTTPServerRequest.new(receiver.request)
|
110
|
+
# http_server_request events are i/o and do not require a package name.
|
111
|
+
AppMap.tracing.record_event call_event, defined_class: defined_class, method: hook_method
|
112
|
+
[ call_event, TIME_NOW.call ]
|
113
|
+
end
|
114
|
+
|
115
|
+
def after_hook(receiver, call_event, start_time, _, _) # return_value, exception
|
116
|
+
elapsed = TIME_NOW.call - start_time
|
117
|
+
return_event = HTTPServerResponse.new receiver.response, call_event.id, elapsed
|
118
|
+
AppMap.tracing.record_event return_event
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|