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.
@@ -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([scoped_method((method :test_method))])
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 scoped_method(method)
19
- AppMap::Trace::ScopedMethod.new AppMap::Config::Package.new, method.receiver.class.name, method, false
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(&:defined_class)).to eq([ 'InstanceMethod' ])
254
- expect(tracer.event_methods.to_a.map(&:to_s)).to eq([ InstanceMethod.public_instance_method(:say_default).to_s ])
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.45.1
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-04 00:00:00.000000000 Z
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