appmap 0.34.5 → 0.37.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b4318e1ef7e025a8f616022cefd323784e7b8ee8ccf06b1b7167b9937e860df
4
- data.tar.gz: c6a743f2c7f4ebe8cd58f7ae971b5dae15791bc808dab9d4dcf87f4971986818
3
+ metadata.gz: fba24ecdb42c951741292378419eef430a56cc16816df46a14b2ae3e318f8f94
4
+ data.tar.gz: 67ff27467f72322c5a8514c90b0a6039e3fd1268dba103dea3b832eda95ac317
5
5
  SHA512:
6
- metadata.gz: 9f271a71bb8bffa7004d054ae91646c9c93a476b15cbe80c459a9cafcb13ab0169be6c8a2db9b8cc993c3bc767deef4c9c0ca4befe6bd6881cf2d93c7b692b64
7
- data.tar.gz: 3b6c5067a325fa2a0aed71b5ed92179196061cd6cfa015f907a25dab798e757c2cd53efbd028513df1f22cc2e2b8aa9345f8d19aa27018eccfbee4cd39766ece
6
+ metadata.gz: 1823304c2733b46470481fd731a1f4650a80e769d96b47dbcdb0e5763e59930d6754068b4cf0729f9f748c55109575f3c8146b09030741fccfafd06cd45669b9
7
+ data.tar.gz: bbebe31041ed1720dc54b9522e4c6baac3b27d8d77be0622315657d0fa5b355c3e12474aa756811a6d747b563999c6e76206805c1392a015f92c6b863e8eeda0
data/.gitignore CHANGED
@@ -14,4 +14,5 @@ Gemfile.lock
14
14
  appmap.json
15
15
  .vscode
16
16
  .byebug_history
17
- /lib/appmap/appmap.bundle
17
+ /lib/appmap/appmap.bundle
18
+ *.so
@@ -1,3 +1,6 @@
1
+ AllCops:
2
+ NewCops: enable
3
+
1
4
  Layout/SpaceInsideArrayLiteralBrackets:
2
5
  Enabled: false
3
6
 
@@ -1,3 +1,27 @@
1
+ # v0.37.0
2
+ * Capture method source and comment.
3
+
4
+ # v0.36.0
5
+ * *appmap.yml* package definition may specify `gem`.
6
+ * Skip loading the railtie if `APPMAP_INITIALIZE` environment variable
7
+ is set to `false`.
8
+
9
+ # v0.35.2
10
+ * Make sure `MethodEvent#display_string` works when the value's `#to_s` and/or `#inspect`
11
+ methods have problems.
12
+
13
+ # v0.35.1
14
+ * Take out hooking of `IO` and `Logger` methods.
15
+ * Enable logging if either `APPMAP_DEBUG` or `DEBUG` is `true`.
16
+
17
+ # v0.35.0
18
+ * Provide a custom display string for files and HTTP requests.
19
+ * Report `mime_type` on HTTP response.
20
+
21
+ # v0.34.6
22
+ * Only warn once about problems determining database version for an ActiveRecord
23
+ connection.
24
+
1
25
  # v0.34.5
2
26
  * Ensure that hooking a method doesn't change its arity.
3
27
 
data/README.md CHANGED
@@ -1,3 +1,4 @@
1
+
1
2
  - [About](#about)
2
3
  - [Installation](#installation)
3
4
  - [Configuration](#configuration)
@@ -13,7 +14,7 @@
13
14
  - [Using fixture apps](#using-fixture-apps)
14
15
  - [`test/fixtures`](#testfixtures)
15
16
  - [`spec/fixtures`](#specfixtures)
16
- - [Build status](#build-status)
17
+
17
18
 
18
19
  # About
19
20
 
@@ -27,8 +28,9 @@ granular than a full debug trace. It's designed to be optimal for understanding
27
28
  There are several ways to record AppMaps of your Ruby program using the `appmap` gem:
28
29
 
29
30
  * Run your RSpec tests with the environment variable `APPMAP=true`. An AppMap will be generated for each spec.
30
- * Run your application server with AppMap remote recording enabled, and use the AppMap.
31
- browser extension to start, stop, and upload recordings.
31
+ * Run your application server with AppMap remote recording enabled, and use the [AppLand
32
+ browser extension](https://github.com/applandinc/appland-browser-extension) to start,
33
+ stop, and upload recordings.
32
34
  * Run the command `appmap record <program>` to record the entire execution of a program.
33
35
 
34
36
  Once you have recorded some AppMaps (for example, by running RSpec tests), you use the `appland upload` command
@@ -76,6 +78,7 @@ name: MyProject
76
78
  packages:
77
79
  - path: app/controllers
78
80
  - path: app/models
81
+ - gem: activerecord
79
82
  ```
80
83
 
81
84
  * **name** Provides the project name (required)
@@ -87,6 +90,7 @@ Each entry in the `packages` list is a YAML object which has the following keys:
87
90
 
88
91
  * **path** The path to the source code directory. The path may be relative to the current working directory, or it may
89
92
  be an absolute path.
93
+ * **gem** As an alternative to specifying the path, specify the name of a dependency gem. When using `gem`, don't specify `path`.
90
94
  * **exclude** A list of files and directories which will be ignored. By default, all modules, classes and public
91
95
  functions are inspected.
92
96
 
@@ -248,11 +252,11 @@ end
248
252
  $ bundle exec rails server
249
253
  ```
250
254
 
251
- 4. Open the AppApp browser extension and push `Start`.
255
+ 4. Open the AppLand browser extension and push `Start`.
252
256
 
253
257
  5. Use your app. For example, perform a login flow, or run through a manual UI test.
254
258
 
255
- 6. Open the AppApp browser extension and push `Stop`. The recording will be transferred to the AppLand website and opened in your browser.
259
+ 6. Open the AppLand browser extension and push `Stop`. The recording will be transferred to the AppLand website and opened in your browser.
256
260
 
257
261
  ## Ruby on Rails
258
262
 
@@ -265,6 +269,7 @@ Note that using this method is kind of a blunt instrument. Recording RSpecs and
265
269
  For instructions on uploading, see the documentation of the [AppLand CLI](https://github.com/applandinc/appland-cli).
266
270
 
267
271
  # Development
272
+ [![Build Status](https://travis-ci.org/applandinc/appmap-ruby.svg?branch=master)](https://travis-ci.org/applandinc/appmap-ruby)
268
273
 
269
274
  ## Running tests
270
275
 
@@ -289,7 +294,7 @@ $ bundle exec rake compile
289
294
  ### `test/fixtures`
290
295
 
291
296
  The fixture apps in `test/fixtures` are plain Ruby projects that exercise the basic functionality of the
292
- `appmap` gem. To develop in a fixture, simple enter the fixture directory and `bundle`.
297
+ `appmap` gem. To develop in a fixture, simply enter the fixture directory and `bundle`.
293
298
 
294
299
  ### `spec/fixtures`
295
300
 
@@ -339,5 +344,3 @@ Finished in 0.07357 seconds (files took 2.1 seconds to load)
339
344
  4 examples, 0 failures
340
345
  ```
341
346
 
342
- # Build status
343
- [![Build Status](https://travis-ci.org/applandinc/appmap-ruby.svg?branch=master)](https://travis-ci.org/applandinc/appmap-ruby)
data/Rakefile CHANGED
@@ -1,3 +1,4 @@
1
+ $: << File.join(__dir__, 'lib')
1
2
  require 'appmap/version'
2
3
  GEM_VERSION = AppMap::VERSION
3
4
 
@@ -113,7 +114,7 @@ namespace :spec do
113
114
  desc ruby_version
114
115
  task ruby_version, [:specs] => ["compile", "build:fixtures:#{ruby_version}:all"] do |_, task_args|
115
116
  run_specs(ruby_version, task_args)
116
- end.tap do|t|
117
+ end.tap do |t|
117
118
  desc "Run all specs"
118
119
  task :all, [:specs] => t
119
120
  end
@@ -27,10 +27,11 @@ Gem::Specification.new do |spec|
27
27
  spec.add_dependency 'activesupport'
28
28
  spec.add_dependency 'faraday'
29
29
  spec.add_dependency 'gli'
30
+ spec.add_dependency 'method_source'
30
31
  spec.add_dependency 'parser'
31
32
  spec.add_dependency 'rack'
32
33
 
33
- spec.add_development_dependency 'bundler', '~> 1.16'
34
+ spec.add_development_dependency 'bundler', '>= 1.16'
34
35
  spec.add_development_dependency 'minitest', '~> 5.0'
35
36
  spec.add_development_dependency 'pry-byebug'
36
37
  spec.add_development_dependency 'rake', '>= 12.3.3'
data/appmap.yml CHANGED
@@ -1,8 +1,2 @@
1
1
  name: AppMap Rubygem
2
- packages:
3
- - path: lib/appmap
4
- exclude:
5
- - server
6
- - trace
7
-
8
- - path: examples
2
+ packages: []
@@ -83,8 +83,8 @@ module AppMap
83
83
  end
84
84
 
85
85
  # Builds a class map from a config and a list of Ruby methods.
86
- def class_map(methods)
87
- ClassMap.build_from_methods(configuration, methods)
86
+ def class_map(methods, options = {})
87
+ ClassMap.build_from_methods(methods, options)
88
88
  end
89
89
 
90
90
  # Returns default metadata detected from the Ruby system and from the
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'method_source'
4
+
3
5
  module AppMap
4
6
  class ClassMap
5
7
  module HasChildren
@@ -48,7 +50,7 @@ module AppMap
48
50
  end
49
51
  end
50
52
  Function = Struct.new(:name) do
51
- attr_accessor :static, :location, :labels
53
+ attr_accessor :static, :location, :labels, :comment, :source
52
54
 
53
55
  def type
54
56
  'function'
@@ -60,31 +62,32 @@ module AppMap
60
62
  type: type,
61
63
  location: location,
62
64
  static: static,
63
- labels: labels
64
- }.delete_if { |k,v| v.nil? || v == [] }
65
+ labels: labels,
66
+ comment: comment,
67
+ source: source
68
+ }.delete_if { |_, v| v.nil? || v == [] }
65
69
  end
66
70
  end
67
71
  end
68
72
 
69
73
  class << self
70
- def build_from_methods(config, methods)
74
+ def build_from_methods(methods, options = {})
71
75
  root = Types::Root.new
72
76
  methods.each do |method|
73
- package = config.package_for_method(method) \
74
- or raise "No package found for method #{method}"
75
- add_function root, package, method
77
+ add_function root, method, options
76
78
  end
77
79
  root.children.map(&:to_h)
78
80
  end
79
81
 
80
82
  protected
81
83
 
82
- def add_function(root, package, method)
84
+ def add_function(root, method, include_source: true)
85
+ package = method.package
83
86
  static = method.static
84
87
 
85
88
  object_infos = [
86
89
  {
87
- name: package.path,
90
+ name: package.name,
88
91
  type: 'package'
89
92
  }
90
93
  ]
@@ -110,6 +113,16 @@ module AppMap
110
113
  [ method.defined_class, static ? '.' : '#', method.name ].join
111
114
  end
112
115
 
116
+ if include_source
117
+ begin
118
+ function_info[:source] = method.source
119
+ comment = method.comment || ''
120
+ function_info[:comment] = comment unless comment.empty?
121
+ rescue MethodSource::SourceNotFoundError
122
+ # pass
123
+ end
124
+ end
125
+
113
126
  function_info[:labels] = package.labels if package.labels
114
127
  object_infos << function_info
115
128
 
@@ -2,15 +2,39 @@
2
2
 
3
3
  module AppMap
4
4
  class Config
5
- Package = Struct.new(:path, :package_name, :exclude, :labels) do
6
- def initialize(path, package_name: nil, exclude: [], labels: [])
7
- super path, package_name, exclude, labels
5
+ Package = Struct.new(:path, :gem, :package_name, :exclude, :labels) do
6
+ class << self
7
+ def build_from_path(path, package_name: nil, exclude: [], labels: [])
8
+ Package.new(path, nil, package_name, exclude, labels)
9
+ end
10
+
11
+ def build_from_gem(gem, package_name: nil, exclude: [], labels: [])
12
+ gem_paths(gem).map do |gem_path|
13
+ Package.new(gem_path, gem, package_name, exclude, labels)
14
+ end
15
+ end
16
+
17
+ private_class_method :new
18
+
19
+ protected
20
+
21
+ def gem_paths(gem)
22
+ gemspec = Gem.loaded_specs[gem] or raise "Gem #{gem.inspect} not found"
23
+ gemspec.source_paths.map do |path|
24
+ File.join(gemspec.gem_dir, path)
25
+ end
26
+ end
27
+ end
28
+
29
+ def name
30
+ gem || path
8
31
  end
9
-
32
+
10
33
  def to_h
11
34
  {
12
35
  path: path,
13
36
  package_name: package_name,
37
+ gem: gem,
14
38
  exclude: exclude.blank? ? nil : exclude,
15
39
  labels: labels.blank? ? nil : labels
16
40
  }.compact
@@ -20,31 +44,29 @@ module AppMap
20
44
  Hook = Struct.new(:method_names, :package) do
21
45
  end
22
46
 
23
- OPENSSL_PACKAGE = Package.new('openssl', package_name: 'openssl', labels: %w[security crypto])
47
+ OPENSSL_PACKAGES = Package.build_from_path('openssl', package_name: 'openssl', labels: %w[security crypto])
24
48
 
25
49
  # Methods that should always be hooked, with their containing
26
50
  # package and labels that should be applied to them.
27
51
  HOOKED_METHODS = {
28
- 'ActiveSupport::SecurityUtils' => Hook.new(:secure_compare, Package.new('active_support', package_name: 'active_support', labels: %w[security crypto]))
52
+ 'ActiveSupport::SecurityUtils' => Hook.new(:secure_compare, Package.build_from_path('active_support', package_name: 'active_support', labels: %w[security crypto]))
29
53
  }.freeze
30
54
 
31
55
  BUILTIN_METHODS = {
32
- 'OpenSSL::PKey::PKey' => Hook.new(:sign, OPENSSL_PACKAGE),
33
- 'Digest::Instance' => Hook.new(:digest, OPENSSL_PACKAGE),
34
- 'OpenSSL::X509::Request' => Hook.new(%i[sign verify], OPENSSL_PACKAGE),
35
- 'OpenSSL::PKCS5' => Hook.new(%i[pbkdf2_hmac_sha1 pbkdf2_hmac], OPENSSL_PACKAGE),
36
- 'OpenSSL::Cipher' => Hook.new(%i[encrypt decrypt final], OPENSSL_PACKAGE),
37
- 'OpenSSL::X509::Certificate' => Hook.new(:sign, OPENSSL_PACKAGE),
38
- 'Logger' => Hook.new(:add, Package.new('logger', labels: %w[log io])),
39
- 'Net::HTTP' => Hook.new(:request, Package.new('net/http', package_name: 'net/http', labels: %w[http io])),
40
- 'Net::SMTP' => Hook.new(:send, Package.new('net/smtp', package_name: 'net/smtp', labels: %w[smtp email io])),
41
- 'Net::POP3' => Hook.new(:mails, Package.new('net/pop3', package_name: 'net/pop', labels: %w[pop pop3 email io])),
42
- 'Net::IMAP' => Hook.new(:send_command, Package.new('net/imap', package_name: 'net/imap', labels: %w[imap email io])),
43
- 'IO' => Hook.new(%i[read write open close], Package.new('io', labels: %w[io])),
44
- 'Marshal' => Hook.new(%i[dump load], Package.new('marshal', labels: %w[serialization marshal])),
45
- 'Psych' => Hook.new(%i[dump dump_stream load load_stream parse parse_stream], Package.new('yaml', package_name: 'psych', labels: %w[serialization yaml])),
46
- 'JSON::Ext::Parser' => Hook.new(:parse, Package.new('json', package_name: 'json', labels: %w[serialization json])),
47
- 'JSON::Ext::Generator::State' => Hook.new(:generate, Package.new('json', package_name: 'json', labels: %w[serialization json]))
56
+ 'OpenSSL::PKey::PKey' => Hook.new(:sign, OPENSSL_PACKAGES),
57
+ 'Digest::Instance' => Hook.new(:digest, OPENSSL_PACKAGES),
58
+ 'OpenSSL::X509::Request' => Hook.new(%i[sign verify], OPENSSL_PACKAGES),
59
+ 'OpenSSL::PKCS5' => Hook.new(%i[pbkdf2_hmac_sha1 pbkdf2_hmac], OPENSSL_PACKAGES),
60
+ 'OpenSSL::Cipher' => Hook.new(%i[encrypt decrypt final], OPENSSL_PACKAGES),
61
+ 'OpenSSL::X509::Certificate' => Hook.new(:sign, OPENSSL_PACKAGES),
62
+ 'Net::HTTP' => Hook.new(:request, Package.build_from_path('net/http', package_name: 'net/http', labels: %w[http io])),
63
+ 'Net::SMTP' => Hook.new(:send, Package.build_from_path('net/smtp', package_name: 'net/smtp', labels: %w[smtp email io])),
64
+ 'Net::POP3' => Hook.new(:mails, Package.build_from_path('net/pop3', package_name: 'net/pop', labels: %w[pop pop3 email io])),
65
+ 'Net::IMAP' => Hook.new(:send_command, Package.build_from_path('net/imap', package_name: 'net/imap', labels: %w[imap email io])),
66
+ 'Marshal' => Hook.new(%i[dump load], Package.build_from_path('marshal', labels: %w[serialization marshal])),
67
+ 'Psych' => Hook.new(%i[dump dump_stream load load_stream parse parse_stream], Package.build_from_path('yaml', package_name: 'psych', labels: %w[serialization yaml])),
68
+ 'JSON::Ext::Parser' => Hook.new(:parse, Package.build_from_path('json', package_name: 'json', labels: %w[serialization json])),
69
+ 'JSON::Ext::Generator::State' => Hook.new(:generate, Package.build_from_path('json', package_name: 'json', labels: %w[serialization json]))
48
70
  }.freeze
49
71
 
50
72
  attr_reader :name, :packages
@@ -64,8 +86,16 @@ module AppMap
64
86
  # Loads configuration from a Hash.
65
87
  def load(config_data)
66
88
  packages = (config_data['packages'] || []).map do |package|
67
- Package.new(package['path'], exclude: package['exclude'] || [])
68
- end
89
+ gem = package['gem']
90
+ path = package['path']
91
+ raise 'AppMap package configuration should specify gem or path, not both' if gem && path
92
+
93
+ if gem
94
+ Package.build_from_gem(gem, exclude: package['exclude'] || [])
95
+ else
96
+ [ Package.build_from_path(path, exclude: package['exclude'] || []) ]
97
+ end
98
+ end.flatten
69
99
  Config.new config_data['name'], packages
70
100
  end
71
101
  end
@@ -105,7 +135,7 @@ module AppMap
105
135
  hook = find_hook(defined_class)
106
136
  return nil unless hook
107
137
 
108
- Array(hook.method_names).include?(method_name) ? hook.package : nil
138
+ Array(hook.method_names).include?(method_name) ? hook.package : nil
109
139
  end
110
140
 
111
141
  def find_hook(defined_class)
@@ -31,25 +31,43 @@ module AppMap
31
31
  def display_string(value)
32
32
  return nil unless value
33
33
 
34
+ value_string = custom_display_string(value) || default_display_string(value)
35
+
36
+ (value_string||'')[0...LIMIT].encode('utf-8', invalid: :replace, undef: :replace, replace: '_')
37
+ end
38
+
39
+ protected
40
+
41
+ def custom_display_string(value)
42
+ case value
43
+ when File
44
+ "#{value.class}[path=#{value.path}]"
45
+ when Net::HTTP
46
+ "#{value.class}[#{value.address}:#{value.port}]"
47
+ when Net::HTTPGenericRequest
48
+ "#{value.class}[#{value.method} #{value.path}]"
49
+ end
50
+ rescue StandardError
51
+ nil
52
+ end
53
+
54
+ def default_display_string(value)
34
55
  last_resort_string = lambda do
35
56
  warn "AppMap encountered an error inspecting a #{value.class.name}: #{$!.message}"
36
57
  '*Error inspecting variable*'
37
58
  end
38
59
 
39
- value_string = \
60
+ begin
61
+ value.to_s
62
+ rescue NoMethodError
40
63
  begin
41
- value.to_s
42
- rescue NoMethodError
43
- begin
44
- value.inspect
45
- rescue StandardError
46
- last_resort_string.call
47
- end
64
+ value.inspect
48
65
  rescue StandardError
49
66
  last_resort_string.call
50
67
  end
51
-
52
- (value_string||'')[0...LIMIT].encode('utf-8', invalid: :replace, undef: :replace, replace: '_')
68
+ rescue StandardError
69
+ last_resort_string.call
70
+ end
53
71
  end
54
72
  end
55
73
  end
@@ -4,7 +4,7 @@ require 'English'
4
4
 
5
5
  module AppMap
6
6
  class Hook
7
- LOG = (ENV['DEBUG'] == 'true')
7
+ LOG = (ENV['APPMAP_DEBUG'] == 'true' || ENV['DEBUG'] == 'true')
8
8
 
9
9
  @unbound_method_arity = ::UnboundMethod.instance_method(:arity)
10
10
  @method_arity = ::Method.instance_method(:arity)
@@ -48,7 +48,6 @@ module AppMap
48
48
  hook = lambda do |hook_cls|
49
49
  lambda do |method_id|
50
50
  method = hook_cls.public_instance_method(method_id)
51
- hook_method = Hook::Method.new(hook_cls, method)
52
51
 
53
52
  warn "AppMap: Examining #{hook_cls} #{method.name}" if LOG
54
53
 
@@ -56,14 +55,16 @@ module AppMap
56
55
  # Skip methods that have no instruction sequence, as they are obviously trivial.
57
56
  next unless disasm
58
57
 
59
- # Don't try and trace the AppMap methods or there will be
60
- # a stack overflow in the defined hook method.
61
- next if /\AAppMap[:\.]/.match?(hook_method.method_display_name)
62
-
63
58
  next unless \
64
59
  config.always_hook?(hook_cls, method.name) ||
65
60
  config.included_by_location?(method)
66
61
 
62
+ hook_method = Hook::Method.new(config.package_for_method(method), hook_cls, method)
63
+
64
+ # Don't try and trace the AppMap methods or there will be
65
+ # a stack overflow in the defined hook method.
66
+ next if /\AAppMap[:\.]/.match?(hook_method.method_display_name)
67
+
67
68
  hook_method.activate
68
69
  end
69
70
  end
@@ -97,9 +98,9 @@ module AppMap
97
98
  end
98
99
 
99
100
  if method
100
- Hook::Method.new(cls, method).activate
101
+ Hook::Method.new(hook.package, cls, method).activate
101
102
  else
102
- warn "Method #{method_name} not found on #{cls.name}"
103
+ warn "Method #{method_name} not found on #{cls.name}"
103
104
  end
104
105
  end
105
106
  end