appmap 0.66.1 → 0.68.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 +2 -0
- data/CHANGELOG.md +45 -0
- data/lib/appmap/builtin_hooks/json.yml +4 -0
- data/lib/appmap/builtin_hooks/net/http.yml +3 -0
- data/lib/appmap/builtin_hooks/openssl.yml +16 -0
- data/lib/appmap/builtin_hooks/yaml.yml +10 -0
- data/lib/appmap/command/agent_setup/validate.rb +8 -1
- data/lib/appmap/command/inspect.rb +0 -1
- data/lib/appmap/config.rb +157 -137
- data/lib/appmap/depends/api.rb +1 -1
- data/lib/appmap/depends/configuration.rb +0 -2
- data/lib/appmap/depends/util.rb +1 -1
- data/lib/appmap/event.rb +6 -2
- data/lib/appmap/gem_hooks/actionpack.yml +40 -0
- data/lib/appmap/gem_hooks/actionview.yml +13 -0
- data/lib/appmap/gem_hooks/activesupport.yml +12 -0
- data/lib/appmap/gem_hooks/cancancan.yml +6 -0
- data/lib/appmap/handler/net_http.rb +14 -12
- data/lib/appmap/handler/rails/request_handler.rb +4 -10
- data/lib/appmap/hook.rb +46 -30
- data/lib/appmap/minitest.rb +1 -0
- data/lib/appmap/rspec.rb +1 -0
- data/lib/appmap/swagger/configuration.rb +2 -0
- data/lib/appmap/util.rb +20 -2
- data/lib/appmap/version.rb +1 -1
- data/lib/appmap.rb +2 -2
- data/spec/config_spec.rb +207 -65
- data/spec/depends/api_spec.rb +3 -3
- data/spec/fixtures/config/missing_path_or_gem.yml +3 -0
- data/spec/rails_recording_spec.rb +1 -1
- data/spec/rails_spec_helper.rb +6 -1
- data/test/agent_setup_validate_test.rb +35 -8
- data/test/fixtures/mocha_mock_app/Gemfile +5 -0
- data/test/fixtures/mocha_mock_app/appmap.yml +5 -0
- data/test/fixtures/mocha_mock_app/lib/sheep.rb +5 -0
- data/test/fixtures/mocha_mock_app/test/sheep_test.rb +18 -0
- data/test/mock_compatibility_test.rb +45 -0
- metadata +17 -3
@@ -0,0 +1,40 @@
|
|
1
|
+
- methods:
|
2
|
+
- ActionDispatch::Request::Session#[]
|
3
|
+
- ActionDispatch::Request::Session#dig
|
4
|
+
- ActionDispatch::Request::Session#values
|
5
|
+
- ActionDispatch::Request::Session#fetch
|
6
|
+
- ActionDispatch::Cookies::CookieJar#[]
|
7
|
+
- ActionDispatch::Cookies::CookieJar#fetch
|
8
|
+
label: http.session.read
|
9
|
+
require_name: action_dispatch
|
10
|
+
- methods:
|
11
|
+
- ActionDispatch::Request::Session#destroy
|
12
|
+
- ActionDispatch::Request::Session#[]=
|
13
|
+
- ActionDispatch::Request::Session#clear
|
14
|
+
- ActionDispatch::Request::Session#update
|
15
|
+
- ActionDispatch::Request::Session#delete
|
16
|
+
- ActionDispatch::Request::Session#merge
|
17
|
+
- ActionDispatch::Cookies::CookieJar#[]=
|
18
|
+
- ActionDispatch::Cookies::CookieJar#clear
|
19
|
+
- ActionDispatch::Cookies::CookieJar#update
|
20
|
+
- ActionDispatch::Cookies::CookieJar#delete
|
21
|
+
- ActionDispatch::Cookies::CookieJar#recycle!
|
22
|
+
label: http.session.write
|
23
|
+
require_name: action_dispatch
|
24
|
+
- methods:
|
25
|
+
- ActionDispatch::Cookies::EncryptedCookieJar#[]=
|
26
|
+
- ActionDispatch::Cookies::EncryptedCookieJar#clear
|
27
|
+
- ActionDispatch::Cookies::EncryptedCookieJar#update
|
28
|
+
- ActionDispatch::Cookies::EncryptedCookieJar#delete
|
29
|
+
- ActionDispatch::Cookies::EncryptedCookieJar#recycle
|
30
|
+
labels:
|
31
|
+
- http.cookie
|
32
|
+
- crypto.encrypt
|
33
|
+
require_name: action_dispatch
|
34
|
+
- methods:
|
35
|
+
- ActionController::Instrumentation#process_action
|
36
|
+
- ActionController::Instrumentation#send_file
|
37
|
+
- ActionController::Instrumentation#send_data
|
38
|
+
- ActionController::Instrumentation#redirect_to
|
39
|
+
label: mvc.controller
|
40
|
+
require_name: action_controller
|
@@ -0,0 +1,13 @@
|
|
1
|
+
- methods:
|
2
|
+
- ActionView::Renderer#render
|
3
|
+
- ActionView::TemplateRenderer#render
|
4
|
+
- ActionView::PartialRenderer#render
|
5
|
+
label: mvc.view
|
6
|
+
handler_class: AppMap::Handler::Rails::Template::RenderHandler
|
7
|
+
require_name: action_view
|
8
|
+
- methods:
|
9
|
+
- ActionView::Resolver#find_all
|
10
|
+
- ActionView::Resolver#find_all_anywhere
|
11
|
+
label: mvc.template.resolver
|
12
|
+
handler_class: AppMap::Handler::Rails::Template::ResolverHandler
|
13
|
+
require_name: action_view
|
@@ -0,0 +1,12 @@
|
|
1
|
+
- method: ActiveSupport::Callbacks::CallbackSequence#invoke_before
|
2
|
+
label: mvc.before_action
|
3
|
+
require_name: active_support
|
4
|
+
force: true
|
5
|
+
- method: ActiveSupport::Callbacks::CallbackSequence#invoke_after
|
6
|
+
label: mvc.after_action
|
7
|
+
require_name: active_support
|
8
|
+
force: true
|
9
|
+
- method: ActiveSupport::SecurityUtils#secure_compare
|
10
|
+
label: crypto.secure_compare
|
11
|
+
require_name: active_support/security_utils
|
12
|
+
force: true
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'appmap/event'
|
4
4
|
require 'appmap/util'
|
5
|
+
require 'rack'
|
5
6
|
|
6
7
|
module AppMap
|
7
8
|
module Handler
|
@@ -27,7 +28,7 @@ module AppMap
|
|
27
28
|
|
28
29
|
self.request_method = request.method
|
29
30
|
self.url = url
|
30
|
-
self.headers =
|
31
|
+
self.headers = NetHTTP.copy_headers(request)
|
31
32
|
self.params = Rack::Utils.parse_nested_query(query)
|
32
33
|
end
|
33
34
|
|
@@ -55,22 +56,25 @@ module AppMap
|
|
55
56
|
end
|
56
57
|
|
57
58
|
class HTTPClientResponse < AppMap::Event::MethodReturnIgnoreValue
|
58
|
-
attr_accessor :status, :
|
59
|
+
attr_accessor :status, :headers
|
59
60
|
|
60
61
|
def initialize(response, parent_id, elapsed)
|
61
62
|
super AppMap::Event.next_id_counter, :return, Thread.current.object_id
|
62
63
|
|
63
|
-
|
64
|
+
if response
|
65
|
+
self.status = response.code.to_i
|
66
|
+
self.headers = NetHTTP.copy_headers(response)
|
67
|
+
else
|
68
|
+
self.headers = {}
|
69
|
+
end
|
64
70
|
self.parent_id = parent_id
|
65
71
|
self.elapsed = elapsed
|
66
|
-
self.headers = AppMap::Util.select_headers(NetHTTP.response_headers(response))
|
67
72
|
end
|
68
73
|
|
69
74
|
def to_h
|
70
75
|
super.tap do |h|
|
71
76
|
h[:http_client_response] = {
|
72
77
|
status_code: status,
|
73
|
-
mime_type: mime_type,
|
74
78
|
headers: headers
|
75
79
|
}.compact
|
76
80
|
end
|
@@ -79,17 +83,15 @@ module AppMap
|
|
79
83
|
|
80
84
|
class NetHTTP
|
81
85
|
class << self
|
82
|
-
def
|
86
|
+
def copy_headers(obj)
|
83
87
|
{}.tap do |headers|
|
84
|
-
|
85
|
-
key =
|
86
|
-
headers[key] =
|
88
|
+
obj.each_header do |key, value|
|
89
|
+
key = key.split('-').map(&:capitalize).join('-')
|
90
|
+
headers[key] = value
|
87
91
|
end
|
88
92
|
end
|
89
93
|
end
|
90
|
-
|
91
|
-
alias response_headers request_headers
|
92
|
-
|
94
|
+
|
93
95
|
def handle_call(defined_class, hook_method, receiver, args)
|
94
96
|
# request will call itself again in a start block if it's not already started.
|
95
97
|
return unless receiver.started?
|
@@ -9,16 +9,14 @@ module AppMap
|
|
9
9
|
module Rails
|
10
10
|
module RequestHandler
|
11
11
|
class HTTPServerRequest < AppMap::Event::MethodEvent
|
12
|
-
attr_accessor :normalized_path_info, :request_method, :path_info, :params, :
|
12
|
+
attr_accessor :normalized_path_info, :request_method, :path_info, :params, :headers
|
13
13
|
|
14
14
|
def initialize(request)
|
15
15
|
super AppMap::Event.next_id_counter, :call, Thread.current.object_id
|
16
16
|
|
17
17
|
self.request_method = request.request_method
|
18
18
|
self.normalized_path_info = normalized_path(request)
|
19
|
-
self.
|
20
|
-
self.headers = AppMap::Util.select_headers(request.env)
|
21
|
-
self.authorization = request.headers['Authorization']
|
19
|
+
self.headers = AppMap::Util.select_rack_headers(request.env)
|
22
20
|
self.path_info = request.path_info.split('?')[0]
|
23
21
|
# ActionDispatch::Http::ParameterFilter is deprecated
|
24
22
|
parameter_filter_cls = \
|
@@ -35,9 +33,7 @@ module AppMap
|
|
35
33
|
h[:http_server_request] = {
|
36
34
|
request_method: request_method,
|
37
35
|
path_info: path_info,
|
38
|
-
mime_type: mime_type,
|
39
36
|
normalized_path_info: normalized_path_info,
|
40
|
-
authorization: authorization,
|
41
37
|
headers: headers,
|
42
38
|
}.compact
|
43
39
|
|
@@ -72,23 +68,21 @@ module AppMap
|
|
72
68
|
end
|
73
69
|
|
74
70
|
class HTTPServerResponse < AppMap::Event::MethodReturnIgnoreValue
|
75
|
-
attr_accessor :status, :
|
71
|
+
attr_accessor :status, :headers
|
76
72
|
|
77
73
|
def initialize(response, parent_id, elapsed)
|
78
74
|
super AppMap::Event.next_id_counter, :return, Thread.current.object_id
|
79
75
|
|
80
76
|
self.status = response.status
|
81
|
-
self.mime_type = response.headers['Content-Type']
|
82
77
|
self.parent_id = parent_id
|
83
78
|
self.elapsed = elapsed
|
84
|
-
self.headers =
|
79
|
+
self.headers = response.headers.dup
|
85
80
|
end
|
86
81
|
|
87
82
|
def to_h
|
88
83
|
super.tap do |h|
|
89
84
|
h[:http_server_response] = {
|
90
85
|
status_code: status,
|
91
|
-
mime_type: mime_type,
|
92
86
|
headers: headers
|
93
87
|
}.compact
|
94
88
|
end
|
data/lib/appmap/hook.rb
CHANGED
@@ -15,10 +15,23 @@ module AppMap
|
|
15
15
|
@method_arity = ::Method.instance_method(:arity)
|
16
16
|
|
17
17
|
class << self
|
18
|
-
def
|
19
|
-
|
18
|
+
def hook_builtins?
|
19
|
+
Mutex.new.synchronize do
|
20
|
+
@hook_builtins = true if @hook_builtins.nil?
|
20
21
|
|
21
|
-
|
22
|
+
return false unless @hook_builtins
|
23
|
+
|
24
|
+
@hook_builtins = false
|
25
|
+
true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def already_hooked?(method)
|
30
|
+
# After a method is defined, the statement "module_function <the-method>" can convert that method
|
31
|
+
# into a module (class) method. The method is hooked first when it's defined, then AppMap will attempt to
|
32
|
+
# hook it again when it's redefined as a module method. So we check the method source location - if it's
|
33
|
+
# part of the AppMap source tree, we ignore it.
|
34
|
+
method.source_location && method.source_location[0].index(__dir__) == 0
|
22
35
|
end
|
23
36
|
|
24
37
|
# Return the class, separator ('.' or '#'), and method name for
|
@@ -79,42 +92,43 @@ module AppMap
|
|
79
92
|
# hook_builtins builds hooks for code that is built in to the Ruby standard library.
|
80
93
|
# No TracePoint events are emitted for builtins, so a separate hooking mechanism is needed.
|
81
94
|
def hook_builtins
|
82
|
-
return unless self.class.
|
95
|
+
return unless self.class.hook_builtins?
|
83
96
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
end
|
97
|
+
hook_loaded_code = lambda do |hooks_by_class, builtin|
|
98
|
+
hooks_by_class.each do |class_name, hooks|
|
99
|
+
Array(hooks).each do |hook|
|
100
|
+
require hook.package.require_name if builtin && hook.package.require_name && hook.package.require_name != 'ruby'
|
89
101
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
method_name = method_name.to_sym
|
95
|
-
base_cls = class_from_string.(class_name)
|
102
|
+
Array(hook.method_names).each do |method_name|
|
103
|
+
method_name = method_name.to_sym
|
104
|
+
base_cls = Util::class_from_string(class_name, must: false)
|
105
|
+
next unless base_cls
|
96
106
|
|
97
|
-
|
98
|
-
|
99
|
-
|
107
|
+
hook_method = lambda do |entry|
|
108
|
+
cls, method = entry
|
109
|
+
return false if config.never_hook?(cls, method)
|
100
110
|
|
101
|
-
|
102
|
-
|
111
|
+
Hook::Method.new(hook.package, cls, method).activate
|
112
|
+
end
|
103
113
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
+
methods = []
|
115
|
+
methods << [ base_cls, base_cls.public_instance_method(method_name) ] rescue nil
|
116
|
+
if base_cls.respond_to?(:singleton_class)
|
117
|
+
methods << [ base_cls.singleton_class, base_cls.singleton_class.public_instance_method(method_name) ] rescue nil
|
118
|
+
end
|
119
|
+
methods.compact!
|
120
|
+
if methods.empty?
|
121
|
+
warn "Method #{method_name} not found on #{base_cls.name}" if LOG
|
122
|
+
else
|
123
|
+
methods.each(&hook_method)
|
124
|
+
end
|
114
125
|
end
|
115
126
|
end
|
116
127
|
end
|
117
128
|
end
|
129
|
+
|
130
|
+
hook_loaded_code.(config.builtin_hooks, true)
|
131
|
+
hook_loaded_code.(config.gem_hooks, false)
|
118
132
|
end
|
119
133
|
|
120
134
|
protected
|
@@ -165,6 +179,8 @@ module AppMap
|
|
165
179
|
next
|
166
180
|
end
|
167
181
|
|
182
|
+
next if self.class.already_hooked?(method)
|
183
|
+
|
168
184
|
warn "AppMap: Examining #{hook_cls} #{method.name}" if LOG
|
169
185
|
|
170
186
|
disasm = RubyVM::InstructionSequence.disasm(method)
|
data/lib/appmap/minitest.rb
CHANGED
data/lib/appmap/rspec.rb
CHANGED
data/lib/appmap/util.rb
CHANGED
@@ -21,6 +21,14 @@ module AppMap
|
|
21
21
|
WHITE = "\e[37m"
|
22
22
|
|
23
23
|
class << self
|
24
|
+
def class_from_string(fq_class, must: true)
|
25
|
+
fq_class.split('::').inject(Object) do |mod, class_name|
|
26
|
+
mod.const_get(class_name)
|
27
|
+
end
|
28
|
+
rescue NameError
|
29
|
+
raise if must
|
30
|
+
end
|
31
|
+
|
24
32
|
def parse_function_name(name)
|
25
33
|
package_tokens = name.split('/')
|
26
34
|
|
@@ -108,8 +116,18 @@ module AppMap
|
|
108
116
|
event
|
109
117
|
end
|
110
118
|
|
111
|
-
def
|
119
|
+
def select_rack_headers(env)
|
120
|
+
finalize_headers = lambda do |headers|
|
121
|
+
blank?(headers) ? nil : headers
|
122
|
+
end
|
123
|
+
|
112
124
|
# Rack prepends HTTP_ to all client-sent headers.
|
125
|
+
|
126
|
+
if !env['rack.version']
|
127
|
+
warn "Request headers does not contain rack.version. HTTP_ prefix is not expected."
|
128
|
+
return finalize_headers.call(env.dup)
|
129
|
+
end
|
130
|
+
|
113
131
|
matching_headers = env
|
114
132
|
.select { |k,v| k.start_with? 'HTTP_'}
|
115
133
|
.reject { |k,v| blank?(v) }
|
@@ -118,7 +136,7 @@ module AppMap
|
|
118
136
|
value = kv[1]
|
119
137
|
memo[key] = value
|
120
138
|
end
|
121
|
-
|
139
|
+
finalize_headers.call(matching_headers)
|
122
140
|
end
|
123
141
|
|
124
142
|
def normalize_path(path)
|
data/lib/appmap/version.rb
CHANGED
data/lib/appmap.rb
CHANGED
@@ -74,6 +74,6 @@ lambda do
|
|
74
74
|
require 'appmap/depends'
|
75
75
|
end
|
76
76
|
|
77
|
-
end.call
|
77
|
+
end.call unless ENV['APPMAP_AUTOREQUIRE'] == 'false'
|
78
78
|
|
79
|
-
AppMap.initialize_configuration if ENV['APPMAP'] == 'true'
|
79
|
+
AppMap.initialize_configuration if ENV['APPMAP'] == 'true' && ENV['APPMAP_INITIALIZE'] != 'false'
|