Module: NewRelic::Agent::Instrumentation::Curb::Multi

Includes:
MethodTracer
Included in:
Prepend
Defined in:
lib/new_relic/agent/instrumentation/curb/prepend.rb,
lib/new_relic/agent/instrumentation/curb/instrumentation.rb

Defined Under Namespace

Modules: Prepend

Constant Summary collapse

INSTRUMENTATION_NAME =
'Curb'

Constants included from MethodTracer::ClassMethods::AddMethodTracer

MethodTracer::ClassMethods::AddMethodTracer::ALLOWED_KEYS, MethodTracer::ClassMethods::AddMethodTracer::DEFAULT_SETTINGS

Instance Method Summary collapse

Methods included from MethodTracer

extended, included, #trace_execution_scoped, #trace_execution_unscoped

Methods included from MethodTracer::ClassMethods

#add_method_tracer, #remove_method_tracer

Methods included from MethodTracer::ClassMethods::AddMethodTracer

#_nr_clear_traced_methods!, #_nr_default_metric_name, #_nr_derived_class_name, #_nr_traced_method_module, #_nr_validate_method_tracer_options, #method_traced?, #newrelic_method_exists?

Instance Method Details

#add_with_tracing(curl) ⇒ Object

Add CAT with callbacks if the request is serial



74
75
76
77
78
79
80
# File 'lib/new_relic/agent/instrumentation/curb/instrumentation.rb', line 74

def add_with_tracing(curl)
  if curl.respond_to?(:_nr_serial) && curl._nr_serial
    hook_pending_request(curl) if NewRelic::Agent::Tracer.tracing_enabled?
  end

  return yield
end

#hook_pending_request(request) ⇒ Object

Instrument the specified request (a Curl::Easy object) and set up cross-application tracing if it’s enabled.



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/new_relic/agent/instrumentation/curb/instrumentation.rb', line 95

def hook_pending_request(request)
  wrapped_request, wrapped_response = wrap_request(request)

  segment = NewRelic::Agent::Tracer.start_external_request_segment(
    library: wrapped_request.type,
    uri: wrapped_request.uri,
    procedure: wrapped_request.method
  )

  segment.add_request_headers(wrapped_request)

  # install all callbacks
  unless request._nr_instrumented
    install_header_callback(request, wrapped_response)
    install_completion_callback(request, wrapped_response, segment)
    install_failure_callback(request, wrapped_response, segment)
    request._nr_instrumented = true
  end
rescue => err
  NewRelic::Agent.logger.error('Untrapped exception', err)
end

#install_completion_callback(request, wrapped_response, segment) ⇒ Object

Install a callback that will finish the trace.



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/new_relic/agent/instrumentation/curb/instrumentation.rb', line 143

def install_completion_callback(request, wrapped_response, segment)
  original_callback = request.on_complete
  request._nr_original_on_complete = original_callback
  request.on_complete do |finished_request|
    begin
      segment&.process_response_headers(wrapped_response)
    ensure
      ::NewRelic::Agent::Transaction::Segment.finish(segment)
      # Make sure the existing completion callback is run, and restore the
      # on_complete callback to how it was before.
      original_callback&.call(finished_request)
      remove_instrumentation_callbacks(request)
    end
  end
end

#install_failure_callback(request, _wrapped_response, segment) ⇒ Object

Install a callback that will fire on failures NOTE: on_failure is not always called, so we’re not always unhooking the callback. No harm/no foul in production, but definitely something to beware of if debugging callback issues our on_failure callback hook.



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/new_relic/agent/instrumentation/curb/instrumentation.rb', line 165

def install_failure_callback(request, _wrapped_response, segment)
  original_callback = request.on_failure

  nr_original_callback = original_callback.instance_variable_get(:@__newrelic_original_callback)
  original_callback = nr_original_callback || original_callback

  request._nr_original_on_failure = original_callback

  newrelic_callback = proc do |failed_request, error|
    begin
      if segment
        noticeable_error = NewRelic::Agent::NoticeableError.new(error[0].name, error[-1])
        segment.notice_error(noticeable_error)
      end
    ensure
      original_callback&.call(failed_request, error)
      remove_failure_callback(failed_request)
    end
  end
  newrelic_callback.instance_variable_set(:@__newrelic_original_callback, original_callback)

  request.on_failure(&newrelic_callback)
end

#install_header_callback(request, wrapped_response) ⇒ Object

Install a callback that will record the response headers to enable CAT linking



128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/new_relic/agent/instrumentation/curb/instrumentation.rb', line 128

def install_header_callback(request, wrapped_response)
  original_callback = request.on_header
  request._nr_original_on_header = original_callback
  request._nr_header_str = nil
  request.on_header do |header_data|
    if original_callback
      original_callback.call(header_data)
    else
      wrapped_response.append_header_data(header_data)
      header_data.length
    end
  end
end

#perform_with_tracingObject

Trace as an External/Multiple call if the first request isn’t serial.



83
84
85
86
87
88
89
90
91
# File 'lib/new_relic/agent/instrumentation/curb/instrumentation.rb', line 83

def perform_with_tracing
  return yield if first_request_is_serial?

  NewRelic::Agent.record_instrumentation_invocation(INSTRUMENTATION_NAME)

  trace_execution_scoped('External/Multiple/Curb::Multi/perform') do
    yield
  end
end

#remove_failure_callback(request) ⇒ Object

We execute customer’s on_failure callback (if any) and uninstall our hook here since the on_complete callback fires before the on_failure callback.



201
202
203
# File 'lib/new_relic/agent/instrumentation/curb/instrumentation.rb', line 201

def remove_failure_callback(request)
  request.on_failure(&request._nr_original_on_failure)
end

#remove_instrumentation_callbacks(request) ⇒ Object

on_failure callbacks cannot be removed in the on_complete callback where this method is invoked because on_complete fires before the on_failure!



192
193
194
195
196
# File 'lib/new_relic/agent/instrumentation/curb/instrumentation.rb', line 192

def remove_instrumentation_callbacks(request)
  request.on_complete(&request._nr_original_on_complete)
  request.on_header(&request._nr_original_on_header)
  request._nr_instrumented = false
end

#wrap_request(request) ⇒ Object

Create request and response adapter objects for the specified request NOTE: Although strange to wrap request and response at once, it works because curb’s callback mechanism updates the instantiated wrappers during the life-cycle of external request



121
122
123
124
# File 'lib/new_relic/agent/instrumentation/curb/instrumentation.rb', line 121

def wrap_request(request)
  return NewRelic::Agent::HTTPClients::CurbRequest.new(request),
          NewRelic::Agent::HTTPClients::CurbResponse.new(request)
end