appmap 0.34.5 → 0.37.0

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