elastic-apm 3.15.1 → 4.2.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/.ci/.jenkins_exclude.yml +13 -0
- data/.ci/.jenkins_ruby.yml +0 -1
- data/.ci/Jenkinsfile +1 -0
- data/.github/dependabot.yml +16 -0
- data/.rubocop.yml +0 -7
- data/CHANGELOG.asciidoc +77 -4
- data/Gemfile +4 -1
- data/README.md +12 -0
- data/SECURITY.md +7 -0
- data/bench/report.rb +1 -1
- data/bin/run-bdd +17 -0
- data/bin/run-tests +1 -1
- data/docker-compose.yml +1 -1
- data/docs/configuration.asciidoc +62 -147
- data/docs/release-notes.asciidoc +1 -0
- data/docs/upgrading.asciidoc +0 -27
- data/lib/elastic_apm.rb +12 -1
- data/lib/elastic_apm/agent.rb +7 -3
- data/lib/elastic_apm/central_config.rb +8 -7
- data/lib/elastic_apm/config.rb +2 -88
- data/lib/elastic_apm/config/regexp_list.rb +1 -1
- data/lib/elastic_apm/config/wildcard_pattern_list.rb +1 -1
- data/lib/elastic_apm/context/response.rb +1 -3
- data/lib/elastic_apm/fields.rb +88 -0
- data/lib/elastic_apm/grpc.rb +2 -4
- data/lib/elastic_apm/instrumenter.rb +1 -1
- data/lib/elastic_apm/metadata/cloud_info.rb +32 -5
- data/lib/elastic_apm/metadata/system_info.rb +14 -4
- data/lib/elastic_apm/metrics/cpu_mem_set.rb +1 -1
- data/lib/elastic_apm/normalizers.rb +2 -2
- data/lib/elastic_apm/normalizers/rails/active_record.rb +3 -3
- data/lib/elastic_apm/opentracing.rb +3 -2
- data/lib/elastic_apm/span.rb +26 -1
- data/lib/elastic_apm/span/context.rb +2 -1
- data/lib/elastic_apm/span/context/destination.rb +53 -40
- data/lib/elastic_apm/span_helpers.rb +6 -8
- data/lib/elastic_apm/spies.rb +20 -0
- data/lib/elastic_apm/spies/action_dispatch.rb +10 -9
- data/lib/elastic_apm/spies/azure_storage_table.rb +148 -0
- data/lib/elastic_apm/spies/dynamo_db.rb +12 -12
- data/lib/elastic_apm/spies/elasticsearch.rb +32 -29
- data/lib/elastic_apm/spies/faraday.rb +83 -63
- data/lib/elastic_apm/spies/http.rb +33 -34
- data/lib/elastic_apm/spies/mongo.rb +5 -3
- data/lib/elastic_apm/spies/net_http.rb +59 -56
- data/lib/elastic_apm/spies/rake.rb +28 -26
- data/lib/elastic_apm/spies/redis.rb +11 -10
- data/lib/elastic_apm/spies/resque.rb +18 -21
- data/lib/elastic_apm/spies/s3.rb +14 -15
- data/lib/elastic_apm/spies/sequel.rb +42 -48
- data/lib/elastic_apm/spies/sidekiq.rb +13 -15
- data/lib/elastic_apm/spies/sinatra.rb +20 -21
- data/lib/elastic_apm/spies/sns.rb +39 -44
- data/lib/elastic_apm/spies/sqs.rb +21 -31
- data/lib/elastic_apm/spies/tilt.rb +10 -9
- data/lib/elastic_apm/sql/tokenizer.rb +21 -5
- data/lib/elastic_apm/stacktrace_builder.rb +3 -1
- data/lib/elastic_apm/subscriber.rb +1 -0
- data/lib/elastic_apm/trace_context.rb +5 -13
- data/lib/elastic_apm/trace_context/traceparent.rb +5 -6
- data/lib/elastic_apm/transport/connection.rb +1 -1
- data/lib/elastic_apm/transport/connection/http.rb +4 -2
- data/lib/elastic_apm/transport/connection/proxy_pipe.rb +1 -2
- data/lib/elastic_apm/transport/filters/hash_sanitizer.rb +5 -23
- data/lib/elastic_apm/transport/serializers/metadata_serializer.rb +3 -2
- data/lib/elastic_apm/transport/serializers/metricset_serializer.rb +2 -2
- data/lib/elastic_apm/transport/serializers/span_serializer.rb +12 -12
- data/lib/elastic_apm/transport/user_agent.rb +3 -2
- data/lib/elastic_apm/transport/worker.rb +1 -1
- data/lib/elastic_apm/util/deep_dup.rb +1 -1
- data/lib/elastic_apm/version.rb +1 -1
- metadata +12 -9
- data/lib/elastic_apm/sql.rb +0 -36
- data/lib/elastic_apm/sql_summarizer.rb +0 -53
@@ -22,6 +22,7 @@ module ElasticAPM
|
|
22
22
|
module Spies
|
23
23
|
# @api private
|
24
24
|
class DynamoDBSpy
|
25
|
+
NAME = 'dynamodb'
|
25
26
|
TYPE = 'db'
|
26
27
|
SUBTYPE = 'dynamodb'
|
27
28
|
|
@@ -49,15 +50,12 @@ module ElasticAPM
|
|
49
50
|
end
|
50
51
|
end
|
51
52
|
|
52
|
-
|
53
|
-
|
53
|
+
# @api private
|
54
|
+
module Ext
|
55
|
+
def self.prepended(mod)
|
54
56
|
# Alias all available operations
|
55
|
-
api.operation_names.each do |operation_name|
|
56
|
-
alias :"#{operation_name}_without_apm" :"#{operation_name}"
|
57
|
-
|
57
|
+
mod.api.operation_names.each do |operation_name|
|
58
58
|
define_method(operation_name) do |params = {}, options = {}|
|
59
|
-
cloud = ElasticAPM::Span::Context::Destination::Cloud.new(region: config.region)
|
60
|
-
|
61
59
|
context = ElasticAPM::Span::Context.new(
|
62
60
|
db: {
|
63
61
|
instance: config.region,
|
@@ -65,9 +63,8 @@ module ElasticAPM
|
|
65
63
|
statement: params[:key_condition_expression]
|
66
64
|
},
|
67
65
|
destination: {
|
68
|
-
|
69
|
-
|
70
|
-
type: TYPE
|
66
|
+
service: { resource: "#{SUBTYPE}/#{config.region}" },
|
67
|
+
cloud: { region: config.region }
|
71
68
|
}
|
72
69
|
)
|
73
70
|
|
@@ -79,14 +76,17 @@ module ElasticAPM
|
|
79
76
|
context: context
|
80
77
|
) do
|
81
78
|
ElasticAPM::Spies::DynamoDBSpy.without_net_http do
|
82
|
-
|
83
|
-
original_method.call(params, options)
|
79
|
+
super(params, options)
|
84
80
|
end
|
85
81
|
end
|
86
82
|
end
|
87
83
|
end
|
88
84
|
end
|
89
85
|
end
|
86
|
+
|
87
|
+
def install
|
88
|
+
::Aws::DynamoDB::Client.prepend(Ext)
|
89
|
+
end
|
90
90
|
end
|
91
91
|
|
92
92
|
register(
|
@@ -37,47 +37,50 @@ module ElasticAPM
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
return perform_request_without_apm(method, path, *args, &block)
|
47
|
-
end
|
40
|
+
# @api private
|
41
|
+
module Ext
|
42
|
+
def perform_request(method, path, *args, &block)
|
43
|
+
unless ElasticAPM.current_transaction
|
44
|
+
return super(method, path, *args, &block)
|
45
|
+
end
|
48
46
|
|
49
|
-
|
50
|
-
|
47
|
+
name = format(NAME_FORMAT, method, path)
|
48
|
+
statement = []
|
51
49
|
|
52
|
-
|
50
|
+
statement << { params: args&.[](0) }
|
53
51
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
end
|
52
|
+
if ElasticAPM.agent.config.capture_elasticsearch_queries
|
53
|
+
unless args[1].nil? || args[1].empty?
|
54
|
+
body =
|
55
|
+
ElasticAPM::Spies::ElasticsearchSpy
|
56
|
+
.sanitizer.strip_from(args[1])
|
57
|
+
statement << { body: body }
|
61
58
|
end
|
59
|
+
end
|
62
60
|
|
63
|
-
|
64
|
-
|
65
|
-
|
61
|
+
context = Span::Context.new(
|
62
|
+
db: { statement: statement.reduce({}, :merge).to_json },
|
63
|
+
destination: {
|
64
|
+
service: {
|
66
65
|
name: SUBTYPE,
|
67
66
|
resource: SUBTYPE,
|
68
67
|
type: TYPE
|
69
68
|
}
|
70
|
-
|
69
|
+
}
|
70
|
+
)
|
71
71
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
end
|
72
|
+
ElasticAPM.with_span(
|
73
|
+
name,
|
74
|
+
TYPE,
|
75
|
+
subtype: SUBTYPE,
|
76
|
+
context: context
|
77
|
+
) { super(method, path, *args, &block) }
|
79
78
|
end
|
80
79
|
end
|
80
|
+
|
81
|
+
def install
|
82
|
+
::Elasticsearch::Transport::Client.prepend(Ext)
|
83
|
+
end
|
81
84
|
end
|
82
85
|
|
83
86
|
register(
|
@@ -22,88 +22,108 @@ module ElasticAPM
|
|
22
22
|
module Spies
|
23
23
|
# @api private
|
24
24
|
class FaradaySpy
|
25
|
-
|
26
|
-
|
25
|
+
DISABLE_KEY = :__elastic_apm_faraday_disabled
|
26
|
+
TYPE = 'external'
|
27
|
+
SUBTYPE = 'http'
|
27
28
|
|
28
|
-
|
29
|
-
|
29
|
+
class << self
|
30
|
+
def disabled=(disabled)
|
31
|
+
Thread.current[DISABLE_KEY] = disabled
|
32
|
+
end
|
33
|
+
|
34
|
+
def disabled?
|
35
|
+
Thread.current[DISABLE_KEY] ||= false
|
36
|
+
end
|
30
37
|
|
31
|
-
|
32
|
-
|
33
|
-
|
38
|
+
def disable_in
|
39
|
+
self.disabled = true
|
40
|
+
|
41
|
+
begin
|
42
|
+
yield
|
43
|
+
ensure
|
44
|
+
self.disabled = false
|
45
|
+
end
|
34
46
|
end
|
35
|
-
# rubocop:enable Style/ExplicitBlockArgument
|
36
47
|
end
|
37
48
|
|
38
|
-
#
|
39
|
-
|
40
|
-
|
41
|
-
|
49
|
+
# @api private
|
50
|
+
module Ext
|
51
|
+
# rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
52
|
+
def run_request(method, url, body, headers, &block)
|
53
|
+
unless (transaction = ElasticAPM.current_transaction)
|
54
|
+
return super(method, url, body, headers, &block)
|
55
|
+
end
|
42
56
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
end
|
57
|
+
if ElasticAPM::Spies::FaradaySpy.disabled?
|
58
|
+
return super(method, url, body, headers, &block)
|
59
|
+
end
|
47
60
|
|
48
|
-
|
61
|
+
uri = URI(build_url(url))
|
49
62
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
end
|
58
|
-
uri = tmp_request.path && URI(tmp_request.path)
|
63
|
+
# If url is set inside block it isn't available until yield,
|
64
|
+
# so we temporarily build the request to yield. This could be a
|
65
|
+
# problem if the block has side effects as it will be yielded twice
|
66
|
+
# ~mikker
|
67
|
+
unless uri.host
|
68
|
+
tmp_request = build_request(method) do |req|
|
69
|
+
yield(req) if block_given?
|
59
70
|
end
|
71
|
+
uri = tmp_request.path && URI(tmp_request.path)
|
72
|
+
end
|
60
73
|
|
61
|
-
|
74
|
+
host = uri&.host || 'localhost'
|
62
75
|
|
63
|
-
|
76
|
+
upcased_method = method.to_s.upcase
|
64
77
|
|
65
|
-
|
66
|
-
|
78
|
+
if uri
|
79
|
+
destination = ElasticAPM::Span::Context::Destination.from_uri(uri, type: SUBTYPE)
|
67
80
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
81
|
+
context =
|
82
|
+
ElasticAPM::Span::Context.new(
|
83
|
+
http: { url: uri, method: upcased_method },
|
84
|
+
destination: destination
|
85
|
+
)
|
86
|
+
else
|
87
|
+
context =
|
88
|
+
ElasticAPM::Span::Context.new(http: { url: uri, method: upcased_method })
|
89
|
+
end
|
90
|
+
|
91
|
+
context =
|
92
|
+
ElasticAPM::Span::Context.new(
|
93
|
+
http: { url: uri, method: upcased_method },
|
94
|
+
destination: destination
|
95
|
+
)
|
96
|
+
|
97
|
+
ElasticAPM.with_span(
|
98
|
+
"#{upcased_method} #{host}",
|
99
|
+
TYPE,
|
100
|
+
subtype: SUBTYPE,
|
101
|
+
context: context
|
102
|
+
) do |span|
|
103
|
+
ElasticAPM::Spies.without_net_http do
|
104
|
+
trace_context = span&.trace_context || transaction.trace_context
|
105
|
+
|
106
|
+
result = super(method, url, body, headers) do |req|
|
107
|
+
trace_context.apply_headers { |k, v| req[k] = v }
|
108
|
+
|
109
|
+
yield req if block
|
110
|
+
end
|
77
111
|
|
78
|
-
|
79
|
-
|
80
|
-
TYPE,
|
81
|
-
subtype: SUBTYPE,
|
82
|
-
action: upcased_method,
|
83
|
-
context: context
|
84
|
-
) do |span|
|
85
|
-
ElasticAPM::Spies::FaradaySpy.without_net_http do
|
86
|
-
trace_context = span&.trace_context || transaction.trace_context
|
87
|
-
|
88
|
-
result =
|
89
|
-
run_request_without_apm(method, url, body, headers) do |req|
|
90
|
-
trace_context.apply_headers { |k, v| req[k] = v }
|
91
|
-
|
92
|
-
yield req if block_given?
|
93
|
-
end
|
94
|
-
|
95
|
-
if (http = span&.context&.http)
|
96
|
-
http.status_code = result.status.to_s
|
97
|
-
end
|
98
|
-
|
99
|
-
span&.outcome = Span::Outcome.from_http_status(result.status)
|
100
|
-
result
|
112
|
+
if (http = span&.context&.http)
|
113
|
+
http.status_code = result.status.to_s
|
101
114
|
end
|
115
|
+
|
116
|
+
span&.outcome = Span::Outcome.from_http_status(result.status)
|
117
|
+
result
|
102
118
|
end
|
103
119
|
end
|
104
120
|
end
|
105
121
|
end
|
106
|
-
# rubocop:enable Metrics/
|
122
|
+
# rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
123
|
+
|
124
|
+
def install
|
125
|
+
::Faraday::Connection.prepend(Ext)
|
126
|
+
end
|
107
127
|
end
|
108
128
|
|
109
129
|
register 'Faraday', 'faraday', FaradaySpy.new
|
@@ -22,51 +22,50 @@ module ElasticAPM
|
|
22
22
|
module Spies
|
23
23
|
# @api private
|
24
24
|
class HTTPSpy
|
25
|
-
TYPE = '
|
26
|
-
SUBTYPE = '
|
27
|
-
def install
|
28
|
-
::HTTP::Client.class_eval do
|
29
|
-
alias perform_without_apm perform
|
30
|
-
|
31
|
-
def perform(req, options)
|
32
|
-
unless (transaction = ElasticAPM.current_transaction)
|
33
|
-
return perform_without_apm(req, options)
|
34
|
-
end
|
25
|
+
TYPE = 'external'
|
26
|
+
SUBTYPE = 'http'
|
35
27
|
|
36
|
-
|
37
|
-
|
28
|
+
# @api private
|
29
|
+
module Ext
|
30
|
+
def perform(req, options)
|
31
|
+
unless (transaction = ElasticAPM.current_transaction)
|
32
|
+
return super(req, options)
|
33
|
+
end
|
38
34
|
|
39
|
-
|
40
|
-
|
41
|
-
context = ElasticAPM::Span::Context.new(
|
42
|
-
http: { url: req.uri, method: method },
|
43
|
-
destination: destination
|
44
|
-
)
|
35
|
+
method = req.verb.to_s.upcase
|
36
|
+
host = req.uri.host
|
45
37
|
|
46
|
-
|
38
|
+
context = ElasticAPM::Span::Context.new(
|
39
|
+
http: { url: req.uri, method: method },
|
40
|
+
destination: ElasticAPM::Span::Context::Destination.from_uri(req.uri, type: SUBTYPE)
|
41
|
+
)
|
47
42
|
|
48
|
-
|
49
|
-
name,
|
50
|
-
TYPE,
|
51
|
-
subtype: SUBTYPE,
|
52
|
-
action: method,
|
53
|
-
context: context
|
54
|
-
) do |span|
|
55
|
-
trace_context = span&.trace_context || transaction.trace_context
|
56
|
-
trace_context.apply_headers { |key, value| req[key] = value }
|
43
|
+
name = "#{method} #{host}"
|
57
44
|
|
58
|
-
|
45
|
+
ElasticAPM.with_span(
|
46
|
+
name,
|
47
|
+
TYPE,
|
48
|
+
subtype: SUBTYPE,
|
49
|
+
context: context
|
50
|
+
) do |span|
|
51
|
+
trace_context = span&.trace_context || transaction.trace_context
|
52
|
+
trace_context.apply_headers { |key, value| req[key] = value }
|
59
53
|
|
60
|
-
|
61
|
-
http.status_code = result.status.to_s
|
62
|
-
end
|
54
|
+
result = super(req, options)
|
63
55
|
|
64
|
-
|
65
|
-
result
|
56
|
+
if (http = span&.context&.http)
|
57
|
+
http.status_code = result.status.to_s
|
66
58
|
end
|
59
|
+
|
60
|
+
span&.outcome = Span::Outcome.from_http_status(result.status)
|
61
|
+
result
|
67
62
|
end
|
68
63
|
end
|
69
64
|
end
|
65
|
+
|
66
|
+
def install
|
67
|
+
::HTTP::Client.prepend(Ext)
|
68
|
+
end
|
70
69
|
end
|
71
70
|
|
72
71
|
register 'HTTP', 'http', HTTPSpy.new
|
@@ -22,17 +22,17 @@ module ElasticAPM
|
|
22
22
|
module Spies
|
23
23
|
# @api private
|
24
24
|
class NetHTTPSpy
|
25
|
-
|
26
|
-
TYPE = '
|
27
|
-
SUBTYPE = '
|
25
|
+
DISABLE_KEY = :__elastic_apm_net_http_disabled
|
26
|
+
TYPE = 'external'
|
27
|
+
SUBTYPE = 'http'
|
28
28
|
|
29
29
|
class << self
|
30
30
|
def disabled=(disabled)
|
31
|
-
Thread.current[
|
31
|
+
Thread.current[DISABLE_KEY] = disabled
|
32
32
|
end
|
33
33
|
|
34
34
|
def disabled?
|
35
|
-
Thread.current[
|
35
|
+
Thread.current[DISABLE_KEY] ||= false
|
36
36
|
end
|
37
37
|
|
38
38
|
def disable_in
|
@@ -46,65 +46,68 @@ module ElasticAPM
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
-
#
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
49
|
+
# @api private
|
50
|
+
module Ext
|
51
|
+
# rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
52
|
+
def request(req, body = nil, &block)
|
53
|
+
unless (transaction = ElasticAPM.current_transaction)
|
54
|
+
return super(req, body, &block)
|
55
|
+
end
|
54
56
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
end
|
57
|
+
if ElasticAPM::Spies::NetHTTPSpy.disabled?
|
58
|
+
return super(req, body, &block)
|
59
|
+
end
|
59
60
|
|
60
|
-
|
61
|
-
|
61
|
+
host = req['host']&.split(':')&.first || address || 'localhost'
|
62
|
+
method = req.method.to_s.upcase
|
63
|
+
|
64
|
+
uri_or_path = URI(req.path)
|
65
|
+
|
66
|
+
# Support the case where a whole url is passed as a path to a nil host
|
67
|
+
uri =
|
68
|
+
if uri_or_path.host
|
69
|
+
uri_or_path
|
70
|
+
else
|
71
|
+
path, query = req.path.split('?')
|
72
|
+
url = use_ssl? ? +'https://' : +'http://'
|
73
|
+
url << host
|
74
|
+
url << ":#{port}" if port
|
75
|
+
url << path
|
76
|
+
url << "?#{query}" if query
|
77
|
+
URI(url)
|
62
78
|
end
|
63
79
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
ElasticAPM.with_span(
|
85
|
-
"#{method} #{host}",
|
86
|
-
TYPE,
|
87
|
-
subtype: SUBTYPE,
|
88
|
-
action: method,
|
89
|
-
context: context
|
90
|
-
) do |span|
|
91
|
-
trace_context = span&.trace_context || transaction.trace_context
|
92
|
-
trace_context.apply_headers { |key, value| req[key] = value }
|
93
|
-
|
94
|
-
result = request_without_apm(req, body, &block)
|
95
|
-
|
96
|
-
if (http = span&.context&.http)
|
97
|
-
http.status_code = result.code
|
98
|
-
end
|
99
|
-
|
100
|
-
span&.outcome = Span::Outcome.from_http_status(result.code)
|
101
|
-
result
|
80
|
+
context =
|
81
|
+
ElasticAPM::Span::Context.new(
|
82
|
+
http: { url: uri, method: method },
|
83
|
+
destination: ElasticAPM::Span::Context::Destination.from_uri(uri, type: SUBTYPE)
|
84
|
+
)
|
85
|
+
|
86
|
+
ElasticAPM.with_span(
|
87
|
+
"#{method} #{host}",
|
88
|
+
TYPE,
|
89
|
+
subtype: SUBTYPE,
|
90
|
+
context: context
|
91
|
+
) do |span|
|
92
|
+
trace_context = span&.trace_context || transaction.trace_context
|
93
|
+
trace_context.apply_headers { |key, value| req[key] = value }
|
94
|
+
|
95
|
+
result = super(req, body, &block)
|
96
|
+
|
97
|
+
if (http = span&.context&.http)
|
98
|
+
http.status_code = result.code
|
102
99
|
end
|
100
|
+
|
101
|
+
span&.outcome = Span::Outcome.from_http_status(result.code)
|
102
|
+
result
|
103
103
|
end
|
104
104
|
end
|
105
|
+
# rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
106
|
+
end
|
107
|
+
|
108
|
+
def install
|
109
|
+
Net::HTTP.prepend(Ext)
|
105
110
|
end
|
106
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
107
|
-
# rubocop:enable Metrics/PerceivedComplexity
|
108
111
|
end
|
109
112
|
|
110
113
|
register 'Net::HTTP', 'net/http', NetHTTPSpy.new
|