http_instrumentation 1.0.0 → 1.0.1

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: 2df5ce2bdf71e28c9a3c95afa298ef3d1d035d17dbc5b8afb8deced97b1d336c
4
- data.tar.gz: debfebac778a078bb34ccaa213389e1785bae0f02f116ba8d4aa2e1584dc26cb
3
+ metadata.gz: 1457b4d97046d8c0a41e99aa0825386e3540eb67ac5603fb0ecc80e2fca77e92
4
+ data.tar.gz: 78b242f2f2821f22d8abddd754e641a271232da63e8218b2492ecd0907e43eca
5
5
  SHA512:
6
- metadata.gz: b268d25d6e6a7e948b68e47d0e08762e3c4708a78feaa17821ebe65ae06d7f675c1ae26a45afb27c983fe5d625f47c4f4cdfa4023ef33b7d71deda6e163d5385
7
- data.tar.gz: 4fc09ff4a26388982f0f2b877c7827cd629bfd0f4905f71e6f31a1ab3c5dbd565b39da5cd96b185319daea8fc445b3b4cc58ef0a3744fe08c0a5eebe2d8e5fe3
6
+ metadata.gz: 6aab6fde374e4f00c40509e32950fc8d3ab3b18666a8c5be593f1fc2d58a1907ab19ea1ba2a309d1728a6c3611aaeae54f8873a2e25b7450a55dbe9952358811
7
+ data.tar.gz: a22576c8eb6253c18c3705211c09764df2c1221c65797fcb944c8f3a62f31c7b7f5f6aec1abe607393c98ebd9bc90792662e609673a1f4bdea86f3b33030e132
data/CHANGELOG.md CHANGED
@@ -4,7 +4,13 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
- ## 1.0.0 (unreleased)
7
+ ## 1.0.1
8
+
9
+ ### Fixed
10
+ - Added detection for other gems that may be instrumenting these same libraries and ensuring that the method of injecting the instrumentation call is compatible. This fixes issues caused by the fundamental incompatibility in Ruby between `alias_method` and `prepend` on the same method. Aliasing is now the default method for injection since this is the most compatible. However if another library has already prepended behavior on a method, then `prepend` will be used instead.
11
+ - `HTTPInstrumentation.client` properly returns the block return value if it was called with a block.
12
+
13
+ ## 1.0.0
8
14
 
9
15
  ### Added
10
16
  - Initial release.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.0
1
+ 1.0.1
@@ -11,17 +11,23 @@ module HTTPInstrumentation
11
11
  module CurbHook
12
12
  class << self
13
13
  def instrument!
14
- Instrumentation.instrument!(::Curl::Easy, self) if defined?(::Curl::Easy)
14
+ Instrumentation.instrument!(::Curl::Easy, self, :http) if defined?(::Curl::Easy)
15
15
  end
16
16
 
17
17
  def installed?
18
18
  !!(defined?(::Curl::Easy) && ::Curl::Easy.include?(self))
19
19
  end
20
+
21
+ attr_accessor :aliased
20
22
  end
21
23
 
22
- def http(method, *)
24
+ def http(method, *args)
23
25
  HTTPInstrumentation.instrument("curb") do |payload|
24
- retval = super
26
+ retval = if HTTPInstrumentation::Instrumentation::CurbHook.aliased
27
+ http_without_http_instrumentation(method, *args)
28
+ else
29
+ super
30
+ end
25
31
 
26
32
  payload[:http_method] = method
27
33
  begin
@@ -11,8 +11,8 @@ module HTTPInstrumentation
11
11
  module EthonHook
12
12
  class << self
13
13
  def instrument!
14
- Instrumentation.instrument!(::Ethon::Easy, Easy) if defined?(::Ethon::Easy)
15
- Instrumentation.instrument!(::Ethon::Multi, Multi) if defined?(::Ethon::Multi)
14
+ Instrumentation.instrument!(::Ethon::Easy, Easy, [:http_request, :perform]) if defined?(::Ethon::Easy)
15
+ Instrumentation.instrument!(::Ethon::Multi, Multi, :perform) if defined?(::Ethon::Multi)
16
16
  end
17
17
 
18
18
  def installed?
@@ -24,31 +24,51 @@ module HTTPInstrumentation
24
24
  end
25
25
 
26
26
  module Multi
27
- def perform(*)
27
+ class << self
28
+ attr_accessor :aliased
29
+ end
30
+
31
+ def perform(*args)
28
32
  HTTPInstrumentation.instrument("ethon") do |payload|
29
33
  begin
30
34
  payload[:count] = easy_handles.size
31
35
  rescue
32
36
  end
33
37
 
34
- super
38
+ if HTTPInstrumentation::Instrumentation::EthonHook::Multi.aliased
39
+ perform_without_http_instrumentation(*args)
40
+ else
41
+ super
42
+ end
35
43
  end
36
44
  end
37
45
  end
38
46
 
39
47
  module Easy
40
- def http_request(url, action_name, *)
41
- @http_method = action_name
42
- @http_url = url
43
- super
48
+ class << self
49
+ attr_accessor :aliased
44
50
  end
45
51
 
46
- def perform(*)
52
+ def http_request(url, action_name, *args)
53
+ @http_instrumentation_method = action_name
54
+ @http_instrumentation_url = url
55
+ if HTTPInstrumentation::Instrumentation::EthonHook::Easy.aliased
56
+ http_request_without_http_instrumentation(url, action_name, *args)
57
+ else
58
+ super
59
+ end
60
+ end
61
+
62
+ def perform(*args)
47
63
  HTTPInstrumentation.instrument("ethon") do |payload|
48
- retval = super
64
+ retval = if HTTPInstrumentation::Instrumentation::EthonHook::Easy.aliased
65
+ perform_without_http_instrumentation(*args)
66
+ else
67
+ super
68
+ end
49
69
 
50
- payload[:http_method] = @http_method
51
- payload[:url] = @http_url
70
+ payload[:http_method] = @http_instrumentation_method
71
+ payload[:url] = @http_instrumentation_url
52
72
  begin
53
73
  payload[:status_code] = response_code
54
74
  rescue
@@ -11,17 +11,23 @@ module HTTPInstrumentation
11
11
  module ExconHook
12
12
  class << self
13
13
  def instrument!
14
- Instrumentation.instrument!(::Excon::Connection, self) if defined?(::Excon::Connection)
14
+ Instrumentation.instrument!(::Excon::Connection, self, :request) if defined?(::Excon::Connection)
15
15
  end
16
16
 
17
17
  def installed?
18
18
  !!(defined?(::Excon::Connection) && ::Excon::Connection.include?(self))
19
19
  end
20
+
21
+ attr_accessor :aliased
20
22
  end
21
23
 
22
- def request(params = {}, *)
24
+ def request(params = {})
23
25
  HTTPInstrumentation.instrument("excon") do |payload|
24
- response = super
26
+ response = if HTTPInstrumentation::Instrumentation::ExconHook.aliased
27
+ request_without_http_instrumentation(params)
28
+ else
29
+ super
30
+ end
25
31
 
26
32
  begin
27
33
  info = params
@@ -11,17 +11,23 @@ module HTTPInstrumentation
11
11
  module HTTPHook
12
12
  class << self
13
13
  def instrument!
14
- Instrumentation.instrument!(::HTTP::Client, self) if defined?(::HTTP::Client)
14
+ Instrumentation.instrument!(::HTTP::Client, self, :perform) if defined?(::HTTP::Client)
15
15
  end
16
16
 
17
17
  def installed?
18
18
  !!(defined?(::HTTP::Client) && ::HTTP::Client.include?(self))
19
19
  end
20
+
21
+ attr_accessor :aliased
20
22
  end
21
23
 
22
- def perform(request, *)
24
+ def perform(request, *args)
23
25
  HTTPInstrumentation.instrument("http") do |payload|
24
- response = super
26
+ response = if HTTPInstrumentation::Instrumentation::HTTPHook.aliased
27
+ perform_without_http_instrumentation(request, *args)
28
+ else
29
+ super
30
+ end
25
31
 
26
32
  begin
27
33
  payload[:http_method] = request.verb
@@ -11,17 +11,23 @@ module HTTPInstrumentation
11
11
  # This module is responsible for instrumenting the httpclient gem.
12
12
  class << self
13
13
  def instrument!
14
- Instrumentation.instrument!(::HTTPClient, self) if defined?(::HTTPClient)
14
+ Instrumentation.instrument!(::HTTPClient, self, :do_get_block) if defined?(::HTTPClient)
15
15
  end
16
16
 
17
17
  def installed?
18
18
  !!(defined?(::HTTPClient) && ::HTTPClient.include?(self))
19
19
  end
20
+
21
+ attr_accessor :aliased
20
22
  end
21
23
 
22
- def do_get_block(request, *)
24
+ def do_get_block(request, *args)
23
25
  HTTPInstrumentation.instrument("httpclient") do |payload|
24
- response = super
26
+ response = if HTTPInstrumentation::Instrumentation::HTTPClientHook.aliased
27
+ do_get_block_without_http_instrumentation(request, *args)
28
+ else
29
+ super
30
+ end
25
31
 
26
32
  begin
27
33
  payload[:http_method] = request.header.request_method
@@ -11,19 +11,25 @@ module HTTPInstrumentation
11
11
  module HTTPXHook
12
12
  class << self
13
13
  def instrument!
14
- Instrumentation.instrument!(::HTTPX::Session, self) if defined?(::HTTPX::Session)
14
+ Instrumentation.instrument!(::HTTPX::Session, self, :send_requests) if defined?(::HTTPX::Session)
15
15
  end
16
16
 
17
17
  def installed?
18
18
  !!(defined?(::HTTPX::Session) && ::HTTPX::Session.include?(self))
19
19
  end
20
+
21
+ attr_accessor :aliased
20
22
  end
21
23
 
22
24
  private
23
25
 
24
26
  def send_requests(*requests)
25
27
  HTTPInstrumentation.instrument("httpx") do |payload|
26
- responses = super
28
+ responses = if HTTPInstrumentation::Instrumentation::HTTPXHook.aliased
29
+ send_requests_without_http_instrumentation(*requests)
30
+ else
31
+ super
32
+ end
27
33
 
28
34
  if requests.size == 1
29
35
  begin
@@ -11,19 +11,31 @@ module HTTPInstrumentation
11
11
  module NetHTTPHook
12
12
  class << self
13
13
  def instrument!
14
- Instrumentation.instrument!(::Net::HTTP, self) if defined?(::Net::HTTP)
14
+ Instrumentation.instrument!(::Net::HTTP, self, :request) if defined?(::Net::HTTP)
15
15
  end
16
16
 
17
17
  def installed?
18
18
  !!(defined?(::Net::HTTP) && ::Net::HTTP.include?(self))
19
19
  end
20
+
21
+ attr_accessor :aliased
20
22
  end
21
23
 
22
- def request(req, *)
23
- return super unless started?
24
+ def request(req, *args, &block)
25
+ unless started?
26
+ if HTTPInstrumentation::Instrumentation::NetHTTPHook.aliased
27
+ return request_without_http_instrumentation(req, *args, &block)
28
+ else
29
+ return super
30
+ end
31
+ end
24
32
 
25
33
  HTTPInstrumentation.instrument("net/http") do |payload|
26
- response = super
34
+ response = if HTTPInstrumentation::Instrumentation::NetHTTPHook.aliased
35
+ request_without_http_instrumentation(req, *args, &block)
36
+ else
37
+ super
38
+ end
27
39
 
28
40
  default_port = (use_ssl? ? 443 : 80)
29
41
  scheme = (use_ssl? ? "https" : "http")
@@ -11,19 +11,25 @@ module HTTPInstrumentation
11
11
  module PatronHook
12
12
  class << self
13
13
  def instrument!
14
- Instrumentation.instrument!(::Patron::Session, self) if defined?(::Patron::Session)
14
+ Instrumentation.instrument!(::Patron::Session, self, :request) if defined?(::Patron::Session)
15
15
  end
16
16
 
17
17
  def installed?
18
18
  !!(defined?(::Patron::Session) && ::Patron::Session.include?(self))
19
19
  end
20
+
21
+ attr_accessor :aliased
20
22
  end
21
23
 
22
24
  private
23
25
 
24
- def request(action, url, *)
26
+ def request(action, url, *args)
25
27
  HTTPInstrumentation.instrument("patron") do |payload|
26
- response = super
28
+ response = if HTTPInstrumentation::Instrumentation::PatronHook.aliased
29
+ request_without_http_instrumentation(action, url, *args)
30
+ else
31
+ super
32
+ end
27
33
 
28
34
  payload[:http_method] = action
29
35
  payload[:url] = url
@@ -11,8 +11,8 @@ module HTTPInstrumentation
11
11
  module TyphoeusHook
12
12
  class << self
13
13
  def instrument!
14
- Instrumentation.instrument!(::Typhoeus::Request, Easy) if defined?(::Typhoeus::Request)
15
- Instrumentation.instrument!(::Typhoeus::Hydra, Multi) if defined?(::Typhoeus::Hydra)
14
+ Instrumentation.instrument!(::Typhoeus::Request, Easy, :run) if defined?(::Typhoeus::Request)
15
+ Instrumentation.instrument!(::Typhoeus::Hydra, Multi, :run) if defined?(::Typhoeus::Hydra)
16
16
  end
17
17
 
18
18
  def installed?
@@ -24,22 +24,38 @@ module HTTPInstrumentation
24
24
  end
25
25
 
26
26
  module Multi
27
- def run(*)
27
+ class << self
28
+ attr_accessor :aliased
29
+ end
30
+
31
+ def run(*args)
28
32
  HTTPInstrumentation.instrument("typhoeus") do |payload|
29
33
  begin
30
34
  payload[:count] = queued_requests.size
31
35
  rescue
32
36
  end
33
37
 
34
- super
38
+ if HTTPInstrumentation::Instrumentation::TyphoeusHook::Multi.aliased
39
+ run_without_http_instrumentation(*args)
40
+ else
41
+ super
42
+ end
35
43
  end
36
44
  end
37
45
  end
38
46
 
39
47
  module Easy
40
- def run(*)
48
+ class << self
49
+ attr_accessor :aliased
50
+ end
51
+
52
+ def run(*args)
41
53
  HTTPInstrumentation.instrument("typhoeus") do |payload|
42
- retval = super
54
+ retval = if HTTPInstrumentation::Instrumentation::TyphoeusHook::Easy.aliased
55
+ run_without_http_instrumentation(*args)
56
+ else
57
+ super
58
+ end
43
59
 
44
60
  begin
45
61
  payload[:http_method] = options[:method]
@@ -13,9 +13,53 @@ require_relative "instrumentation/typhoeus_hook"
13
13
  module HTTPInstrumentation
14
14
  module Instrumentation
15
15
  class << self
16
- # Helper method to prepend an instrumentation module to a class.
17
- def instrument!(klass, instrumentation_module)
18
- klass.prepend(instrumentation_module) unless klass.include?(instrumentation_module)
16
+ # Helper method to add an instrumentation module to methods on a class. The
17
+ # methods must be defined in the instrumentation module.
18
+ #
19
+ # If the methods have already been prepended on the class, then module will
20
+ # be prepended to the class. Otherwise, the methods will be aliased and the
21
+ # module will be included in the class. This is because prepending and aliasing
22
+ # methods are not compatible with each other and other instrumentation libraries
23
+ # may have already prepended the methods. Aliasing is the default strategy because
24
+ # prepending after aliasing will work, but aliasing after prepending will not.
25
+ def instrument!(klass, instrumentation_module, methods)
26
+ return if klass.include?(instrumentation_module)
27
+
28
+ methods = Array(methods).collect(&:to_sym)
29
+
30
+ if HTTPInstrumentation.force_prepend? || methods_prepended?(klass, methods)
31
+ klass.prepend(instrumentation_module)
32
+ instrumentation_module.aliased = false
33
+ else
34
+ Array(methods).each do |method|
35
+ instrumentation_module.alias_method("#{method}_with_http_instrumentation", method)
36
+ end
37
+
38
+ klass.include(instrumentation_module)
39
+
40
+ Array(methods).each do |method|
41
+ klass.alias_method("#{method}_without_http_instrumentation", method)
42
+ klass.alias_method(method, "#{method}_with_http_instrumentation")
43
+ end
44
+
45
+ instrumentation_module.aliased = true
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def methods_prepended?(klass, methods)
52
+ prepended = false
53
+
54
+ klass.ancestors.each do |mod|
55
+ next unless mod.is_a?(Module) && !mod.is_a?(Class)
56
+
57
+ module_methods = mod.instance_methods(false) + mod.private_instance_methods(false)
58
+ prepended = (module_methods & methods).any?
59
+ break if prepended
60
+ end
61
+
62
+ prepended
19
63
  end
20
64
  end
21
65
  end
@@ -60,18 +60,20 @@ module HTTPInstrumentation
60
60
  # block. If no block is given, then return the current HTTP client name.
61
61
  #
62
62
  # @param name [String, Symbol, nil] The name of the client to set
63
- # @return [String, Symbol, nil] The current name of the client
63
+ # @return [String, Symbol, nil] If a block is given, then the return value of the block. Otherwise
64
+ # the current client name.
64
65
  def client(name = nil)
65
66
  save_val = Thread.current[:http_instrumentation_client]
66
67
  if block_given?
67
68
  begin
68
- Thread.current[:http_instrumentation_client] = name
69
+ Thread.current[:http_instrumentation_client] = name&.to_s
69
70
  yield
70
71
  ensure
71
72
  Thread.current[:http_instrumentation_client] = save_val
72
73
  end
74
+ else
75
+ save_val
73
76
  end
74
- save_val
75
77
  end
76
78
 
77
79
  # Returns true if instrumentation is currently silenced.
@@ -121,6 +123,14 @@ module HTTPInstrumentation
121
123
  end
122
124
  end
123
125
 
126
+ # Returns true if instrumentation should always use module prepend rather than method aliasing.
127
+ # The default is to use method aliasing since that is more compatible with other libraries.
128
+ # Prepending can be forced by setting the HTTP_INSTRUMENTATION_FORCE_PREPEND environment variable
129
+ # to "true".
130
+ def force_prepend?
131
+ ENV.fetch("HTTP_INSTRUMENTATION_FORCE_PREPEND", nil) == "true"
132
+ end
133
+
124
134
  private
125
135
 
126
136
  # Turn the given value into a lowercase symbol.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: http_instrumentation
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Durand
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-07-28 00:00:00.000000000 Z
11
+ date: 2023-08-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -80,7 +80,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
80
80
  - !ruby/object:Gem::Version
81
81
  version: '0'
82
82
  requirements: []
83
- rubygems_version: 3.4.12
83
+ rubygems_version: 3.2.22
84
84
  signing_key:
85
85
  specification_version: 4
86
86
  summary: ActiveSupoprt instrumentation for a variety of Ruby HTTP client libraries.