appmap 0.45.1 → 0.48.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b91b79723565f45d9d59ce341a9944a3a4e1c853bc77c1b176dc7b26ede9df22
4
- data.tar.gz: f9be88ae7e83b801f66ada4087a06923ad9fa4d10e498f1c8b07fe0e301552d7
3
+ metadata.gz: 698da91c88a89867584e3d78e10974a4a2f8d726636d60366c214be9e3ddbf97
4
+ data.tar.gz: eee0b62c442eecc5cf978960128c0b19ff2ebdc393770d8cefc820c82a451d4f
5
5
  SHA512:
6
- metadata.gz: e7eda447c67a44ad10226f5b6e9eb51c82995a4b2df970af27b0e2b5304101d419a6b3843ef6f0a983a3f952da2e3ed55f0de327b2856fcf970961c410c19b79
7
- data.tar.gz: 839df6608d503e74f663d42673d9cf91866592a1715cee1c828c3de23ae472fdff6025ea9ee767a33e384b2ed51f46a999001d354f3e763deba46a5253eb5661
6
+ metadata.gz: 77ae4b055912a7bd6f62f32bb2db7d3684d614453fb82bb7d8f53496181919609ca2af0a65906ca36f1f28aaf28e06c64d6f5e6326b8f384f4c1206c22af559a
7
+ data.tar.gz: a72831c1908beb0130b4797ea5455b976677caec810edcdf0009b9be609ba6510a59873b66ca4df5406fe8f3e5aea995d8dabc983573517adc67fdf86cad822a
data/.dockerignore CHANGED
@@ -2,4 +2,3 @@ vendor
2
2
  node_modules
3
3
  spec/fixtures/rails*_users_app
4
4
  spec/fixtures/rack_users_app
5
-
data/.travis.yml CHANGED
@@ -17,6 +17,16 @@ before_script:
17
17
  cache:
18
18
  bundler: true
19
19
 
20
+ before_install:
21
+ # see https://blog.travis-ci.com/docker-rate-limits
22
+ # and also https://www.docker.com/blog/what-you-need-to-know-about-upcoming-docker-hub-rate-limiting/
23
+ # if we do not use authorized account,
24
+ # the pulls-per-IP quota is shared with other Travis users
25
+ - >
26
+ if [ ! -z "$DOCKERHUB_PASSWORD" ] && [ ! -z "$DOCKERHUB_USERNAME" ]; then
27
+ echo "$DOCKERHUB_PASSWORD" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin ;
28
+ fi
29
+
20
30
 
21
31
  # GEM_ALTERNATIVE_NAME only needed for deployment
22
32
  jobs:
data/CHANGELOG.md CHANGED
@@ -1,3 +1,40 @@
1
+ ## [0.48.1](https://github.com/applandinc/appmap-ruby/compare/v0.48.0...v0.48.1) (2021-05-25)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Account for bundle path when normalizing source path ([095c278](https://github.com/applandinc/appmap-ruby/commit/095c27818fc8ae8dfa39b30516d37c6dfd642d9c))
7
+ * Scan exception messages for non-UTF8 characters ([3dcaeae](https://github.com/applandinc/appmap-ruby/commit/3dcaeae44da5e40e432eda41caf5b9ebff5bea12))
8
+
9
+ # [0.48.0](https://github.com/applandinc/appmap-ruby/compare/v0.47.1...v0.48.0) (2021-05-19)
10
+
11
+
12
+ ### Features
13
+
14
+ * Hook the code only when APPMAP=true ([dd9e383](https://github.com/applandinc/appmap-ruby/commit/dd9e383024d1d9205a617d46bd64b90820035533))
15
+ * Remove server process recording from doc and tests ([383ba0a](https://github.com/applandinc/appmap-ruby/commit/383ba0ad444922a0a85409477d11bc7ed06a9160))
16
+
17
+ ## [0.47.1](https://github.com/applandinc/appmap-ruby/compare/v0.47.0...v0.47.1) (2021-05-13)
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * Add the proper template function hooks for Rails 6.0.7 ([175f489](https://github.com/applandinc/appmap-ruby/commit/175f489acbaed77ad52a18d805e4b6eeae1abfdb))
23
+
24
+ # [0.47.0](https://github.com/applandinc/appmap-ruby/compare/v0.46.0...v0.47.0) (2021-05-13)
25
+
26
+
27
+ ### Features
28
+
29
+ * Emit swagger-style normalized paths instead of Rails-style ones ([5a93cd7](https://github.com/applandinc/appmap-ruby/commit/5a93cd7096ca195146a84a6733c7d502dbcd0272))
30
+
31
+ # [0.46.0](https://github.com/applandinc/appmap-ruby/compare/v0.45.1...v0.46.0) (2021-05-12)
32
+
33
+
34
+ ### Features
35
+
36
+ * Record view template rendering events and template paths ([973b258](https://github.com/applandinc/appmap-ruby/commit/973b2581b6e2d4e15a1b93331e4e95a88678faae))
37
+
1
38
  ## [0.45.1](https://github.com/applandinc/appmap-ruby/compare/v0.45.0...v0.45.1) (2021-05-04)
2
39
 
3
40
 
data/README.md CHANGED
@@ -9,7 +9,6 @@
9
9
  - [Minitest](#minitest)
10
10
  - [Cucumber](#cucumber)
11
11
  - [Remote recording](#remote-recording)
12
- - [Server process recording](#server-process-recording)
13
12
  - [AppMap for VSCode](#appmap-for-vscode)
14
13
  - [AppMap Swagger](#appmap-swagger)
15
14
  - [Uploading AppMaps](#uploading-appmaps)
@@ -84,22 +83,6 @@ If you are using Ruby on Rails, require the railtie after Rails is loaded.
84
83
  require 'appmap/railtie' if defined?(AppMap).
85
84
  ```
86
85
 
87
- **application.rb**
88
-
89
- Add this line to *application.rb*, to enable server recording with `APPMAP_RECORD=true`:
90
-
91
- ```ruby
92
- module MyApp
93
- class Application < Rails::Application
94
- ...
95
-
96
- config.appmap.enabled = true if ENV['APPMAP_RECORD']
97
-
98
- ...
99
- end
100
- end
101
- ```
102
-
103
86
  # Configuration
104
87
 
105
88
  When you run your program, the `appmap` gem reads configuration settings from `appmap.yml`. Here's a sample configuration
@@ -326,25 +309,25 @@ if defined?(AppMap)
326
309
  end
327
310
  ```
328
311
 
329
- 2. Download and unpack the [AppLand browser extension](https://github.com/applandinc/appland-browser-extension). Install into Chrome using `chrome://extensions/`. Turn on "Developer Mode" and then load the extension using the "Load unpacked" button.
312
+ 2. (optional) Download and unpack the [AppLand browser extension](https://github.com/applandinc/appland-browser-extension). Install into Chrome using `chrome://extensions/`. Turn on "Developer Mode" and then load the extension using the "Load unpacked" button.
330
313
 
331
- 3. Start your Rails application server. For example:
314
+ 3. Start your Rails application server, with `APPMAP_RECORD=true`. For example:
332
315
 
333
316
  ```sh-session
334
- $ bundle exec rails server
317
+ $ APPMAP_RECORD=true bundle exec rails server
335
318
  ```
336
319
 
337
- 4. Open the AppLand browser extension and push `Start`.
320
+ 4. Start the recording
338
321
 
339
- 5. Use your app. For example, perform a login flow, or run through a manual UI test.
322
+ Option 1: Open the AppLand browser extension and push `Start`.
323
+ Option 2: `curl -XPOST localhost:3000/_appmap/record` (be sure and get the port number right)
340
324
 
341
- 6. Open the AppLand browser extension and push `Stop`. The recording will be transferred to the AppLand website and opened in your browser.
342
-
343
- ## Server process recording
325
+ 5. Use your app. For example, perform a login flow, or run through a manual UI test.
344
326
 
345
- Run your Rails server with `APPMAP_RECORD=true`. When the server exits, an *appmap.json* file will be written to the project directory. This is a great way to start the server, interact with your app as a user (or through it's API), and then view an AppMap of everything that happened.
327
+ 6. Finish the recording.
346
328
 
347
- Be sure and set `WEB_CONCURRENCY=1`, if you are using a webserver that can run multiple processes. You only want there to be one process while you are recording, otherwise they will both try and write *appmap.json* and one of them will clobber the other.
329
+ Option 1: Open the AppLand browser extension and push `Stop`. The recording will be transferred to the AppLand website and opened in your browser.
330
+ Option 2: `curl -XDELETE localhost:3000/_appmap/record > recording.appmap.json` - Saves the recording as a local file.
348
331
 
349
332
 
350
333
  # AppMap for VSCode
data/lib/appmap.rb CHANGED
@@ -9,7 +9,6 @@ end
9
9
 
10
10
  require 'appmap/version'
11
11
  require 'appmap/hook'
12
- require 'appmap/handler/net_http'
13
12
  require 'appmap/config'
14
13
  require 'appmap/trace'
15
14
  require 'appmap/class_map'
@@ -99,4 +98,4 @@ module AppMap
99
98
  end
100
99
 
101
100
  require 'appmap/railtie' if defined?(::Rails::Railtie)
102
- AppMap.initialize unless ENV['APPMAP_INITIALIZE'] == 'false'
101
+ AppMap.initialize if ENV['APPMAP'] == 'true'
@@ -82,16 +82,13 @@ module AppMap
82
82
  protected
83
83
 
84
84
  def add_function(root, method)
85
- package = method.package
86
- static = method.static
87
-
88
85
  object_infos = [
89
86
  {
90
- name: package.name,
87
+ name: method.package,
91
88
  type: 'package'
92
89
  }
93
90
  ]
94
- object_infos += method.defined_class.split('::').map do |name|
91
+ object_infos += method.class_name.split('::').map do |name|
95
92
  {
96
93
  name: name,
97
94
  type: 'class'
@@ -100,7 +97,7 @@ module AppMap
100
97
  function_info = {
101
98
  name: method.name,
102
99
  type: 'function',
103
- static: static
100
+ static: method.static
104
101
  }
105
102
  location = method.source_location
106
103
 
@@ -108,20 +105,15 @@ module AppMap
108
105
  if location
109
106
  location_file, lineno = location
110
107
  location_file = location_file[Dir.pwd.length + 1..-1] if location_file.index(Dir.pwd) == 0
111
- [ location_file, lineno ].join(':')
108
+ [ location_file, lineno ].compact.join(':')
112
109
  else
113
- [ method.defined_class, static ? '.' : '#', method.name ].join
110
+ [ method.class_name, method.static ? '.' : '#', method.name ].join
114
111
  end
115
112
 
116
- comment = begin
117
- method.comment
118
- rescue MethodSource::SourceNotFoundError
119
- nil
120
- end
121
-
113
+ comment = method.comment
122
114
  function_info[:comment] = comment unless comment.blank?
123
115
 
124
- function_info[:labels] = parse_labels(comment) + (package.labels || [])
116
+ function_info[:labels] = parse_labels(comment) + (method.labels || [])
125
117
  object_infos << function_info
126
118
 
127
119
  parent = root
data/lib/appmap/config.rb CHANGED
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'appmap/handler/net_http'
4
+ require 'appmap/handler/rails/template'
5
+
3
6
  module AppMap
4
7
  class Config
5
8
  # Specifies a code +path+ to be mapped.
@@ -82,18 +85,7 @@ module AppMap
82
85
  end
83
86
  end
84
87
 
85
- Function = Struct.new(:package, :cls, :labels, :function_names) do # :nodoc:
86
- def to_h
87
- {
88
- package: package,
89
- class: cls,
90
- labels: labels,
91
- functions: function_names.map(&:to_sym)
92
- }.compact
93
- end
94
- end
95
- private_constant :Function
96
-
88
+ # Identifies specific methods within a package which should be hooked.
97
89
  class TargetMethods # :nodoc:
98
90
  attr_reader :method_names, :package
99
91
 
@@ -115,24 +107,91 @@ module AppMap
115
107
  end
116
108
  private_constant :TargetMethods
117
109
 
118
- OPENSSL_PACKAGES = ->(labels) { Package.build_from_path('openssl', package_name: 'openssl', labels: labels) }
110
+ # Function represents a specific function configured for hooking by the +functions+
111
+ # entry in appmap.yml. When the Config is initialized, each Function is converted into
112
+ # a Package and TargetMethods. It's called a Function rather than a Method, because Function
113
+ # is the AppMap terminology.
114
+ Function = Struct.new(:package, :cls, :labels, :function_names) do # :nodoc:
115
+ def to_h
116
+ {
117
+ package: package,
118
+ class: cls,
119
+ labels: labels,
120
+ functions: function_names.map(&:to_sym)
121
+ }.compact
122
+ end
123
+ end
124
+ private_constant :Function
119
125
 
120
- # Methods that should always be hooked, with their containing
121
- # package and labels that should be applied to them.
122
- HOOKED_METHODS = {
123
- 'ActionView::Renderer' => TargetMethods.new(:render, Package.build_from_gem('actionview', package_name: 'action_view', labels: %w[mvc.view], optional: true)),
124
- 'ActionDispatch::Request::Session' => TargetMethods.new(%i[destroy [] dig values []= clear update delete fetch merge], Package.build_from_gem('actionpack', package_name: 'action_dispatch', labels: %w[http.session], optional: true)),
125
- 'ActionDispatch::Cookies::CookieJar' => TargetMethods.new(%i[[]= clear update delete recycle], Package.build_from_gem('actionpack', package_name: 'action_dispatch', labels: %w[http.cookie], optional: true)),
126
- 'ActionDispatch::Cookies::EncryptedCookieJar' => TargetMethods.new(%i[[]=], Package.build_from_gem('actionpack', package_name: 'action_dispatch', labels: %w[http.cookie crypto.encrypt], optional: true)),
127
- 'CanCan::ControllerAdditions' => TargetMethods.new(%i[authorize! can? cannot?], Package.build_from_gem('cancancan', labels: %w[security.authorization], optional: true)),
128
- 'CanCan::Ability' => TargetMethods.new(%i[authorize!], Package.build_from_gem('cancancan', labels: %w[security.authorization], optional: true)),
129
- 'ActionController::Instrumentation' => [
130
- TargetMethods.new(%i[process_action send_file send_data redirect_to], Package.build_from_gem('actionpack', package_name: 'action_controller', labels: %w[mvc.controller], optional: true)),
131
- TargetMethods.new(%i[render], Package.build_from_gem('actionpack', package_name: 'action_controller', labels: %w[mvc.view], optional: true)),
132
- ]
133
- }.freeze
126
+ ClassTargetMethods = Struct.new(:cls, :target_methods) # :nodoc:
127
+ private_constant :ClassTargetMethods
128
+
129
+ MethodHook = Struct.new(:cls, :method_names, :labels) # :nodoc:
130
+ private_constant :MethodHook
131
+
132
+ class << self
133
+ def package_hooks(gem_name, methods, handler_class: nil, package_name: nil)
134
+ Array(methods).map do |method|
135
+ package = Package.build_from_gem(gem_name, package_name: package_name, labels: method.labels, shallow: false, optional: true)
136
+ next unless package
137
+
138
+ package.handler_class = handler_class if handler_class
139
+ ClassTargetMethods.new(method.cls, TargetMethods.new(Array(method.method_names), package))
140
+ end.compact
141
+ end
142
+
143
+ def method_hook(cls, method_names, labels)
144
+ MethodHook.new(cls, method_names, labels)
145
+ end
146
+ end
147
+
148
+ # Hook well-known functions. When a function configured here is available in the bundle, it will be hooked with the
149
+ # predefined labels specified here. If any of these hooks are not desired, they can be disabled in the +exclude+ section
150
+ # of appmap.yml.
151
+ METHOD_HOOKS = [
152
+ package_hooks('actionview',
153
+ [
154
+ method_hook('ActionView::Renderer', :render, %w[mvc.view]),
155
+ method_hook('ActionView::TemplateRenderer', :render, %w[mvc.view]),
156
+ method_hook('ActionView::PartialRenderer', :render, %w[mvc.view])
157
+ ],
158
+ handler_class: AppMap::Handler::Rails::Template::RenderHandler,
159
+ package_name: 'action_view'
160
+ ),
161
+ package_hooks('actionview',
162
+ [
163
+ method_hook('ActionView::Resolver', %i[find_all find_all_anywhere], %w[mvc.template.resolver])
164
+ ],
165
+ handler_class: AppMap::Handler::Rails::Template::ResolverHandler,
166
+ package_name: 'action_view'
167
+ ),
168
+ package_hooks('actionpack',
169
+ [
170
+ method_hook('ActionDispatch::Request::Session', %i[destroy [] dig values []= clear update delete fetch merge], %w[http.session]),
171
+ method_hook('ActionDispatch::Cookies::CookieJar', %i[[]= clear update delete recycle], %w[http.session]),
172
+ method_hook('ActionDispatch::Cookies::EncryptedCookieJar', %i[[]= clear update delete recycle], %w[http.cookie crypto.encrypt])
173
+ ],
174
+ package_name: 'action_dispatch'
175
+ ),
176
+ package_hooks('cancancan',
177
+ [
178
+ method_hook('CanCan::ControllerAdditions', %i[authorize! can? cannot?], %w[security.authorization]),
179
+ method_hook('CanCan::Ability', %i[authorize?], %w[security.authorization])
180
+ ]
181
+ ),
182
+ package_hooks('actionpack',
183
+ [
184
+ method_hook('ActionController::Instrumentation', %i[process_action send_file send_data redirect_to], %w[mvc.controller])
185
+ ],
186
+ package_name: 'action_controller'
187
+ )
188
+ ].flatten.freeze
134
189
 
135
- BUILTIN_METHODS = {
190
+ OPENSSL_PACKAGES = ->(labels) { Package.build_from_path('openssl', package_name: 'openssl', labels: labels) }
191
+
192
+ # Hook functions which are builtin to Ruby. Because they are builtins, they may be loaded before appmap.
193
+ # Therefore, we can't rely on TracePoint to report the loading of this code.
194
+ BUILTIN_HOOKS = {
136
195
  'OpenSSL::PKey::PKey' => TargetMethods.new(:sign, OPENSSL_PACKAGES.(%w[crypto.pkey])),
137
196
  'OpenSSL::X509::Request' => TargetMethods.new(%i[sign verify], OPENSSL_PACKAGES.(%w[crypto.x509])),
138
197
  'OpenSSL::PKCS5' => TargetMethods.new(%i[pbkdf2_hmac_sha1 pbkdf2_hmac], OPENSSL_PACKAGES.(%w[crypto.pkcs5])),
@@ -159,26 +218,29 @@ module AppMap
159
218
  'JSON::Ext::Generator::State' => TargetMethods.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[format.json])),
160
219
  }.freeze
161
220
 
162
- attr_reader :name, :packages, :exclude, :hooked_methods, :builtin_methods
221
+ attr_reader :name, :packages, :exclude, :hooked_methods, :builtin_hooks
163
222
 
164
223
  def initialize(name, packages, exclude: [], functions: [])
165
224
  @name = name
166
225
  @packages = packages
167
- @hook_paths = packages.map(&:path)
226
+ @hook_paths = Set.new(packages.map(&:path))
168
227
  @exclude = exclude
169
- @builtin_methods = BUILTIN_METHODS
228
+ @builtin_hooks = BUILTIN_HOOKS
170
229
  @functions = functions
171
- @hooked_methods = HOOKED_METHODS.dup
230
+
231
+ @hooked_methods = METHOD_HOOKS.each_with_object(Hash.new { |h,k| h[k] = [] }) do |cls_target_methods, hooked_methods|
232
+ hooked_methods[cls_target_methods.cls] << cls_target_methods.target_methods
233
+ end
234
+
172
235
  functions.each do |func|
173
236
  package_options = {}
174
237
  package_options[:labels] = func.labels if func.labels
175
- @hooked_methods[func.cls] ||= []
176
238
  @hooked_methods[func.cls] << TargetMethods.new(func.function_names, Package.build_from_path(func.package, package_options))
177
239
  end
178
240
 
179
241
  @hooked_methods.each_value do |hooks|
180
242
  Array(hooks).each do |hook|
181
- @hook_paths << hook.package.path if hook.package
243
+ @hook_paths << hook.package.path
182
244
  end
183
245
  end
184
246
  end
data/lib/appmap/event.rb CHANGED
@@ -21,10 +21,10 @@ module AppMap
21
21
  LIMIT = 100
22
22
 
23
23
  class << self
24
- def build_from_invocation(me, event_type)
25
- me.id = AppMap::Event.next_id_counter
26
- me.event = event_type
27
- me.thread_id = Thread.current.object_id
24
+ def build_from_invocation(event_type, event:)
25
+ event.id = AppMap::Event.next_id_counter
26
+ event.event = event_type
27
+ event.thread_id = Thread.current.object_id
28
28
  end
29
29
 
30
30
  # Gets a display string for a value. This is not meant to be a machine deserializable value.
@@ -48,8 +48,6 @@ module AppMap
48
48
  nil
49
49
  end
50
50
 
51
- protected
52
-
53
51
  # Heuristic for dynamically defined class whose name can be nil
54
52
  def best_class_name(value)
55
53
  value_cls = value.class
@@ -103,19 +101,20 @@ module AppMap
103
101
  attr_accessor :defined_class, :method_id, :path, :lineno, :parameters, :receiver, :static
104
102
 
105
103
  class << self
106
- def build_from_invocation(mc = MethodCall.new, defined_class, method, receiver, arguments)
104
+ def build_from_invocation(defined_class, method, receiver, arguments, event: MethodCall.new)
105
+ event ||= MethodCall.new
107
106
  defined_class ||= 'Class'
108
- mc.tap do
107
+ event.tap do
109
108
  static = receiver.is_a?(Module)
110
- mc.defined_class = defined_class
111
- mc.method_id = method.name.to_s
109
+ event.defined_class = defined_class
110
+ event.method_id = method.name.to_s
112
111
  if method.source_location
113
112
  path = method.source_location[0]
114
113
  path = path[Dir.pwd.length + 1..-1] if path.index(Dir.pwd) == 0
115
- mc.path = path
116
- mc.lineno = method.source_location[1]
114
+ event.path = path
115
+ event.lineno = method.source_location[1]
117
116
  else
118
- mc.path = [ defined_class, static ? '.' : '#', method.name ].join
117
+ event.path = [ defined_class, static ? '.' : '#', method.name ].join
119
118
  end
120
119
 
121
120
  # Check if the method has key parameters. If there are any they'll always be last.
@@ -123,7 +122,7 @@ module AppMap
123
122
  has_key = [[:dummy], *method.parameters].last.first.to_s.start_with?('key') && arguments[-1].is_a?(Hash)
124
123
  kwargs = has_key && arguments[-1].dup || {}
125
124
 
126
- mc.parameters = method.parameters.map.with_index do |method_param, idx|
125
+ event.parameters = method.parameters.map.with_index do |method_param, idx|
127
126
  param_type, param_name = method_param
128
127
  param_name ||= 'arg'
129
128
  value = case param_type
@@ -144,13 +143,13 @@ module AppMap
144
143
  kind: param_type
145
144
  }
146
145
  end
147
- mc.receiver = {
146
+ event.receiver = {
148
147
  class: best_class_name(receiver),
149
148
  object_id: receiver.__id__,
150
149
  value: display_string(receiver)
151
150
  }
152
- mc.static = static
153
- MethodEvent.build_from_invocation(mc, :call)
151
+ event.static = static
152
+ MethodEvent.build_from_invocation(:call, event: event)
154
153
  end
155
154
  end
156
155
  end
@@ -175,11 +174,12 @@ module AppMap
175
174
  attr_accessor :parent_id, :elapsed
176
175
 
177
176
  class << self
178
- def build_from_invocation(mr = MethodReturnIgnoreValue.new, parent_id, elapsed)
179
- mr.tap do |_|
180
- mr.parent_id = parent_id
181
- mr.elapsed = elapsed
182
- MethodEvent.build_from_invocation(mr, :return)
177
+ def build_from_invocation(parent_id, elapsed: nil, event: MethodReturnIgnoreValue.new)
178
+ event ||= MethodReturnIgnoreValue.new
179
+ event.tap do |_|
180
+ event.parent_id = parent_id
181
+ event.elapsed = elapsed
182
+ MethodEvent.build_from_invocation(:return, event: event)
183
183
  end
184
184
  end
185
185
  end
@@ -187,7 +187,7 @@ module AppMap
187
187
  def to_h
188
188
  super.tap do |h|
189
189
  h[:parent_id] = parent_id
190
- h[:elapsed] = elapsed
190
+ h[:elapsed] = elapsed if elapsed
191
191
  end
192
192
  end
193
193
  end
@@ -196,10 +196,11 @@ module AppMap
196
196
  attr_accessor :return_value, :exceptions
197
197
 
198
198
  class << self
199
- def build_from_invocation(mr = MethodReturn.new, parent_id, elapsed, return_value, exception)
200
- mr.tap do |_|
199
+ def build_from_invocation(parent_id, return_value, exception, elapsed: nil, event: MethodReturn.new)
200
+ event ||= MethodReturn.new
201
+ event.tap do |_|
201
202
  if return_value
202
- mr.return_value = {
203
+ event.return_value = {
203
204
  class: best_class_name(return_value),
204
205
  value: display_string(return_value),
205
206
  object_id: return_value.__id__
@@ -212,7 +213,7 @@ module AppMap
212
213
  exception_backtrace = next_exception.backtrace_locations.try(:[], 0)
213
214
  exceptions << {
214
215
  class: best_class_name(next_exception),
215
- message: next_exception.message,
216
+ message: display_string(next_exception.message),
216
217
  object_id: next_exception.__id__,
217
218
  path: exception_backtrace&.path,
218
219
  lineno: exception_backtrace&.lineno
@@ -220,9 +221,9 @@ module AppMap
220
221
  next_exception = next_exception.cause
221
222
  end
222
223
 
223
- mr.exceptions = exceptions
224
+ event.exceptions = exceptions
224
225
  end
225
- MethodReturnIgnoreValue.build_from_invocation(mr, parent_id, elapsed)
226
+ MethodReturnIgnoreValue.build_from_invocation(parent_id, elapsed: elapsed, event: event)
226
227
  end
227
228
  end
228
229
  end