newrelic_rpm 6.15.0 → 7.0.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.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +96 -22
  3. data/README.md +2 -2
  4. data/lib/new_relic/agent.rb +0 -6
  5. data/lib/new_relic/agent/autostart.rb +1 -2
  6. data/lib/new_relic/agent/configuration/default_source.rb +270 -104
  7. data/lib/new_relic/agent/configuration/manager.rb +2 -2
  8. data/lib/new_relic/agent/datastores/redis.rb +0 -4
  9. data/lib/new_relic/agent/distributed_tracing.rb +0 -66
  10. data/lib/new_relic/agent/instrumentation/active_record_notifications.rb +0 -16
  11. data/lib/new_relic/agent/instrumentation/bunny.rb +10 -152
  12. data/lib/new_relic/agent/instrumentation/bunny/chain.rb +45 -0
  13. data/lib/new_relic/agent/instrumentation/bunny/instrumentation.rb +152 -0
  14. data/lib/new_relic/agent/instrumentation/bunny/prepend.rb +35 -0
  15. data/lib/new_relic/agent/instrumentation/curb.rb +9 -241
  16. data/lib/new_relic/agent/instrumentation/curb/chain.rb +93 -0
  17. data/lib/new_relic/agent/instrumentation/curb/instrumentation.rb +222 -0
  18. data/lib/new_relic/agent/instrumentation/curb/prepend.rb +63 -0
  19. data/lib/new_relic/agent/instrumentation/delayed_job/chain.rb +38 -0
  20. data/lib/new_relic/agent/instrumentation/delayed_job/instrumentation.rb +53 -0
  21. data/lib/new_relic/agent/instrumentation/delayed_job/prepend.rb +34 -0
  22. data/lib/new_relic/agent/instrumentation/delayed_job_instrumentation.rb +8 -50
  23. data/lib/new_relic/agent/instrumentation/excon.rb +2 -1
  24. data/lib/new_relic/agent/instrumentation/grape.rb +13 -113
  25. data/lib/new_relic/agent/instrumentation/grape/chain.rb +25 -0
  26. data/lib/new_relic/agent/instrumentation/grape/instrumentation.rb +100 -0
  27. data/lib/new_relic/agent/instrumentation/grape/prepend.rb +17 -0
  28. data/lib/new_relic/agent/instrumentation/httpclient.rb +8 -30
  29. data/lib/new_relic/agent/instrumentation/httpclient/chain.rb +25 -0
  30. data/lib/new_relic/agent/instrumentation/httpclient/instrumentation.rb +38 -0
  31. data/lib/new_relic/agent/instrumentation/httpclient/prepend.rb +17 -0
  32. data/lib/new_relic/agent/instrumentation/httprb.rb +29 -0
  33. data/lib/new_relic/agent/instrumentation/httprb/chain.rb +22 -0
  34. data/lib/new_relic/agent/instrumentation/httprb/instrumentation.rb +30 -0
  35. data/lib/new_relic/agent/instrumentation/httprb/prepend.rb +15 -0
  36. data/lib/new_relic/agent/instrumentation/memcache.rb +54 -69
  37. data/lib/new_relic/agent/instrumentation/memcache/chain.rb +16 -0
  38. data/lib/new_relic/agent/instrumentation/memcache/dalli.rb +38 -121
  39. data/lib/new_relic/agent/instrumentation/memcache/helper.rb +56 -0
  40. data/lib/new_relic/agent/instrumentation/memcache/instrumentation.rb +88 -0
  41. data/lib/new_relic/agent/instrumentation/memcache/prepend.rb +88 -0
  42. data/lib/new_relic/agent/instrumentation/middleware_proxy.rb +2 -0
  43. data/lib/new_relic/agent/instrumentation/mongo.rb +7 -0
  44. data/lib/new_relic/agent/instrumentation/net_http.rb +39 -0
  45. data/lib/new_relic/agent/instrumentation/net_http/chain.rb +25 -0
  46. data/lib/new_relic/agent/instrumentation/{net_prepend.rb → net_http/instrumentation.rb} +3 -3
  47. data/lib/new_relic/agent/instrumentation/net_http/prepend.rb +21 -0
  48. data/lib/new_relic/agent/instrumentation/padrino.rb +18 -53
  49. data/lib/new_relic/agent/instrumentation/padrino/chain.rb +34 -0
  50. data/lib/new_relic/agent/instrumentation/padrino/instrumentation.rb +27 -0
  51. data/lib/new_relic/agent/instrumentation/padrino/prepend.rb +20 -0
  52. data/lib/new_relic/agent/instrumentation/rack.rb +29 -139
  53. data/lib/new_relic/agent/instrumentation/rack/chain.rb +57 -0
  54. data/lib/new_relic/agent/instrumentation/rack/helpers.rb +32 -0
  55. data/lib/new_relic/agent/instrumentation/rack/instrumentation.rb +73 -0
  56. data/lib/new_relic/agent/instrumentation/rack/prepend.rb +36 -0
  57. data/lib/new_relic/agent/instrumentation/rake.rb +13 -154
  58. data/lib/new_relic/agent/instrumentation/rake/chain.rb +25 -0
  59. data/lib/new_relic/agent/instrumentation/rake/instrumentation.rb +144 -0
  60. data/lib/new_relic/agent/instrumentation/rake/prepend.rb +14 -0
  61. data/lib/new_relic/agent/instrumentation/redis.rb +10 -109
  62. data/lib/new_relic/agent/instrumentation/redis/chain.rb +34 -0
  63. data/lib/new_relic/agent/instrumentation/redis/instrumentation.rb +65 -0
  64. data/lib/new_relic/agent/instrumentation/redis/prepend.rb +24 -0
  65. data/lib/new_relic/agent/instrumentation/resque.rb +8 -28
  66. data/lib/new_relic/agent/instrumentation/resque/chain.rb +22 -0
  67. data/lib/new_relic/agent/instrumentation/resque/instrumentation.rb +33 -0
  68. data/lib/new_relic/agent/instrumentation/resque/prepend.rb +16 -0
  69. data/lib/new_relic/agent/instrumentation/sinatra.rb +20 -158
  70. data/lib/new_relic/agent/instrumentation/sinatra/chain.rb +55 -0
  71. data/lib/new_relic/agent/instrumentation/sinatra/ignorer.rb +29 -34
  72. data/lib/new_relic/agent/instrumentation/sinatra/instrumentation.rb +118 -0
  73. data/lib/new_relic/agent/instrumentation/sinatra/prepend.rb +33 -0
  74. data/lib/new_relic/agent/instrumentation/typhoeus.rb +10 -89
  75. data/lib/new_relic/agent/instrumentation/typhoeus/chain.rb +22 -0
  76. data/lib/new_relic/agent/instrumentation/typhoeus/instrumentation.rb +82 -0
  77. data/lib/new_relic/agent/instrumentation/typhoeus/prepend.rb +14 -0
  78. data/lib/new_relic/agent/new_relic_service.rb +3 -12
  79. data/lib/new_relic/agent/sql_sampler.rb +1 -1
  80. data/lib/new_relic/agent/transaction.rb +1 -4
  81. data/lib/new_relic/control/frameworks/rails.rb +11 -9
  82. data/lib/new_relic/control/instance_methods.rb +1 -0
  83. data/lib/new_relic/dependency_detection.rb +116 -10
  84. data/lib/new_relic/noticed_error.rb +1 -5
  85. data/lib/new_relic/supportability_helper.rb +1 -2
  86. data/lib/new_relic/version.rb +2 -2
  87. data/newrelic_rpm.gemspec +1 -1
  88. metadata +53 -8
  89. data/cert/cacert.pem +0 -1177
  90. data/lib/new_relic/agent/instrumentation/http.rb +0 -49
  91. data/lib/new_relic/agent/instrumentation/net.rb +0 -70
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
4
+
5
+ module NewRelic::Agent::Instrumentation
6
+ module Bunny::Prepend
7
+ module Exchange
8
+ include NewRelic::Agent::Instrumentation::Bunny::Exchange
9
+
10
+ def publish payload, opts = {}
11
+ publish_with_tracing(payload, opts) { super }
12
+ end
13
+ end
14
+
15
+ module Queue
16
+ include NewRelic::Agent::Instrumentation::Bunny::Queue
17
+
18
+ def pop(opts = {:manual_ack => false}, &block)
19
+ pop_with_tracing { super }
20
+ end
21
+
22
+ def purge *args
23
+ purge_with_tracing { super }
24
+ end
25
+ end
26
+
27
+ module Consumer
28
+ include NewRelic::Agent::Instrumentation::Bunny::Consumer
29
+
30
+ def call *args
31
+ call_with_tracing(*args) { super }
32
+ end
33
+ end
34
+ end
35
+ end
@@ -2,6 +2,9 @@
2
2
  # This file is distributed under New Relic's license terms.
3
3
  # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
4
4
 
5
+ require_relative 'curb/chain'
6
+ require_relative 'curb/prepend'
7
+
5
8
  DependencyDetection.defer do
6
9
  named :curb
7
10
 
@@ -19,246 +22,11 @@ DependencyDetection.defer do
19
22
  end
20
23
 
21
24
  executes do
22
- class Curl::Easy
23
-
24
- attr_accessor :_nr_instrumented,
25
- :_nr_failure_instrumented,
26
- :_nr_http_verb,
27
- :_nr_header_str,
28
- :_nr_original_on_header,
29
- :_nr_original_on_complete,
30
- :_nr_original_on_failure,
31
- :_nr_serial
32
-
33
- # We have to hook these three methods separately, as they don't use
34
- # Curl::Easy#http
35
- def http_head_with_newrelic(*args, &blk)
36
- self._nr_http_verb = :HEAD
37
- http_head_without_newrelic(*args, &blk)
38
- end
39
-
40
-
41
- alias_method :http_head_without_newrelic, :http_head
42
- alias_method :http_head, :http_head_with_newrelic
43
-
44
- def http_post_with_newrelic(*args, &blk)
45
- self._nr_http_verb = :POST
46
- http_post_without_newrelic(*args, &blk)
47
- end
48
-
49
-
50
- alias_method :http_post_without_newrelic, :http_post
51
- alias_method :http_post, :http_post_with_newrelic
52
-
53
- def http_put_with_newrelic(*args, &blk)
54
- self._nr_http_verb = :PUT
55
- http_put_without_newrelic(*args, &blk)
56
- end
57
-
58
-
59
- alias_method :http_put_without_newrelic, :http_put
60
- alias_method :http_put, :http_put_with_newrelic
61
-
62
-
63
- # Hook the #http method to set the verb.
64
- def http_with_newrelic verb
65
- self._nr_http_verb = verb.to_s.upcase
66
- http_without_newrelic( verb )
67
- end
68
-
69
- alias_method :http_without_newrelic, :http
70
- alias_method :http, :http_with_newrelic
71
-
72
- # Hook the #perform method to mark the request as non-parallel.
73
- def perform_with_newrelic
74
- self._nr_http_verb ||= :GET
75
- self._nr_serial = true
76
- perform_without_newrelic
77
- end
78
-
79
- alias_method :perform_without_newrelic, :perform
80
- alias_method :perform, :perform_with_newrelic
81
-
82
- # Record the HTTP verb for future #perform calls
83
- def method_with_newrelic verb
84
- self._nr_http_verb = verb.upcase
85
- method_without_newrelic(verb)
86
- end
87
-
88
- alias_method :method_without_newrelic, :method
89
- alias_method :method, :method_with_newrelic
90
-
91
- # We override this method in order to ensure access to header_str even
92
- # though we use an on_header callback
93
- def header_str_with_newrelic
94
- if self._nr_serial
95
- self._nr_header_str
96
- else
97
- # Since we didn't install a header callback for a non-serial request,
98
- # just fall back to the original implementation.
99
- header_str_without_newrelic
100
- end
101
- end
102
-
103
- alias_method :header_str_without_newrelic, :header_str
104
- alias_method :header_str, :header_str_with_newrelic
105
- end # class Curl::Easy
106
-
107
-
108
- class Curl::Multi
109
- include NewRelic::Agent::MethodTracer
110
-
111
- # Add CAT with callbacks if the request is serial
112
- def add_with_newrelic(curl)
113
- if curl.respond_to?(:_nr_serial) && curl._nr_serial
114
- hook_pending_request(curl) if NewRelic::Agent::Tracer.tracing_enabled?
115
- end
116
-
117
- return add_without_newrelic curl
118
- end
119
-
120
- alias_method :add_without_newrelic, :add
121
- alias_method :add, :add_with_newrelic
122
-
123
- # Trace as an External/Multiple call if the first request isn't serial.
124
- def perform_with_newrelic(&blk)
125
- return perform_without_newrelic if first_request_is_serial?
126
-
127
- trace_execution_scoped("External/Multiple/Curb::Multi/perform") do
128
- perform_without_newrelic(&blk)
129
- end
130
- end
131
-
132
- alias_method :perform_without_newrelic, :perform
133
- alias_method :perform, :perform_with_newrelic
134
-
135
-
136
- # Instrument the specified +request+ (a Curl::Easy object)
137
- # and set up cross-application tracing if it's enabled.
138
- def hook_pending_request(request)
139
- wrapped_request, wrapped_response = wrap_request(request)
140
-
141
- segment = NewRelic::Agent::Tracer.start_external_request_segment(
142
- library: wrapped_request.type,
143
- uri: wrapped_request.uri,
144
- procedure: wrapped_request.method
145
- )
146
-
147
- segment.add_request_headers wrapped_request
148
-
149
- # install all callbacks
150
- unless request._nr_instrumented
151
- install_header_callback(request, wrapped_response)
152
- install_completion_callback(request, wrapped_response, segment)
153
- install_failure_callback(request, wrapped_response, segment)
154
- request._nr_instrumented = true
155
- end
156
- rescue => err
157
- NewRelic::Agent.logger.error("Untrapped exception", err)
158
- end
159
-
160
-
161
- # Create request and response adapter objects for the specified +request+
162
- # NOTE: Although strange to wrap request and response at once, it works
163
- # because curb's callback mechanism updates the instantiated wrappers
164
- # during the life-cycle of external request
165
- def wrap_request(request)
166
- return NewRelic::Agent::HTTPClients::CurbRequest.new(request),
167
- NewRelic::Agent::HTTPClients::CurbResponse.new(request)
168
- end
169
-
170
- # Install a callback that will record the response headers
171
- # to enable CAT linking
172
- def install_header_callback(request, wrapped_response)
173
- original_callback = request.on_header
174
- request._nr_original_on_header = original_callback
175
- request._nr_header_str = nil
176
- request.on_header do |header_data|
177
- if original_callback
178
- original_callback.call header_data
179
- else
180
- wrapped_response.append_header_data header_data
181
- header_data.length
182
- end
183
- end
184
- end
185
-
186
- # Install a callback that will finish the trace.
187
- def install_completion_callback(request, wrapped_response, segment)
188
- original_callback = request.on_complete
189
- request._nr_original_on_complete = original_callback
190
- request.on_complete do |finished_request|
191
- begin
192
- segment.process_response_headers wrapped_response
193
- ensure
194
- segment.finish if segment
195
- # Make sure the existing completion callback is run, and restore the
196
- # on_complete callback to how it was before.
197
- original_callback.call(finished_request) if original_callback
198
- remove_instrumentation_callbacks(request)
199
- end
200
- end
201
- end
202
-
203
- # Install a callback that will fire on failures
204
- # NOTE: on_failure is not always called, so we're not always
205
- # unhooking the callback. No harm/no foul in production, but
206
- # definitely something to beware of if debugging callback issues
207
- # _nr_failure_instrumented exists to prevent infinitely chaining
208
- # our on_failure callback hook.
209
- def install_failure_callback(request, wrapped_response, segment)
210
- return if request._nr_failure_instrumented
211
- original_callback = request.on_failure
212
- request._nr_original_on_failure = original_callback
213
- request.on_failure do |failed_request, error|
214
- begin
215
- if segment
216
- noticible_error = NewRelic::Agent::NoticibleError.new error[0].name, error[-1]
217
- segment.notice_error noticible_error
218
- end
219
- ensure
220
- original_callback.call(failed_request, error) if original_callback
221
- remove_failure_callback(failed_request)
222
- end
223
- request._nr_failure_instrumented = true
224
- end
225
- end
226
-
227
- # on_failure callbacks cannot be removed in the on_complete
228
- # callback where this method is invoked because on_complete
229
- # fires before the on_failure!
230
- def remove_instrumentation_callbacks(request)
231
- request.on_complete(&request._nr_original_on_complete)
232
- request.on_header(&request._nr_original_on_header)
233
- request._nr_instrumented = false
234
- end
235
-
236
- # We execute customer's on_failure callback (if any) and
237
- # uninstall our hook here since the on_complete callback
238
- # fires before the on_failure callback.
239
- def remove_failure_callback(request)
240
- request.on_failure(&request._nr_original_on_failure)
241
- request._nr_failure_instrumented = false
242
- end
243
-
244
- private
245
-
246
- def first_request_is_serial?
247
- return false unless (first = self.requests.first)
248
-
249
- # Before curb 0.9.8, requests was an array of Curl::Easy
250
- # instances. Starting with 0.9.8, it's a Hash where the
251
- # values are Curl::Easy instances.
252
- #
253
- # So, requests.first will either be an_obj or [a_key, an_obj].
254
- # We need to handle either case.
255
- #
256
- first = first[-1] if first.is_a?(Array)
257
-
258
- first.respond_to?(:_nr_serial) && first._nr_serial
259
- end
260
-
261
- end # class Curl::Multi
262
-
25
+ if use_prepend?
26
+ prepend_instrument ::Curl::Easy, NewRelic::Agent::Instrumentation::Curb::Easy::Prepend
27
+ prepend_instrument ::Curl::Multi, NewRelic::Agent::Instrumentation::Curb::Multi::Prepend
28
+ else
29
+ chain_instrument ::NewRelic::Agent::Instrumentation::Curb::Chain
30
+ end
263
31
  end
264
32
  end
@@ -0,0 +1,93 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
4
+ # frozen_string_literal: true
5
+
6
+ require_relative 'instrumentation'
7
+
8
+ module NewRelic::Agent::Instrumentation
9
+ module Curb
10
+ module Chain
11
+ def self.instrument!
12
+ Curl::Easy.class_eval do
13
+ include NewRelic::Agent::Instrumentation::Curb::Easy
14
+
15
+ def http_head_with_newrelic(*args, &blk)
16
+ http_head_with_tracing { http_head_without_newrelic(*args, &blk) }
17
+ end
18
+
19
+ alias_method :http_head_without_newrelic, :http_head
20
+ alias_method :http_head, :http_head_with_newrelic
21
+
22
+ def http_post_with_newrelic(*args, &blk)
23
+ http_post_with_tracing { http_post_without_newrelic(*args, &blk) }
24
+ end
25
+
26
+ alias_method :http_post_without_newrelic, :http_post
27
+ alias_method :http_post, :http_post_with_newrelic
28
+
29
+ def http_put_with_newrelic(*args, &blk)
30
+ http_put_with_tracing { http_put_without_newrelic(*args, &blk) }
31
+ end
32
+
33
+ alias_method :http_put_without_newrelic, :http_put
34
+ alias_method :http_put, :http_put_with_newrelic
35
+
36
+ # Hook the #http method to set the verb.
37
+ def http_with_newrelic verb
38
+ http_with_tracing(verb) { http_without_newrelic( verb ) }
39
+ end
40
+
41
+ alias_method :http_without_newrelic, :http
42
+ alias_method :http, :http_with_newrelic
43
+
44
+ # Hook the #perform method to mark the request as non-parallel.
45
+ def perform_with_newrelic
46
+ perform_with_tracing { perform_without_newrelic }
47
+ end
48
+
49
+ alias_method :perform_without_newrelic, :perform
50
+ alias_method :perform, :perform_with_newrelic
51
+
52
+ # Record the HTTP verb for future #perform calls
53
+ def method_with_newrelic verb
54
+ method_with_tracing { method_without_newrelic(verb) }
55
+ end
56
+
57
+ alias_method :method_without_newrelic, :method
58
+ alias_method :method, :method_with_newrelic
59
+
60
+ # We override this method in order to ensure access to header_str even
61
+ # though we use an on_header callback
62
+ def header_str_with_newrelic
63
+ header_str_with_tracing { header_str_without_newrelic }
64
+ end
65
+
66
+ alias_method :header_str_without_newrelic, :header_str
67
+ alias_method :header_str, :header_str_with_newrelic
68
+ end
69
+
70
+ Curl::Multi.class_eval do
71
+ include NewRelic::Agent::Instrumentation::Curb::Multi
72
+
73
+ # Add CAT with callbacks if the request is serial
74
+ def add_with_newrelic(curl)
75
+ add_with_tracing(curl) { add_without_newrelic curl }
76
+ end
77
+
78
+ alias_method :add_without_newrelic, :add
79
+ alias_method :add, :add_with_newrelic
80
+
81
+ # Trace as an External/Multiple call if the first request isn't serial.
82
+ def perform_with_newrelic(&blk)
83
+ perform_with_tracing { perform_without_newrelic(&blk) }
84
+ end
85
+
86
+ alias_method :perform_without_newrelic, :perform
87
+ alias_method :perform, :perform_with_newrelic
88
+
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,222 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
4
+ # frozen_string_literal: true
5
+
6
+ module NewRelic
7
+ module Agent
8
+ module Instrumentation
9
+ module Curb
10
+ module Easy
11
+ attr_accessor :_nr_instrumented,
12
+ :_nr_failure_instrumented,
13
+ :_nr_http_verb,
14
+ :_nr_header_str,
15
+ :_nr_original_on_header,
16
+ :_nr_original_on_complete,
17
+ :_nr_original_on_failure,
18
+ :_nr_serial
19
+
20
+ # We have to hook these three methods separately, as they don't use
21
+ # Curl::Easy#http
22
+ def http_head_with_tracing
23
+ self._nr_http_verb = :HEAD
24
+ yield
25
+ end
26
+
27
+ def http_post_with_tracing
28
+ self._nr_http_verb = :POST
29
+ yield
30
+ end
31
+
32
+
33
+ def http_put_with_tracing
34
+ self._nr_http_verb = :PUT
35
+ yield
36
+ end
37
+
38
+ # Hook the #http method to set the verb.
39
+ def http_with_tracing verb
40
+ self._nr_http_verb = verb.to_s.upcase
41
+ yield
42
+ end
43
+
44
+ # Hook the #perform method to mark the request as non-parallel.
45
+ def perform_with_tracing
46
+ self._nr_http_verb ||= :GET
47
+ self._nr_serial = true
48
+ yield
49
+ end
50
+
51
+ # Record the HTTP verb for future #perform calls
52
+ def method_with_tracing verb
53
+ self._nr_http_verb = verb.upcase
54
+ yield
55
+ end
56
+
57
+ # We override this method in order to ensure access to header_str even
58
+ # though we use an on_header callback
59
+ def header_str_with_tracing
60
+ if self._nr_serial
61
+ self._nr_header_str
62
+ else
63
+ # Since we didn't install a header callback for a non-serial request,
64
+ # just fall back to the original implementation.
65
+ yield
66
+ end
67
+ end
68
+
69
+ end
70
+ ####################################################
71
+
72
+ module Multi
73
+ include NewRelic::Agent::MethodTracer
74
+
75
+ # Add CAT with callbacks if the request is serial
76
+ def add_with_tracing(curl)
77
+ if curl.respond_to?(:_nr_serial) && curl._nr_serial
78
+ hook_pending_request(curl) if NewRelic::Agent::Tracer.tracing_enabled?
79
+ end
80
+
81
+ return yield
82
+ end
83
+
84
+ # Trace as an External/Multiple call if the first request isn't serial.
85
+ def perform_with_tracing
86
+ return yield if first_request_is_serial?
87
+
88
+ trace_execution_scoped("External/Multiple/Curb::Multi/perform") do
89
+ yield
90
+ end
91
+ end
92
+
93
+ # Instrument the specified +request+ (a Curl::Easy object)
94
+ # and set up cross-application tracing if it's enabled.
95
+ def hook_pending_request(request)
96
+ wrapped_request, wrapped_response = wrap_request(request)
97
+
98
+ segment = NewRelic::Agent::Tracer.start_external_request_segment(
99
+ library: wrapped_request.type,
100
+ uri: wrapped_request.uri,
101
+ procedure: wrapped_request.method
102
+ )
103
+
104
+ segment.add_request_headers wrapped_request
105
+
106
+ # install all callbacks
107
+ unless request._nr_instrumented
108
+ install_header_callback(request, wrapped_response)
109
+ install_completion_callback(request, wrapped_response, segment)
110
+ install_failure_callback(request, wrapped_response, segment)
111
+ request._nr_instrumented = true
112
+ end
113
+ rescue => err
114
+ NewRelic::Agent.logger.error("Untrapped exception", err)
115
+ end
116
+
117
+
118
+ # Create request and response adapter objects for the specified +request+
119
+ # NOTE: Although strange to wrap request and response at once, it works
120
+ # because curb's callback mechanism updates the instantiated wrappers
121
+ # during the life-cycle of external request
122
+ def wrap_request(request)
123
+ return NewRelic::Agent::HTTPClients::CurbRequest.new(request),
124
+ NewRelic::Agent::HTTPClients::CurbResponse.new(request)
125
+ end
126
+
127
+ # Install a callback that will record the response headers
128
+ # to enable CAT linking
129
+ def install_header_callback(request, wrapped_response)
130
+ original_callback = request.on_header
131
+ request._nr_original_on_header = original_callback
132
+ request._nr_header_str = nil
133
+ request.on_header do |header_data|
134
+ if original_callback
135
+ original_callback.call header_data
136
+ else
137
+ wrapped_response.append_header_data header_data
138
+ header_data.length
139
+ end
140
+ end
141
+ end
142
+
143
+ # Install a callback that will finish the trace.
144
+ def install_completion_callback(request, wrapped_response, segment)
145
+ original_callback = request.on_complete
146
+ request._nr_original_on_complete = original_callback
147
+ request.on_complete do |finished_request|
148
+ begin
149
+ segment.process_response_headers wrapped_response
150
+ ensure
151
+ segment.finish if segment
152
+ # Make sure the existing completion callback is run, and restore the
153
+ # on_complete callback to how it was before.
154
+ original_callback.call(finished_request) if original_callback
155
+ remove_instrumentation_callbacks(request)
156
+ end
157
+ end
158
+ end
159
+
160
+ # Install a callback that will fire on failures
161
+ # NOTE: on_failure is not always called, so we're not always
162
+ # unhooking the callback. No harm/no foul in production, but
163
+ # definitely something to beware of if debugging callback issues
164
+ # _nr_failure_instrumented exists to prevent infinitely chaining
165
+ # our on_failure callback hook.
166
+ def install_failure_callback(request, wrapped_response, segment)
167
+ return if request._nr_failure_instrumented
168
+ original_callback = request.on_failure
169
+ request._nr_original_on_failure = original_callback
170
+ request.on_failure do |failed_request, error|
171
+ begin
172
+ if segment
173
+ noticible_error = NewRelic::Agent::NoticibleError.new error[0].name, error[-1]
174
+ segment.notice_error noticible_error
175
+ end
176
+ ensure
177
+ original_callback.call(failed_request, error) if original_callback
178
+ remove_failure_callback(failed_request)
179
+ end
180
+ request._nr_failure_instrumented = true
181
+ end
182
+ end
183
+
184
+ # on_failure callbacks cannot be removed in the on_complete
185
+ # callback where this method is invoked because on_complete
186
+ # fires before the on_failure!
187
+ def remove_instrumentation_callbacks(request)
188
+ request.on_complete(&request._nr_original_on_complete)
189
+ request.on_header(&request._nr_original_on_header)
190
+ request._nr_instrumented = false
191
+ end
192
+
193
+ # We execute customer's on_failure callback (if any) and
194
+ # uninstall our hook here since the on_complete callback
195
+ # fires before the on_failure callback.
196
+ def remove_failure_callback(request)
197
+ request.on_failure(&request._nr_original_on_failure)
198
+ request._nr_failure_instrumented = false
199
+ end
200
+
201
+ private
202
+
203
+ def first_request_is_serial?
204
+ return false unless (first = self.requests.first)
205
+
206
+ # Before curb 0.9.8, requests was an array of Curl::Easy
207
+ # instances. Starting with 0.9.8, it's a Hash where the
208
+ # values are Curl::Easy instances.
209
+ #
210
+ # So, requests.first will either be an_obj or [a_key, an_obj].
211
+ # We need to handle either case.
212
+ #
213
+ first = first[-1] if first.is_a?(Array)
214
+
215
+ first.respond_to?(:_nr_serial) && first._nr_serial
216
+ end
217
+
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end