appmap 0.45.1 → 0.46.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|