Module: Koderift::Rails::NetHttpPatch

Defined in:
lib/koderift/rails/instrumentation.rb

Overview

Installed once when the gem loads (see the prepend at the bottom of this file). Writes to the thread-local external_calls array set by Instrumentation.capture; outside of a capture block it is a pass-through.

CONTRACT: exactly one entry per LOGICAL outbound call. Net::HTTP#request re-enters itself whenever it is called on a session that is not started:

def request(req, body = nil, &block)
unless started?
  start { req['connection'] ||= 'close'; return request(req, body, &block) }
end

(net-http-0.9.1 lib/net/http.rb:2399-2405). Both frames run this prepended patch, so one call was recorded twice. That branch also forces Connection: close and start's ensure closes the socket, so an affected call site double-counts on EVERY call, not intermittently.

Instance Method Summary collapse

Instance Method Details

#request(req, *args, &block) ⇒ Object



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/koderift/rails/instrumentation.rb', line 146

def request(req, *args, &block)
  calls    = Thread.current[:koderift_external_calls]
  config   = Thread.current[:koderift_external_config]
  trace_id = Thread.current[:koderift_trace_id]

  req['X-Koderift-Trace-ID'] = trace_id if trace_id

  # Nothing below this line may run before the pass-through return: the
  # spec stand-in defines only #request, and the header injection must
  # still happen when instrumentation is not capturing.
  return super(req, *args, &block) unless calls && config

  # The stdlib recursion re-invokes with the SAME req object, so request
  # identity separates "my own re-entry" from a genuinely different call
  # issued while this one is in flight. Do NOT use `started?` (it keeps
  # the inner frame, which cannot see connection setup, and the stand-in
  # does not define it) and do NOT use an object ivar (one Net::HTTP
  # shared across threads would silently lose most of its spans).
  in_flight = (Thread.current[:koderift_http_in_flight] ||= [])
  return super(req, *args, &block) if in_flight.any? { |r| r.equal?(req) }

  in_flight << req
  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  begin
    response = super(req, *args, &block)
  ensure
    # Strictly LIFO: the inner frame returns above without pushing, and
    # push/pop are paired in one begin/ensure. The ensure also covers a
    # raise (Net::OpenTimeout, ECONNREFUSED, a raising response block) --
    # without it one failure would silence every later call on this thread.
    in_flight.pop
  end
  duration = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round

  host    = address.to_s
  ignored = ['127.0.0.1', 'localhost', '[::1]'].include?(host) ||
            config.external_call_ignore_hosts.any? { |h| host.include?(h) }

  unless ignored
    raw_path = req.path.to_s.split('?').first.to_s
    segments = raw_path.split('/').reject(&:empty?).first(2)
    endpoint = segments.empty? ? '/' : '/' + segments.join('/')

    calls << {
      host: host, endpoint: endpoint, method: req.method.to_s.upcase,
      status: response.code.to_i, duration_ms: duration, trace_id: trace_id
    }
  end

  response
end