appmap 0.86.0 → 0.89.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/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
|