appmap 0.45.1 → 0.46.0

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