appmap 0.44.0 → 0.45.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 +4 -4
- data/.releaserc.yml +11 -0
- data/.travis.yml +20 -14
- data/CHANGELOG.md +14 -0
- data/README_CI.md +29 -0
- data/Rakefile +4 -2
- data/appmap.gemspec +5 -1
- data/lib/appmap.rb +1 -0
- data/lib/appmap/config.rb +11 -1
- data/lib/appmap/handler/function.rb +19 -0
- data/lib/appmap/handler/net_http.rb +107 -0
- data/lib/appmap/hook/method.rb +5 -7
- data/lib/appmap/rails/request_handler.rb +15 -33
- data/lib/appmap/trace.rb +2 -1
- data/lib/appmap/util.rb +23 -2
- data/lib/appmap/version.rb +2 -2
- data/release.sh +17 -0
- data/spec/abstract_controller_base_spec.rb +7 -3
- data/spec/config_spec.rb +3 -1
- data/spec/hook_spec.rb +1 -8
- data/spec/record_net_http_spec.rb +160 -0
- data/spec/spec_helper.rb +10 -0
- metadata +13 -8
- data/patch +0 -1447
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0db499c7da4baea3f29fb56214a95e18047a07fc0c132ba559f7f6eb7ef9033a
|
4
|
+
data.tar.gz: 3aabf3bf1a31c39d6844ccf17807a4b7e206783bdf8e373008d3db0d04a854d7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a38e51f5c932d9a04b566c2664104e08a56af80fd3277c039603eb719336599bc9764781e1d0835c6c7b702e4e0465587fd4537e292df325c0d9efe4a90c1493
|
7
|
+
data.tar.gz: 168e2b8c1605cf7b4f8c4d6a5f373b5add4ce1804c4c494f3cc4e3b29da5a08f4e121889bca36e06e1e29aa4c9943c16b76f7868e9331c3a7e5b33af0817f1a0
|
data/.releaserc.yml
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
plugins:
|
2
|
+
- '@semantic-release/commit-analyzer'
|
3
|
+
- '@semantic-release/release-notes-generator'
|
4
|
+
- '@semantic-release/changelog'
|
5
|
+
- 'semantic-release-rubygem'
|
6
|
+
- - '@semantic-release/git'
|
7
|
+
- assets:
|
8
|
+
- CHANGELOG.md
|
9
|
+
- appmap.gemspec
|
10
|
+
- lib/appmap/version.rb
|
11
|
+
- '@semantic-release/github'
|
data/.travis.yml
CHANGED
@@ -16,23 +16,29 @@ before_script:
|
|
16
16
|
|
17
17
|
cache:
|
18
18
|
bundler: true
|
19
|
-
directories:
|
20
|
-
- $HOME/docker
|
21
|
-
|
22
|
-
# https://stackoverflow.com/a/41975912
|
23
|
-
before_cache:
|
24
|
-
# Save tagged docker images
|
25
|
-
- >
|
26
|
-
mkdir -p $HOME/docker && docker images -a --filter='dangling=false' --format '{{.Repository}}:{{.Tag}} {{.ID}}'
|
27
|
-
| xargs -n 2 -t sh -c 'test -e $HOME/docker/$1.tar.gz || docker save $0 | gzip -2 > $HOME/docker/$1.tar.gz'
|
28
|
-
|
29
|
-
before_install:
|
30
|
-
# Load cached docker images
|
31
|
-
- if [[ -d $HOME/docker ]]; then ls $HOME/docker/*.tar.gz | xargs -I {file} sh -c "zcat {file} | docker load"; fi
|
32
19
|
|
20
|
+
|
21
|
+
# GEM_ALTERNATIVE_NAME only needed for deployment
|
33
22
|
jobs:
|
34
23
|
include:
|
35
24
|
- stage: test
|
36
25
|
script:
|
37
26
|
- mkdir tmp
|
38
|
-
- bundle exec rake test
|
27
|
+
- GEM_ALTERNATIVE_NAME='' bundle exec rake test
|
28
|
+
|
29
|
+
|
30
|
+
before_deploy:
|
31
|
+
- |
|
32
|
+
nvm install --lts \
|
33
|
+
&& nvm use --lts \
|
34
|
+
&& npm i -g \
|
35
|
+
semantic-release \
|
36
|
+
@semantic-release/git \
|
37
|
+
@semantic-release/changelog \
|
38
|
+
semantic-release-rubygem
|
39
|
+
|
40
|
+
deploy:
|
41
|
+
- provider: script
|
42
|
+
script: ./release.sh
|
43
|
+
on:
|
44
|
+
branch: master
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
# [0.45.0](https://github.com/applandinc/appmap-ruby/compare/v0.44.0...v0.45.0) (2021-05-03)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* Properly name status_code in HTTP server response ([556e87c](https://github.com/applandinc/appmap-ruby/commit/556e87c9a7bf214f6b8714add4f77448fd223d33))
|
7
|
+
|
8
|
+
|
9
|
+
### Features
|
10
|
+
|
11
|
+
* Record http_client_request and http_client_response ([1db32ae](https://github.com/applandinc/appmap-ruby/commit/1db32ae0d26a7f1400b6b814d25b13368f06c158))
|
12
|
+
* Update AppMap format version to 1.5.0 ([061705e](https://github.com/applandinc/appmap-ruby/commit/061705e4619cb881e8edd022ef835183e399e127))
|
13
|
+
* **build:** add deployment via `semantic-release` with automatic publication to rubygems ([9f183de](https://github.com/applandinc/appmap-ruby/commit/9f183de13f405900000c3da979c3a8a5b6e34a24))
|
14
|
+
|
1
15
|
# v0.44.0
|
2
16
|
|
3
17
|
* Support recording and labeling of indivudal functions via `functions:` section in *appmap.yml*.
|
data/README_CI.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Configuration variables:
|
2
|
+
|
3
|
+
* `GH_TOKEN`: used by `semantic-release` to push changes to Github and manage releases
|
4
|
+
* `GEM_HOST_API_KEY`: rubygems API key
|
5
|
+
* `GEM_ALTERNATIVE_NAME` (optional): used for testing of CI flows,
|
6
|
+
to avoid publication of test releases under official package name
|
7
|
+
* `DOCKERHUB\_USERNAME`, `DOCKERHUB_PASSWORD`: optional dockerhub credentials,
|
8
|
+
to avoid throttling of dockerhub anonymous pulls
|
9
|
+
|
10
|
+
Note: for security reasons, it's better to use dedicated (not personal)
|
11
|
+
Dockerhub account,
|
12
|
+
and also use [access tokens](https://docs.docker.com/docker-hub/access-tokens/)
|
13
|
+
instead of primary password
|
14
|
+
|
15
|
+
# Release command
|
16
|
+
|
17
|
+
`./release.sh`
|
18
|
+
|
19
|
+
Bash wrapper script is used merely as a launcher of `semantic-release`
|
20
|
+
with extra logic to explicitly determine git url from `TRAVIS_REPO_SLUG` \
|
21
|
+
variable if its defined (otherwise git url is taken from `package.json`,
|
22
|
+
which breaks CI on forked repos).
|
23
|
+
|
24
|
+
# CI flow
|
25
|
+
|
26
|
+
1. Test happens using current version number specified in `lib/appmap/version.rb`, then `release.sh` launches `semantic-release` to do the rest
|
27
|
+
2. The version number is increased (including modicication of `version.rb`)
|
28
|
+
3. Gem is published under new version number
|
29
|
+
4. Github release is created with the new version number
|
data/Rakefile
CHANGED
@@ -37,7 +37,8 @@ end
|
|
37
37
|
|
38
38
|
def build_base_image(ruby_version)
|
39
39
|
run_cmd "docker build" \
|
40
|
-
" --build-arg RUBY_VERSION=#{ruby_version}
|
40
|
+
" --build-arg RUBY_VERSION=#{ruby_version}" \
|
41
|
+
" --build-arg GEM_VERSION=#{GEM_VERSION}" \
|
41
42
|
" -t appmap:#{GEM_VERSION} -f Dockerfile.appmap ."
|
42
43
|
end
|
43
44
|
|
@@ -46,7 +47,7 @@ def build_app_image(app, ruby_version)
|
|
46
47
|
run_cmd( {"RUBY_VERSION" => ruby_version, "GEM_VERSION" => GEM_VERSION},
|
47
48
|
" docker-compose build" \
|
48
49
|
" --build-arg RUBY_VERSION=#{ruby_version}" \
|
49
|
-
" --build-arg GEM_VERSION=#{GEM_VERSION}")
|
50
|
+
" --build-arg GEM_VERSION=#{GEM_VERSION}" )
|
50
51
|
end
|
51
52
|
end
|
52
53
|
|
@@ -138,3 +139,4 @@ task spec: %i[spec:all]
|
|
138
139
|
task test: %i[spec:all minitest]
|
139
140
|
|
140
141
|
task default: :test
|
142
|
+
|
data/appmap.gemspec
CHANGED
@@ -4,8 +4,12 @@ lib = File.expand_path('lib', __dir__)
|
|
4
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
5
|
require 'appmap/version'
|
6
6
|
|
7
|
+
|
8
|
+
|
7
9
|
Gem::Specification.new do |spec|
|
8
|
-
|
10
|
+
# ability to parameterize gem name is added intentionally,
|
11
|
+
# to support the possibility of unofficial releases, e.g. during CI tests
|
12
|
+
spec.name = (ENV['GEM_ALTERNATIVE_NAME'].to_s.empty? ? 'appmap' : ENV["GEM_ALTERNATIVE_NAME"] )
|
9
13
|
spec.version = AppMap::VERSION
|
10
14
|
spec.authors = ['Kevin Gilpin']
|
11
15
|
spec.email = ['kgilpin@gmail.com']
|
data/lib/appmap.rb
CHANGED
data/lib/appmap/config.rb
CHANGED
@@ -3,6 +3,13 @@
|
|
3
3
|
module AppMap
|
4
4
|
class Config
|
5
5
|
Package = Struct.new(:path, :gem, :package_name, :exclude, :labels, :shallow) do
|
6
|
+
attr_writer :handler_class
|
7
|
+
|
8
|
+
def handler_class
|
9
|
+
require 'appmap/handler/function'
|
10
|
+
@handler_class || AppMap::Handler::Function
|
11
|
+
end
|
12
|
+
|
6
13
|
# Indicates that only the entry points to a package will be recorded.
|
7
14
|
# Once the code has entered a package, subsequent calls within the package will not be
|
8
15
|
# recorded unless the code leaves the package and re-enters it.
|
@@ -42,6 +49,7 @@ module AppMap
|
|
42
49
|
path: path,
|
43
50
|
package_name: package_name,
|
44
51
|
gem: gem,
|
52
|
+
handler_class: handler_class.name,
|
45
53
|
exclude: exclude.blank? ? nil : exclude,
|
46
54
|
labels: labels.blank? ? nil : labels,
|
47
55
|
shallow: shallow
|
@@ -107,7 +115,9 @@ module AppMap
|
|
107
115
|
Hook.new(:invoke_after, Package.build_from_path('active_support', package_name: 'active_support', labels: %w[mvc.after_action])),
|
108
116
|
],
|
109
117
|
'OpenSSL::X509::Certificate' => Hook.new(:sign, OPENSSL_PACKAGES.(%w[crypto.x509])),
|
110
|
-
'Net::HTTP' => Hook.new(:request, Package.build_from_path('net/http', package_name: 'net/http', labels: %w[protocol.http])
|
118
|
+
'Net::HTTP' => Hook.new(:request, Package.build_from_path('net/http', package_name: 'net/http', labels: %w[protocol.http]).tap do |package|
|
119
|
+
package.handler_class = AppMap::Handler::NetHTTP
|
120
|
+
end),
|
111
121
|
'Net::SMTP' => Hook.new(:send, Package.build_from_path('net/smtp', package_name: 'net/smtp', labels: %w[protocol.email.smtp])),
|
112
122
|
'Net::POP3' => Hook.new(:mails, Package.build_from_path('net/pop3', package_name: 'net/pop', labels: %w[protocol.email.pop])),
|
113
123
|
'Net::IMAP' => Hook.new(:send_command, Package.build_from_path('net/imap', package_name: 'net/imap', labels: %w[protocol.email.imap])),
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'appmap/event'
|
4
|
+
|
5
|
+
module AppMap
|
6
|
+
module Handler
|
7
|
+
module Function
|
8
|
+
class << self
|
9
|
+
def handle_call(defined_class, hook_method, receiver, args)
|
10
|
+
AppMap::Event::MethodCall.build_from_invocation(defined_class, hook_method, receiver, args)
|
11
|
+
end
|
12
|
+
|
13
|
+
def handle_return(call_event_id, elapsed, return_value, exception)
|
14
|
+
AppMap::Event::MethodReturn.build_from_invocation(call_event_id, elapsed, return_value, exception)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'appmap/event'
|
4
|
+
|
5
|
+
module AppMap
|
6
|
+
module Handler
|
7
|
+
class HTTPClientRequest < AppMap::Event::MethodEvent
|
8
|
+
attr_accessor :request_method, :url, :params, :headers
|
9
|
+
|
10
|
+
def initialize(http, request)
|
11
|
+
super AppMap::Event.next_id_counter, :call, Thread.current.object_id
|
12
|
+
|
13
|
+
path, query = request.path.split('?')
|
14
|
+
query ||= ''
|
15
|
+
|
16
|
+
protocol = http.use_ssl? ? 'https' : 'http'
|
17
|
+
port = if http.use_ssl? && http.port == 443
|
18
|
+
nil
|
19
|
+
elsif !http.use_ssl? && http.port == 80
|
20
|
+
nil
|
21
|
+
else
|
22
|
+
":#{http.port}"
|
23
|
+
end
|
24
|
+
|
25
|
+
url = [ protocol, '://', http.address, port, path ].compact.join
|
26
|
+
|
27
|
+
self.request_method = request.method
|
28
|
+
self.url = url
|
29
|
+
self.headers = AppMap::Util.select_headers(NetHTTP.request_headers(request))
|
30
|
+
self.params = Rack::Utils.parse_nested_query(query)
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_h
|
34
|
+
super.tap do |h|
|
35
|
+
h[:http_client_request] = {
|
36
|
+
request_method: request_method,
|
37
|
+
url: url,
|
38
|
+
headers: headers
|
39
|
+
}.compact
|
40
|
+
|
41
|
+
unless params.blank?
|
42
|
+
h[:message] = params.keys.map do |key|
|
43
|
+
val = params[key]
|
44
|
+
{
|
45
|
+
name: key,
|
46
|
+
class: val.class.name,
|
47
|
+
value: self.class.display_string(val),
|
48
|
+
object_id: val.__id__,
|
49
|
+
}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class HTTPClientResponse < AppMap::Event::MethodReturnIgnoreValue
|
57
|
+
attr_accessor :status, :mime_type, :headers
|
58
|
+
|
59
|
+
def initialize(response, parent_id, elapsed)
|
60
|
+
super AppMap::Event.next_id_counter, :return, Thread.current.object_id
|
61
|
+
|
62
|
+
self.status = response.code.to_i
|
63
|
+
self.parent_id = parent_id
|
64
|
+
self.elapsed = elapsed
|
65
|
+
self.headers = AppMap::Util.select_headers(NetHTTP.response_headers(response))
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_h
|
69
|
+
super.tap do |h|
|
70
|
+
h[:http_client_response] = {
|
71
|
+
status_code: status,
|
72
|
+
mime_type: mime_type,
|
73
|
+
headers: headers
|
74
|
+
}.compact
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class NetHTTP
|
80
|
+
class << self
|
81
|
+
def request_headers(request)
|
82
|
+
{}.tap do |headers|
|
83
|
+
request.each_header do |k,v|
|
84
|
+
key = [ 'HTTP', k.underscore.upcase ].join('_')
|
85
|
+
headers[key] = v
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
alias response_headers request_headers
|
91
|
+
|
92
|
+
def handle_call(defined_class, hook_method, receiver, args)
|
93
|
+
# request will call itself again in a start block if it's not already started.
|
94
|
+
return unless receiver.started?
|
95
|
+
|
96
|
+
http = receiver
|
97
|
+
request = args.first
|
98
|
+
HTTPClientRequest.new(http, request)
|
99
|
+
end
|
100
|
+
|
101
|
+
def handle_return(call_event_id, elapsed, return_value, exception)
|
102
|
+
HTTPClientResponse.new(return_value, call_event_id, elapsed)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
data/lib/appmap/hook/method.rb
CHANGED
@@ -76,7 +76,7 @@ module AppMap
|
|
76
76
|
raise
|
77
77
|
ensure
|
78
78
|
with_disabled_hook.call do
|
79
|
-
after_hook.call(self, call_event, start_time, return_value, exception)
|
79
|
+
after_hook.call(self, call_event, start_time, return_value, exception) if call_event
|
80
80
|
end
|
81
81
|
end
|
82
82
|
end
|
@@ -87,18 +87,16 @@ module AppMap
|
|
87
87
|
protected
|
88
88
|
|
89
89
|
def before_hook(receiver, defined_class, args)
|
90
|
-
|
91
|
-
|
92
|
-
AppMap.tracing.record_event call_event, package: hook_package, defined_class: defined_class, method: hook_method
|
90
|
+
call_event = hook_package.handler_class.handle_call(defined_class, hook_method, receiver, args)
|
91
|
+
AppMap.tracing.record_event(call_event, package: hook_package, defined_class: defined_class, method: hook_method) if call_event
|
93
92
|
[ call_event, TIME_NOW.call ]
|
94
93
|
end
|
95
94
|
|
96
95
|
def after_hook(_receiver, call_event, start_time, return_value, exception)
|
97
|
-
require 'appmap/event'
|
98
96
|
elapsed = TIME_NOW.call - start_time
|
99
|
-
return_event =
|
100
|
-
AppMap::Event::MethodReturn.build_from_invocation call_event.id, elapsed, return_value, exception
|
97
|
+
return_event = hook_package.handler_class.handle_return(call_event.id, elapsed, return_value, exception)
|
101
98
|
AppMap.tracing.record_event return_event
|
99
|
+
nil
|
102
100
|
end
|
103
101
|
|
104
102
|
def with_disabled_hook(&function)
|
@@ -6,26 +6,6 @@ require 'appmap/hook'
|
|
6
6
|
module AppMap
|
7
7
|
module Rails
|
8
8
|
module RequestHandler
|
9
|
-
# Host and User-Agent will just introduce needless variation.
|
10
|
-
# Content-Type and Authorization get their own fields in the request.
|
11
|
-
IGNORE_HEADERS = %w[host user_agent content_type authorization].map(&:upcase).map {|h| "HTTP_#{h}"}.freeze
|
12
|
-
|
13
|
-
class << self
|
14
|
-
def selected_headers(env)
|
15
|
-
# Rack prepends HTTP_ to all client-sent headers.
|
16
|
-
matching_headers = env
|
17
|
-
.select { |k,v| k.start_with? 'HTTP_'}
|
18
|
-
.reject { |k,v| IGNORE_HEADERS.member?(k) }
|
19
|
-
.reject { |k,v| v.blank? }
|
20
|
-
.each_with_object({}) do |kv, memo|
|
21
|
-
key = kv[0].sub(/^HTTP_/, '').split('_').map(&:capitalize).join('-')
|
22
|
-
value = kv[1]
|
23
|
-
memo[key] = value
|
24
|
-
end
|
25
|
-
matching_headers.blank? ? nil : matching_headers
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
9
|
class HTTPServerRequest < AppMap::Event::MethodEvent
|
30
10
|
attr_accessor :normalized_path_info, :request_method, :path_info, :params, :mime_type, :headers, :authorization
|
31
11
|
|
@@ -35,7 +15,7 @@ module AppMap
|
|
35
15
|
self.request_method = request.request_method
|
36
16
|
self.normalized_path_info = normalized_path(request)
|
37
17
|
self.mime_type = request.headers['Content-Type']
|
38
|
-
self.headers =
|
18
|
+
self.headers = AppMap::Util.select_headers(request.env)
|
39
19
|
self.authorization = request.headers['Authorization']
|
40
20
|
self.path_info = request.path_info.split('?')[0]
|
41
21
|
# ActionDispatch::Http::ParameterFilter is deprecated
|
@@ -59,16 +39,18 @@ module AppMap
|
|
59
39
|
headers: headers,
|
60
40
|
}.compact
|
61
41
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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
|
72
54
|
end
|
73
55
|
end
|
74
56
|
end
|
@@ -97,13 +79,13 @@ module AppMap
|
|
97
79
|
self.mime_type = response.headers['Content-Type']
|
98
80
|
self.parent_id = parent_id
|
99
81
|
self.elapsed = elapsed
|
100
|
-
self.headers =
|
82
|
+
self.headers = AppMap::Util.select_headers(response.headers)
|
101
83
|
end
|
102
84
|
|
103
85
|
def to_h
|
104
86
|
super.tap do |h|
|
105
87
|
h[:http_server_response] = {
|
106
|
-
|
88
|
+
status_code: status,
|
107
89
|
mime_type: mime_type,
|
108
90
|
headers: headers
|
109
91
|
}.compact
|
data/lib/appmap/trace.rb
CHANGED
@@ -82,7 +82,8 @@ module AppMap
|
|
82
82
|
|
83
83
|
@last_package_for_thread[Thread.current.object_id] = package if package
|
84
84
|
@events << event
|
85
|
-
|
85
|
+
static = event.static if event.respond_to?(:static)
|
86
|
+
@methods << Trace::ScopedMethod.new(package, defined_class, method, static) \
|
86
87
|
if package && defined_class && method && (event.event == :call)
|
87
88
|
end
|
88
89
|
|