# Copyright (c) 2015 AppNeta, Inc.
# All rights reserved.
module TraceView
module Inst
module HTTPClient
def self.included(klass)
::TraceView::Util.method_alias(klass, :do_request, ::HTTPClient)
::TraceView::Util.method_alias(klass, :do_request_async, ::HTTPClient)
::TraceView::Util.method_alias(klass, :do_get_stream, ::HTTPClient)
end
def traceview_collect(method, uri, query = nil)
kvs = {}
kvs['IsService'] = 1
# Conditionally log URL query params
# Because of the hook points, the query arg can come in under query
# or as a part of uri (not both). Here we handle both cases.
if TraceView::Config[:httpclient][:log_args]
if query
kvs['RemoteURL'] = uri.to_s + '?' + TraceView::Util.to_query(query)
else
kvs['RemoteURL'] = uri.to_s
end
else
kvs['RemoteURL'] = uri.to_s.split('?').first
end
kvs['HTTPMethod'] = ::TraceView::Util.upcase(method)
kvs['Backtrace'] = TraceView::API.backtrace if TraceView::Config[:httpclient][:collect_backtraces]
kvs
rescue => e
TraceView.logger.debug "[traceview/debug] Error capturing httpclient KVs: #{e.message}"
TraceView.logger.debug e.backtrace.join('\n') if ::TraceView::Config[:verbose]
end
def do_request_with_traceview(method, uri, query, body, header, &block)
# If we're not tracing, just do a fast return.
if !TraceView.tracing?
return request_without_traceview(method, uri, query, body, header, &block)
end
begin
response_context = nil
# Avoid cross host tracing for blacklisted domains
blacklisted = TraceView::API.blacklisted?(uri.hostname)
kvs = traceview_collect(method, uri, query)
kvs['Blacklisted'] = true if blacklisted
TraceView::API.log_entry('httpclient', kvs)
kvs.clear
req_context = TraceView::Context.toString()
# Be aware of various ways to call/use httpclient
if header.is_a?(Array)
header.push ["X-Trace", req_context]
elsif header.is_a?(Hash)
header['X-Trace'] = req_context unless blacklisted
end
# The core httpclient call
response = do_request_without_traceview(method, uri, query, body, header, &block)
response_context = response.headers['X-Trace']
kvs['HTTPStatus'] = response.status_code
# If we get a redirect, report the location header
if ((300..308).to_a.include? response.status.to_i) && response.headers.key?("Location")
kvs["Location"] = response.headers["Location"]
end
if response_context && !blacklisted
TraceView::XTrace.continue_service_context(req_context, response_context)
end
response
rescue => e
TraceView::API.log_exception('httpclient', e)
raise e
ensure
TraceView::API.log_exit('httpclient', kvs)
end
end
def do_request_async_with_traceview(method, uri, query, body, header)
if TraceView.tracing?
# Since async is done by calling Thread.new { .. }, we somehow
# have to pass the tracing context into that new thread. Here
# we stowaway the context in the request headers to be picked up
# (and removed from req headers) in do_get_stream.
if header.is_a?(Array)
header.push ["traceview.context", TraceView::Context.toString]
elsif header.is_a?(Hash)
header['traceview.context'] = TraceView::Context.toString
end
end
do_request_async_without_traceview(method, uri, query, body, header)
end
def do_get_stream_with_traceview(req, proxy, conn)
unless req.headers.key?("traceview.context")
return do_get_stream_without_traceview(req, proxy, conn)
end
# Pickup context and delete the headers stowaway
TraceView::Context.fromString req.headers["traceview.context"]
req.header.delete "traceview.context"
begin
response = nil
response_context = nil
uri = req.http_header.request_uri
method = req.http_header.request_method
# Avoid cross host tracing for blacklisted domains
blacklisted = TraceView::API.blacklisted?(uri.hostname)
kvs = traceview_collect(method, uri)
kvs['Blacklisted'] = true if blacklisted
kvs['Async'] = 1
TraceView::API.log_entry('httpclient', kvs)
kvs.clear
req_context = TraceView::Context.toString()
req.header.add('X-Trace', req_context)
# The core httpclient call
result = do_get_stream_without_traceview(req, proxy, conn)
# Older HTTPClient < 2.6.0 returns HTTPClient::Connection
if result.is_a?(::HTTP::Message)
response = result
else
response = conn.pop
end
response_context = response.headers['X-Trace']
kvs['HTTPStatus'] = response.status_code
# If we get a redirect, report the location header
if ((300..308).to_a.include? response.status.to_i) && response.headers.key?("Location")
kvs["Location"] = response.headers["Location"]
end
if response_context && !blacklisted
TraceView::XTrace.continue_service_context(req_context, response_context)
end
# Older HTTPClient < 2.6.0 returns HTTPClient::Connection
conn.push response if result.is_a?(::HTTPClient::Connection)
result
rescue => e
TraceView::API.log_exception('httpclient', e)
raise e
ensure
# TraceView::API.log_exit('httpclient', kvs.merge('Async' => 1))
TraceView::API.log_exit('httpclient', kvs)
end
end
end
end
end
if TraceView::Config[:httpclient][:enabled] && defined?(::HTTPClient)
::TraceView.logger.info '[traceview/loading] Instrumenting httpclient' if TraceView::Config[:verbose]
::TraceView::Util.send_include(::HTTPClient, ::TraceView::Inst::HTTPClient)
end