appmap 0.86.0 → 0.89.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +32 -0
- data/Gemfile +2 -0
- data/README.md +3 -5
- data/appmap.gemspec +2 -2
- data/lib/appmap/gem_hooks/jwt.yml +8 -0
- data/lib/appmap/handler/rails/request_handler.rb +42 -2
- data/lib/appmap/hook.rb +10 -9
- data/lib/appmap/middleware/remote_recording.rb +51 -6
- data/lib/appmap/railtie.rb +9 -1
- data/lib/appmap/trace.rb +8 -4
- data/lib/appmap/version.rb +1 -1
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9d046b1cedf455ae967a663b200c05751c4c0659defa5e7dce0a7f760ba865b6
|
4
|
+
data.tar.gz: 790f5a9705b0d32b60467ecb8e51dc75e8fdbb2ac3af2022ee1c8a1a7c8cc8ca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7c999d39cb9385f3758746cf93d50f291a4a6f9ae1c8b33266897975bdd9aabf4c8a3f666a3b3650ee70a24978d574a5db0bfefe2539240b582f1a430eabe399
|
7
|
+
data.tar.gz: 731810f9c4afe0c0c9130ab81d71e0c20f85860bf57fef5d5736845aba4c4d240dff101bc5a46aef4c6ca9e08f5adfe90ac749bb059fc7ecacd08aec77926f4e
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,35 @@
|
|
1
|
+
# [0.89.0](https://github.com/applandinc/appmap-ruby/compare/v0.88.0...v0.89.0) (2022-09-07)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* Make Rack a runtime dependency ([3f2924d](https://github.com/applandinc/appmap-ruby/commit/3f2924d41af291bfe771829c2ea7978332ff1d44))
|
7
|
+
|
8
|
+
|
9
|
+
### Features
|
10
|
+
|
11
|
+
* Add builtin labels for JWT ([3569333](https://github.com/applandinc/appmap-ruby/commit/3569333e99f82e03fff892001f2b65eb9e8aa52a))
|
12
|
+
* Write an AppMap for each observed HTTP request ([2e89eaf](https://github.com/applandinc/appmap-ruby/commit/2e89eaf1b1f327771d1f4f0642fa6c202e0f3afb))
|
13
|
+
|
14
|
+
# [0.88.0](https://github.com/applandinc/appmap-ruby/compare/v0.87.0...v0.88.0) (2022-08-31)
|
15
|
+
|
16
|
+
|
17
|
+
### Bug Fixes
|
18
|
+
|
19
|
+
* Allow recording of error responses ([e538556](https://github.com/applandinc/appmap-ruby/commit/e5385560cbbab2d27ce6c0a1620d5c7539e0d985))
|
20
|
+
|
21
|
+
|
22
|
+
### Features
|
23
|
+
|
24
|
+
* Add HTTP server request support for Rails 7 ([bd7f6c9](https://github.com/applandinc/appmap-ruby/commit/bd7f6c9109ee6c850250016276c8fbb6a8a66a2e))
|
25
|
+
|
26
|
+
# [0.87.0](https://github.com/applandinc/appmap-ruby/compare/v0.86.0...v0.87.0) (2022-08-19)
|
27
|
+
|
28
|
+
|
29
|
+
### Features
|
30
|
+
|
31
|
+
* Improve performance of initial hooking ([901e262](https://github.com/applandinc/appmap-ruby/commit/901e26237027920ede6b0f9d4bc3d175c861b23a))
|
32
|
+
|
1
33
|
# [0.86.0](https://github.com/applandinc/appmap-ruby/compare/v0.85.0...v0.86.0) (2022-08-10)
|
2
34
|
|
3
35
|
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -92,9 +92,7 @@ You can launch a database like this:
|
|
92
92
|
```
|
93
93
|
➜ docker-compose -p appmap-ruby up -d
|
94
94
|
... stuff
|
95
|
-
➜ docker-compose
|
96
|
-
|
97
|
-
|
98
|
-
appmap-ruby_pg_1 docker-entrypoint.sh postgres Up (healthy) 0.0.0.0:59134->5432/tcp
|
99
|
-
➜ export DATABASE_URL=postgres://postgres@localhost:59134
|
95
|
+
➜ docker-compose port pg 5432
|
96
|
+
0.0.0.0:64479
|
97
|
+
➜ export DATABASE_URL=postgres://postgres@localhost:64479
|
100
98
|
```
|
data/appmap.gemspec
CHANGED
@@ -28,10 +28,10 @@ Gem::Specification.new do |spec|
|
|
28
28
|
spec.require_paths = ['lib']
|
29
29
|
|
30
30
|
spec.add_dependency 'method_source'
|
31
|
-
spec.add_dependency 'rack'
|
32
31
|
spec.add_dependency 'reverse_markdown'
|
33
32
|
|
34
33
|
spec.add_runtime_dependency 'activesupport'
|
34
|
+
spec.add_runtime_dependency 'rack'
|
35
35
|
|
36
36
|
spec.add_development_dependency 'bundler', '>= 1.16'
|
37
37
|
spec.add_development_dependency 'minitest', '~> 5.15'
|
@@ -39,7 +39,7 @@ Gem::Specification.new do |spec|
|
|
39
39
|
spec.add_development_dependency 'rake', '>= 12.3.3'
|
40
40
|
spec.add_development_dependency 'rdoc'
|
41
41
|
spec.add_development_dependency 'rubocop'
|
42
|
-
spec.add_development_dependency
|
42
|
+
spec.add_development_dependency 'rake-compiler'
|
43
43
|
|
44
44
|
# Testing
|
45
45
|
spec.add_development_dependency 'climate_control'
|
@@ -73,8 +73,8 @@ module AppMap
|
|
73
73
|
class << self
|
74
74
|
def build_from_invocation(parent_id, return_value, elapsed, response, event: HTTPServerResponse.new)
|
75
75
|
event ||= HTTPServerResponse.new
|
76
|
-
event.status = response.status
|
77
|
-
event.headers = response.headers.dup
|
76
|
+
event.status = response[:status] || response.status
|
77
|
+
event.headers = (response[:headers] || response.headers).dup
|
78
78
|
AppMap::Event::MethodReturn.build_from_invocation parent_id, return_value, nil, elapsed: elapsed, event: event, parameter_schema: true
|
79
79
|
end
|
80
80
|
end
|
@@ -114,6 +114,46 @@ module AppMap
|
|
114
114
|
AppMap.tracing.record_event return_event
|
115
115
|
end
|
116
116
|
end
|
117
|
+
|
118
|
+
# RequestListener listens to the 'start_processing.action_controller' notification as a
|
119
|
+
# source of HTTP server request events. A strategy other than HookMethod is required for
|
120
|
+
# Rails >= 7 due to the hooked methods visibility dropping to private.
|
121
|
+
class RequestListener
|
122
|
+
def self.begin_request(_name, _started, _finished, _unique_id, payload)
|
123
|
+
RequestListener.new(payload)
|
124
|
+
end
|
125
|
+
|
126
|
+
protected
|
127
|
+
|
128
|
+
def initialize(payload)
|
129
|
+
@request_id = payload[:request].request_id
|
130
|
+
@subscriber = self.class.instance_method(:after_hook).bind(self)
|
131
|
+
|
132
|
+
ActiveSupport::Notifications.subscribe 'process_action.action_controller', @subscriber
|
133
|
+
before_hook payload
|
134
|
+
end
|
135
|
+
|
136
|
+
def before_hook(payload)
|
137
|
+
@call_event = HTTPServerRequest.new payload[:request]
|
138
|
+
AppMap.tracing.record_event @call_event
|
139
|
+
end
|
140
|
+
|
141
|
+
def after_hook(_name, started, finished, _unique_id, payload)
|
142
|
+
return unless @request_id == payload[:request].request_id
|
143
|
+
|
144
|
+
return_value = Thread.current[TEMPLATE_RENDER_VALUE]
|
145
|
+
Thread.current[TEMPLATE_RENDER_VALUE] = nil
|
146
|
+
return_event = HTTPServerResponse.build_from_invocation(
|
147
|
+
@call_event.id,
|
148
|
+
return_value,
|
149
|
+
finished - started,
|
150
|
+
payload[:response] || payload
|
151
|
+
)
|
152
|
+
|
153
|
+
AppMap.tracing.record_event return_event
|
154
|
+
ActiveSupport::Notifications.unsubscribe(@subscriber)
|
155
|
+
end
|
156
|
+
end
|
117
157
|
end
|
118
158
|
end
|
119
159
|
end
|
data/lib/appmap/hook.rb
CHANGED
@@ -183,12 +183,6 @@ module AppMap
|
|
183
183
|
|
184
184
|
hook = lambda do |hook_cls|
|
185
185
|
lambda do |method_id|
|
186
|
-
# Don't try and trace the AppMap methods or there will be
|
187
|
-
# a stack overflow in the defined hook method.
|
188
|
-
next if %w[Marshal AppMap ActiveSupport].member?((hook_cls&.name || '').split('::')[0])
|
189
|
-
|
190
|
-
next if method_id == :call
|
191
|
-
|
192
186
|
method = \
|
193
187
|
begin
|
194
188
|
hook_cls.instance_method(method_id)
|
@@ -197,6 +191,16 @@ module AppMap
|
|
197
191
|
next
|
198
192
|
end
|
199
193
|
|
194
|
+
package = config.lookup_package(hook_cls, method)
|
195
|
+
# doing this check first returned early in 98.7% of cases in sample_app_6th_ed
|
196
|
+
next unless package
|
197
|
+
|
198
|
+
# Don't try and trace the AppMap methods or there will be
|
199
|
+
# a stack overflow in the defined hook method.
|
200
|
+
next if %w[Marshal AppMap ActiveSupport].member?((hook_cls&.name || '').split('::')[0])
|
201
|
+
|
202
|
+
next if method_id == :call
|
203
|
+
|
200
204
|
next if self.class.already_hooked?(method)
|
201
205
|
|
202
206
|
warn "AppMap: Examining #{hook_cls} #{method.name}" if LOG
|
@@ -206,9 +210,6 @@ module AppMap
|
|
206
210
|
# TODO: Figure out how to tell the difference?
|
207
211
|
next unless disasm
|
208
212
|
|
209
|
-
package = config.lookup_package(hook_cls, method)
|
210
|
-
next unless package
|
211
|
-
|
212
213
|
package.handler_class.new(package, hook_cls, method).activate
|
213
214
|
end
|
214
215
|
end
|
@@ -2,7 +2,8 @@
|
|
2
2
|
|
3
3
|
module AppMap
|
4
4
|
module Middleware
|
5
|
-
# RemoteRecording adds `/_appmap/record` routes to control recordings via HTTP requests
|
5
|
+
# RemoteRecording adds `/_appmap/record` routes to control recordings via HTTP requests.
|
6
|
+
# It can also be enabled to emit an AppMap for each request.
|
6
7
|
class RemoteRecording
|
7
8
|
def initialize(app)
|
8
9
|
require 'json'
|
@@ -21,7 +22,7 @@ module AppMap
|
|
21
22
|
end
|
22
23
|
end
|
23
24
|
|
24
|
-
def
|
25
|
+
def ws_start_recording
|
25
26
|
return [ 409, 'Recording is already in progress' ] if @tracer
|
26
27
|
|
27
28
|
@events = []
|
@@ -32,7 +33,7 @@ module AppMap
|
|
32
33
|
[ 200 ]
|
33
34
|
end
|
34
35
|
|
35
|
-
def
|
36
|
+
def ws_stop_recording(req)
|
36
37
|
return [ 404, 'No recording is in progress' ] unless @tracer
|
37
38
|
|
38
39
|
tracer = @tracer
|
@@ -75,10 +76,50 @@ module AppMap
|
|
75
76
|
end
|
76
77
|
|
77
78
|
def call(env)
|
79
|
+
# Note: Puma config is avaliable here. For example:
|
80
|
+
# $ env['puma.config'].final_options[:workers]
|
81
|
+
# 0
|
82
|
+
|
78
83
|
req = Rack::Request.new(env)
|
79
84
|
return handle_record_request(req) if req.path == '/_appmap/record'
|
80
85
|
|
81
|
-
|
86
|
+
start_time = Time.now
|
87
|
+
# Support multi-threaded web server such as Puma by recording each thread
|
88
|
+
# into a separate Tracer.
|
89
|
+
tracer = AppMap.tracing.trace(thread: Thread.current) if record_all_requests?
|
90
|
+
|
91
|
+
@app.call(env).tap do |status, headers|
|
92
|
+
if tracer
|
93
|
+
AppMap.tracing.delete(tracer)
|
94
|
+
|
95
|
+
events = tracer.events.map(&:to_h)
|
96
|
+
|
97
|
+
appmap_name = "#{req.request_method} #{req.path} (#{status}) - #{start_time.strftime('%T.%L')}"
|
98
|
+
appmap_file_name = AppMap::Util.scenario_filename([ start_time.to_f, req.url ].join('_'))
|
99
|
+
output_dir = File.join(AppMap::DEFAULT_APPMAP_DIR, 'requests')
|
100
|
+
appmap_file_path = File.join(output_dir, appmap_file_name)
|
101
|
+
|
102
|
+
metadata = AppMap.detect_metadata
|
103
|
+
metadata[:name] = appmap_name
|
104
|
+
metadata[:timestamp] = start_time.to_f
|
105
|
+
metadata[:recorder] = {
|
106
|
+
name: 'record_requests'
|
107
|
+
}
|
108
|
+
|
109
|
+
appmap = {
|
110
|
+
version: AppMap::APPMAP_FORMAT_VERSION,
|
111
|
+
classMap: AppMap.class_map(tracer.event_methods),
|
112
|
+
metadata: metadata,
|
113
|
+
events: events
|
114
|
+
}
|
115
|
+
|
116
|
+
FileUtils.mkdir_p(output_dir)
|
117
|
+
File.write(appmap_file_path, JSON.generate(appmap))
|
118
|
+
|
119
|
+
headers['AppMap-Name'] = File.expand_path(appmap_name)
|
120
|
+
headers['AppMap-File-Name'] = File.expand_path(appmap_file_path)
|
121
|
+
end
|
122
|
+
end
|
82
123
|
end
|
83
124
|
|
84
125
|
def recording_state
|
@@ -92,9 +133,9 @@ module AppMap
|
|
92
133
|
if method.eql?('GET')
|
93
134
|
recording_state
|
94
135
|
elsif method.eql?('POST')
|
95
|
-
|
136
|
+
ws_start_recording
|
96
137
|
elsif method.eql?('DELETE')
|
97
|
-
|
138
|
+
ws_stop_recording(req)
|
98
139
|
else
|
99
140
|
[ 404, '' ]
|
100
141
|
end
|
@@ -106,6 +147,10 @@ module AppMap
|
|
106
147
|
headers['Content-Type'] && headers['Content-Type'] =~ /html/
|
107
148
|
end
|
108
149
|
|
150
|
+
def record_all_requests?
|
151
|
+
ENV['APPMAP_RECORD_REQUESTS'] == 'true'
|
152
|
+
end
|
153
|
+
|
109
154
|
def recording?
|
110
155
|
!@event_thread.nil?
|
111
156
|
end
|
data/lib/appmap/railtie.rb
CHANGED
@@ -18,7 +18,15 @@ module AppMap
|
|
18
18
|
ActiveSupport::Notifications.subscribe 'sql.sequel', AppMap::Handler::Rails::SQLHandler.new
|
19
19
|
ActiveSupport::Notifications.subscribe 'sql.active_record', AppMap::Handler::Rails::SQLHandler.new
|
20
20
|
|
21
|
-
|
21
|
+
http_hook_available = ActionController::Instrumentation.public_instance_methods.include?(:process_action)
|
22
|
+
if http_hook_available
|
23
|
+
AppMap::Handler::Rails::RequestHandler::HookMethod.new.activate
|
24
|
+
else
|
25
|
+
ActiveSupport::Notifications.subscribe(
|
26
|
+
'start_processing.action_controller',
|
27
|
+
AppMap::Handler::Rails::RequestHandler::RequestListener.method(:begin_request)
|
28
|
+
)
|
29
|
+
end
|
22
30
|
end
|
23
31
|
end
|
24
32
|
end if ENV['APPMAP'] == 'true'
|
data/lib/appmap/trace.rb
CHANGED
@@ -48,8 +48,8 @@ module AppMap
|
|
48
48
|
@tracers.empty?
|
49
49
|
end
|
50
50
|
|
51
|
-
def trace(enable: true)
|
52
|
-
Tracer.new.tap do |tracer|
|
51
|
+
def trace(enable: true, thread: nil)
|
52
|
+
Tracer.new(thread_id: thread&.object_id).tap do |tracer|
|
53
53
|
@tracers << tracer
|
54
54
|
tracer.enable if enable
|
55
55
|
end
|
@@ -64,7 +64,7 @@ module AppMap
|
|
64
64
|
end
|
65
65
|
|
66
66
|
def record_event(event, package: nil, defined_class: nil, method: nil)
|
67
|
-
@tracers.each do |tracer|
|
67
|
+
@tracers.select { |tracer| tracer.thread_id.nil? || tracer.thread_id === event.thread_id }.each do |tracer|
|
68
68
|
tracer.record_event(event, package: package, defined_class: defined_class, method: method)
|
69
69
|
end
|
70
70
|
end
|
@@ -114,14 +114,16 @@ module AppMap
|
|
114
114
|
|
115
115
|
class Tracer
|
116
116
|
attr_accessor :stacks
|
117
|
+
attr_reader :thread_id, :events
|
117
118
|
|
118
119
|
# Records the events which happen in a program.
|
119
|
-
def initialize
|
120
|
+
def initialize(thread_id: nil)
|
120
121
|
@events = []
|
121
122
|
@last_package_for_thread = {}
|
122
123
|
@methods = Set.new
|
123
124
|
@stack_printer = StackPrinter.new if StackPrinter.enabled?
|
124
125
|
@enabled = false
|
126
|
+
@thread_id = thread_id
|
125
127
|
end
|
126
128
|
|
127
129
|
def enable
|
@@ -143,6 +145,8 @@ module AppMap
|
|
143
145
|
def record_event(event, package: nil, defined_class: nil, method: nil)
|
144
146
|
return unless @enabled
|
145
147
|
|
148
|
+
raise "Expected event in thread #{@thread_id}, got #{event.thread_id}" if @thread_id && @thread_id != event.thread_id
|
149
|
+
|
146
150
|
@stack_printer.record(event) if @stack_printer
|
147
151
|
@last_package_for_thread[Thread.current.object_id] = package if package
|
148
152
|
@events << event
|
data/lib/appmap/version.rb
CHANGED
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.
|
4
|
+
version: 0.89.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: 2022-
|
11
|
+
date: 2022-09-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: method_source
|
@@ -25,7 +25,7 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: reverse_markdown
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
@@ -39,7 +39,7 @@ dependencies:
|
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: activesupport
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
@@ -53,7 +53,7 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: rack
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
@@ -386,6 +386,7 @@ files:
|
|
386
386
|
- lib/appmap/gem_hooks/activerecord.yml
|
387
387
|
- lib/appmap/gem_hooks/cancancan.yml
|
388
388
|
- lib/appmap/gem_hooks/devise.yml
|
389
|
+
- lib/appmap/gem_hooks/jwt.yml
|
389
390
|
- lib/appmap/gem_hooks/pandoc-ruby.yml
|
390
391
|
- lib/appmap/gem_hooks/rails.yml
|
391
392
|
- lib/appmap/gem_hooks/railties.yml
|