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
data/spec/class_map_spec.rb
CHANGED
@@ -5,7 +5,7 @@ require 'spec_helper'
|
|
5
5
|
describe 'AppMap::ClassMap' do
|
6
6
|
describe '.build_from_methods' do
|
7
7
|
it 'includes method comment' do
|
8
|
-
map = AppMap.class_map([
|
8
|
+
map = AppMap.class_map([ruby_method((method :test_method))])
|
9
9
|
function = dig_map(map, 5)[0]
|
10
10
|
expect(function).to include(:comment)
|
11
11
|
end
|
@@ -15,8 +15,8 @@ describe 'AppMap::ClassMap' do
|
|
15
15
|
'test method body'
|
16
16
|
end
|
17
17
|
|
18
|
-
def
|
19
|
-
AppMap::Trace::
|
18
|
+
def ruby_method(method)
|
19
|
+
AppMap::Trace::RubyMethod.new AppMap::Config::Package.new, method.receiver.class.name, method, false
|
20
20
|
end
|
21
21
|
|
22
22
|
def dig_map(map, depth)
|
data/spec/hook_spec.rb
CHANGED
@@ -250,8 +250,8 @@ describe 'AppMap class Hooking', docker: false do
|
|
250
250
|
_, tracer = invoke_test_file 'spec/fixtures/hook/instance_method.rb' do
|
251
251
|
InstanceMethod.new.say_default
|
252
252
|
end
|
253
|
-
expect(tracer.event_methods.to_a.map(&:
|
254
|
-
expect(tracer.event_methods.to_a.map(&:
|
253
|
+
expect(tracer.event_methods.to_a.map(&:class_name)).to eq([ 'InstanceMethod' ])
|
254
|
+
expect(tracer.event_methods.to_a.map(&:name)).to eq([ InstanceMethod.public_instance_method(:say_default).name ])
|
255
255
|
end
|
256
256
|
|
257
257
|
it 'builds a class map of invoked methods' do
|
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.46.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin Gilpin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-05-
|
11
|
+
date: 2021-05-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -348,14 +348,15 @@ files:
|
|
348
348
|
- lib/appmap/event.rb
|
349
349
|
- lib/appmap/handler/function.rb
|
350
350
|
- lib/appmap/handler/net_http.rb
|
351
|
+
- lib/appmap/handler/rails/request_handler.rb
|
352
|
+
- lib/appmap/handler/rails/sql_handler.rb
|
353
|
+
- lib/appmap/handler/rails/template.rb
|
351
354
|
- lib/appmap/hook.rb
|
352
355
|
- lib/appmap/hook/method.rb
|
353
356
|
- lib/appmap/metadata.rb
|
354
357
|
- lib/appmap/middleware/remote_recording.rb
|
355
358
|
- lib/appmap/minitest.rb
|
356
359
|
- lib/appmap/open.rb
|
357
|
-
- lib/appmap/rails/request_handler.rb
|
358
|
-
- lib/appmap/rails/sql_handler.rb
|
359
360
|
- lib/appmap/railtie.rb
|
360
361
|
- lib/appmap/record.rb
|
361
362
|
- lib/appmap/rspec.rb
|
@@ -1,122 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'appmap/event'
|
4
|
-
require 'appmap/hook'
|
5
|
-
|
6
|
-
module AppMap
|
7
|
-
module Rails
|
8
|
-
module RequestHandler
|
9
|
-
class HTTPServerRequest < AppMap::Event::MethodEvent
|
10
|
-
attr_accessor :normalized_path_info, :request_method, :path_info, :params, :mime_type, :headers, :authorization
|
11
|
-
|
12
|
-
def initialize(request)
|
13
|
-
super AppMap::Event.next_id_counter, :call, Thread.current.object_id
|
14
|
-
|
15
|
-
self.request_method = request.request_method
|
16
|
-
self.normalized_path_info = normalized_path(request)
|
17
|
-
self.mime_type = request.headers['Content-Type']
|
18
|
-
self.headers = AppMap::Util.select_headers(request.env)
|
19
|
-
self.authorization = request.headers['Authorization']
|
20
|
-
self.path_info = request.path_info.split('?')[0]
|
21
|
-
# ActionDispatch::Http::ParameterFilter is deprecated
|
22
|
-
parameter_filter_cls = \
|
23
|
-
if defined?(ActiveSupport::ParameterFilter)
|
24
|
-
ActiveSupport::ParameterFilter
|
25
|
-
else
|
26
|
-
ActionDispatch::Http::ParameterFilter
|
27
|
-
end
|
28
|
-
self.params = parameter_filter_cls.new(::Rails.application.config.filter_parameters).filter(request.params)
|
29
|
-
end
|
30
|
-
|
31
|
-
def to_h
|
32
|
-
super.tap do |h|
|
33
|
-
h[:http_server_request] = {
|
34
|
-
request_method: request_method,
|
35
|
-
path_info: path_info,
|
36
|
-
mime_type: mime_type,
|
37
|
-
normalized_path_info: normalized_path_info,
|
38
|
-
authorization: authorization,
|
39
|
-
headers: headers,
|
40
|
-
}.compact
|
41
|
-
|
42
|
-
unless params.blank?
|
43
|
-
h[:message] = params.keys.map do |key|
|
44
|
-
val = params[key]
|
45
|
-
{
|
46
|
-
name: key,
|
47
|
-
class: val.class.name,
|
48
|
-
value: self.class.display_string(val),
|
49
|
-
object_id: val.__id__,
|
50
|
-
}.tap do |message|
|
51
|
-
properties = object_properties(val)
|
52
|
-
message[:properties] = properties if properties
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
private
|
60
|
-
|
61
|
-
def normalized_path(request, router = ::Rails.application.routes.router)
|
62
|
-
router.recognize request do |route, _|
|
63
|
-
app = route.app
|
64
|
-
next unless app.matches? request
|
65
|
-
return normalized_path request, app.rack_app.routes.router if app.engine?
|
66
|
-
|
67
|
-
return route.path.spec.to_s
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
class HTTPServerResponse < AppMap::Event::MethodReturnIgnoreValue
|
73
|
-
attr_accessor :status, :mime_type, :headers
|
74
|
-
|
75
|
-
def initialize(response, parent_id, elapsed)
|
76
|
-
super AppMap::Event.next_id_counter, :return, Thread.current.object_id
|
77
|
-
|
78
|
-
self.status = response.status
|
79
|
-
self.mime_type = response.headers['Content-Type']
|
80
|
-
self.parent_id = parent_id
|
81
|
-
self.elapsed = elapsed
|
82
|
-
self.headers = AppMap::Util.select_headers(response.headers)
|
83
|
-
end
|
84
|
-
|
85
|
-
def to_h
|
86
|
-
super.tap do |h|
|
87
|
-
h[:http_server_response] = {
|
88
|
-
status_code: status,
|
89
|
-
mime_type: mime_type,
|
90
|
-
headers: headers
|
91
|
-
}.compact
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
class HookMethod < AppMap::Hook::Method
|
97
|
-
def initialize
|
98
|
-
# ActionController::Instrumentation has issued start_processing.action_controller and
|
99
|
-
# process_action.action_controller since Rails 3. Therefore it's a stable place to hook
|
100
|
-
# the request. Rails controller notifications can't be used directly because they don't
|
101
|
-
# provide response headers, and we want the Content-Type.
|
102
|
-
super(nil, ActionController::Instrumentation, ActionController::Instrumentation.instance_method(:process_action))
|
103
|
-
end
|
104
|
-
|
105
|
-
protected
|
106
|
-
|
107
|
-
def before_hook(receiver, defined_class, _) # args
|
108
|
-
call_event = HTTPServerRequest.new(receiver.request)
|
109
|
-
# http_server_request events are i/o and do not require a package name.
|
110
|
-
AppMap.tracing.record_event call_event, defined_class: defined_class, method: hook_method
|
111
|
-
[ call_event, TIME_NOW.call ]
|
112
|
-
end
|
113
|
-
|
114
|
-
def after_hook(receiver, call_event, start_time, _, _) # return_value, exception
|
115
|
-
elapsed = TIME_NOW.call - start_time
|
116
|
-
return_event = HTTPServerResponse.new receiver.response, call_event.id, elapsed
|
117
|
-
AppMap.tracing.record_event return_event
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
@@ -1,150 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'appmap/event'
|
4
|
-
|
5
|
-
module AppMap
|
6
|
-
module Rails
|
7
|
-
class SQLHandler
|
8
|
-
class SQLCall < AppMap::Event::MethodCall
|
9
|
-
attr_accessor :payload
|
10
|
-
|
11
|
-
def initialize(payload)
|
12
|
-
super AppMap::Event.next_id_counter, :call, Thread.current.object_id
|
13
|
-
|
14
|
-
self.payload = payload
|
15
|
-
end
|
16
|
-
|
17
|
-
def to_h
|
18
|
-
super.tap do |h|
|
19
|
-
h[:sql_query] = {
|
20
|
-
sql: payload[:sql],
|
21
|
-
database_type: payload[:database_type]
|
22
|
-
}.tap do |sql_query|
|
23
|
-
%i[server_version].each do |attribute|
|
24
|
-
sql_query[attribute] = payload[attribute] if payload[attribute]
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
class SQLReturn < AppMap::Event::MethodReturnIgnoreValue
|
32
|
-
def initialize(parent_id, elapsed)
|
33
|
-
super AppMap::Event.next_id_counter, :return, Thread.current.object_id
|
34
|
-
|
35
|
-
self.parent_id = parent_id
|
36
|
-
self.elapsed = elapsed
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
module SQLExaminer
|
41
|
-
class << self
|
42
|
-
def examine(payload, sql:)
|
43
|
-
return unless (examiner = build_examiner)
|
44
|
-
|
45
|
-
payload[:server_version] = examiner.server_version
|
46
|
-
payload[:database_type] = examiner.database_type.to_s
|
47
|
-
end
|
48
|
-
|
49
|
-
protected
|
50
|
-
|
51
|
-
def build_examiner
|
52
|
-
if defined?(Sequel)
|
53
|
-
SequelExaminer.new
|
54
|
-
elsif defined?(ActiveRecord)
|
55
|
-
ActiveRecordExaminer.new
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
class SequelExaminer
|
61
|
-
def server_version
|
62
|
-
Sequel::Model.db.server_version
|
63
|
-
end
|
64
|
-
|
65
|
-
def database_type
|
66
|
-
Sequel::Model.db.database_type.to_sym
|
67
|
-
end
|
68
|
-
|
69
|
-
def execute_query(sql)
|
70
|
-
Sequel::Model.db[sql].all
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
class ActiveRecordExaminer
|
75
|
-
@@db_version_warning_issued = {}
|
76
|
-
|
77
|
-
def issue_warning
|
78
|
-
db_type = database_type
|
79
|
-
return if @@db_version_warning_issued[db_type]
|
80
|
-
warn("AppMap: Unable to determine database version for #{db_type.inspect}")
|
81
|
-
@@db_version_warning_issued[db_type] = true
|
82
|
-
end
|
83
|
-
|
84
|
-
def server_version
|
85
|
-
ActiveRecord::Base.connection.try(:database_version) || issue_warning
|
86
|
-
end
|
87
|
-
|
88
|
-
def database_type
|
89
|
-
type = ActiveRecord::Base.connection.adapter_name.downcase.to_sym
|
90
|
-
type = :postgres if type == :postgresql
|
91
|
-
|
92
|
-
type
|
93
|
-
end
|
94
|
-
|
95
|
-
def execute_query(sql)
|
96
|
-
ActiveRecord::Base.connection.execute(sql).inject([]) { |memo, r| memo << r; memo }
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
def call(_, started, finished, _, payload) # (name, started, finished, unique_id, payload)
|
102
|
-
return if AppMap.tracing.empty?
|
103
|
-
|
104
|
-
reentry_key = "#{self.class.name}#call"
|
105
|
-
return if Thread.current[reentry_key] == true
|
106
|
-
|
107
|
-
Thread.current[reentry_key] = true
|
108
|
-
begin
|
109
|
-
sql = payload[:sql].strip
|
110
|
-
|
111
|
-
# Detect whether a function call within a specified filename is present in the call stack.
|
112
|
-
find_in_backtrace = lambda do |file_name, function_name = nil|
|
113
|
-
Thread.current.backtrace.find do |line|
|
114
|
-
tokens = line.split(':')
|
115
|
-
matches_file = tokens.find { |t| t.rindex(file_name) == (t.length - file_name.length) }
|
116
|
-
matches_function = function_name.nil? || tokens.find { |t| t == "in `#{function_name}'" }
|
117
|
-
matches_file && matches_function
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
# Ignore SQL calls which are made while establishing a new connection.
|
122
|
-
#
|
123
|
-
# Example:
|
124
|
-
# /path/to/ruby/2.6.0/gems/sequel-5.20.0/lib/sequel/connection_pool.rb:122:in `make_new'
|
125
|
-
return if find_in_backtrace.call('lib/sequel/connection_pool.rb', 'make_new')
|
126
|
-
# lib/active_record/connection_adapters/abstract/connection_pool.rb:811:in `new_connection'
|
127
|
-
return if find_in_backtrace.call('lib/active_record/connection_adapters/abstract/connection_pool.rb', 'new_connection')
|
128
|
-
|
129
|
-
# Ignore SQL calls which are made while inspecting the DB schema.
|
130
|
-
#
|
131
|
-
# Example:
|
132
|
-
# /path/to/ruby/2.6.0/gems/sequel-5.20.0/lib/sequel/model/base.rb:812:in `get_db_schema'
|
133
|
-
return if find_in_backtrace.call('lib/sequel/model/base.rb', 'get_db_schema')
|
134
|
-
# /usr/local/bundle/gems/activerecord-5.2.3/lib/active_record/model_schema.rb:466:in `load_schema!'
|
135
|
-
return if find_in_backtrace.call('lib/active_record/model_schema.rb', 'load_schema!')
|
136
|
-
return if find_in_backtrace.call('lib/active_model/attribute_methods.rb', 'define_attribute_methods')
|
137
|
-
return if find_in_backtrace.call('lib/active_record/connection_adapters/schema_cache.rb')
|
138
|
-
|
139
|
-
SQLExaminer.examine payload, sql: sql
|
140
|
-
|
141
|
-
call = SQLCall.new(payload)
|
142
|
-
AppMap.tracing.record_event(call)
|
143
|
-
AppMap.tracing.record_event(SQLReturn.new(call.id, finished - started))
|
144
|
-
ensure
|
145
|
-
Thread.current[reentry_key] = nil
|
146
|
-
end
|
147
|
-
end
|
148
|
-
end
|
149
|
-
end
|
150
|
-
end
|