OpenCensus integration for outgoing requests (#724)
This commit is contained in:
parent
deea81f7bf
commit
50c4897e18
|
@ -1,8 +1,6 @@
|
|||
language: ruby
|
||||
sudo: false
|
||||
rvm:
|
||||
- 2.0
|
||||
- 2.1
|
||||
- 2.2
|
||||
- 2.3
|
||||
- 2.4
|
||||
|
|
1
Gemfile
1
Gemfile
|
@ -21,6 +21,7 @@ group :development do
|
|||
gem 'rmail', '~> 1.1'
|
||||
gem 'redis', '~> 3.2'
|
||||
gem 'logging', '~> 2.2'
|
||||
gem 'opencensus', '~> 0.3'
|
||||
end
|
||||
|
||||
platforms :jruby do
|
||||
|
|
|
@ -29,6 +29,13 @@ module Google
|
|||
|
||||
RETRIABLE_ERRORS = [Google::Apis::ServerError, Google::Apis::RateLimitError, Google::Apis::TransmissionError]
|
||||
|
||||
begin
|
||||
require 'opencensus'
|
||||
OPENCENSUS_AVAILABLE = true
|
||||
rescue LoadError
|
||||
OPENCENSUS_AVAILABLE = false
|
||||
end
|
||||
|
||||
# Request options
|
||||
# @return [Google::Apis::RequestOptions]
|
||||
attr_accessor :options
|
||||
|
@ -76,6 +83,7 @@ module Google
|
|||
self.body = body
|
||||
self.query = {}
|
||||
self.params = {}
|
||||
@opencensus_span = nil
|
||||
end
|
||||
|
||||
# Execute the command, retrying as necessary
|
||||
|
@ -89,6 +97,7 @@ module Google
|
|||
# @raise [Google::Apis::AuthorizationError] Authorization is required
|
||||
def execute(client)
|
||||
prepare!
|
||||
opencensus_begin_span
|
||||
begin
|
||||
Retriable.retriable tries: options.retries + 1,
|
||||
base_interval: 1,
|
||||
|
@ -116,6 +125,8 @@ module Google
|
|||
end
|
||||
end
|
||||
ensure
|
||||
opencensus_end_span
|
||||
@http_res = nil
|
||||
release!
|
||||
end
|
||||
|
||||
|
@ -157,7 +168,6 @@ module Google
|
|||
end
|
||||
|
||||
self.body = '' unless self.body
|
||||
|
||||
end
|
||||
|
||||
# Release any resources used by this command
|
||||
|
@ -288,15 +298,15 @@ module Google
|
|||
request_header = header.dup
|
||||
apply_request_options(request_header)
|
||||
|
||||
http_res = client.request(method.to_s.upcase,
|
||||
url.to_s,
|
||||
query: nil,
|
||||
body: body,
|
||||
header: request_header,
|
||||
follow_redirect: true)
|
||||
logger.debug { http_res.status }
|
||||
logger.debug { http_res.inspect }
|
||||
response = process_response(http_res.status.to_i, http_res.header, http_res.body)
|
||||
@http_res = client.request(method.to_s.upcase,
|
||||
url.to_s,
|
||||
query: nil,
|
||||
body: body,
|
||||
header: request_header,
|
||||
follow_redirect: true)
|
||||
logger.debug { @http_res.status }
|
||||
logger.debug { @http_res.inspect }
|
||||
response = process_response(@http_res.status.to_i, @http_res.header, @http_res.body)
|
||||
success(response)
|
||||
rescue => e
|
||||
logger.debug { sprintf('Caught error %s', e) }
|
||||
|
@ -323,10 +333,73 @@ module Google
|
|||
|
||||
private
|
||||
|
||||
def opencensus_begin_span
|
||||
return unless OPENCENSUS_AVAILABLE && options.use_opencensus
|
||||
return if @opencensus_span
|
||||
return unless OpenCensus::Trace.span_context
|
||||
|
||||
@opencensus_span = OpenCensus::Trace.start_span url.path.to_s
|
||||
@opencensus_span.kind = OpenCensus::Trace::SpanBuilder::CLIENT
|
||||
@opencensus_span.put_attribute "http.host", url.host.to_s
|
||||
@opencensus_span.put_attribute "http.method", method.to_s.upcase
|
||||
@opencensus_span.put_attribute "http.path", url.path.to_s
|
||||
if body.respond_to? :bytesize
|
||||
@opencensus_span.put_message_event \
|
||||
OpenCensus::Trace::SpanBuilder::SENT, 1, body.bytesize
|
||||
end
|
||||
|
||||
formatter = OpenCensus::Trace.config.http_formatter
|
||||
if formatter.respond_to? :header_name
|
||||
header[formatter.header_name] = formatter.serialize @opencensus_span.context.trace_context
|
||||
end
|
||||
rescue StandardError => e
|
||||
# Log exceptions and continue, so opencensus failures don't cause
|
||||
# the entire request to fail.
|
||||
logger.debug { sprintf('Error opening OpenCensus span: %s', e) }
|
||||
end
|
||||
|
||||
def opencensus_end_span
|
||||
return unless OPENCENSUS_AVAILABLE
|
||||
return unless @opencensus_span
|
||||
return unless OpenCensus::Trace.span_context
|
||||
|
||||
if @http_res.body.respond_to? :bytesize
|
||||
@opencensus_span.put_message_event \
|
||||
OpenCensus::Trace::SpanBuilder::RECEIVED, 1, @http_res.body.bytesize
|
||||
end
|
||||
status = @http_res.status.to_i
|
||||
if status > 0
|
||||
@opencensus_span.set_status map_http_status status
|
||||
@opencensus_span.put_attribute "http.status_code", status
|
||||
end
|
||||
|
||||
OpenCensus::Trace.end_span @opencensus_span
|
||||
@opencensus_span = nil
|
||||
rescue StandardError => e
|
||||
# Log exceptions and continue, so failures don't cause leaks by
|
||||
# aborting cleanup.
|
||||
logger.debug { sprintf('Error finishing OpenCensus span: %s', e) }
|
||||
end
|
||||
|
||||
def form_encoded?
|
||||
@form_encoded
|
||||
end
|
||||
|
||||
def map_http_status http_status
|
||||
case http_status
|
||||
when 200..399 then 0 # OK
|
||||
when 400 then 3 # INVALID_ARGUMENT
|
||||
when 401 then 16 # UNAUTHENTICATED
|
||||
when 403 then 7 # PERMISSION_DENIED
|
||||
when 404 then 5 # NOT_FOUND
|
||||
when 429 then 8 # RESOURCE_EXHAUSTED
|
||||
when 501 then 12 # UNIMPLEMENTED
|
||||
when 503 then 14 # UNAVAILABLE
|
||||
when 504 then 4 # DEADLINE_EXCEEDED
|
||||
else 2 # UNKNOWN
|
||||
end
|
||||
end
|
||||
|
||||
def normalize_query_value(v)
|
||||
case v
|
||||
when Array
|
||||
|
|
|
@ -32,7 +32,8 @@ module Google
|
|||
:normalize_unicode,
|
||||
:skip_serialization,
|
||||
:skip_deserialization,
|
||||
:api_format_version)
|
||||
:api_format_version,
|
||||
:use_opencensus)
|
||||
|
||||
# General client options
|
||||
class ClientOptions
|
||||
|
@ -73,6 +74,8 @@ module Google
|
|||
# @return [Boolean] True if response should be returned in raw form instead of deserialized.
|
||||
# @!attribute [rw] api_format_version
|
||||
# @return [Fixnum] Version of the error format to request/expect.
|
||||
# @!attribute [rw] use_opencensus
|
||||
# @return [Boolean] Whether OpenCensus spans should be generated for requests. Default is true.
|
||||
|
||||
# Get the default options
|
||||
# @return [Google::Apis::RequestOptions]
|
||||
|
@ -101,5 +104,6 @@ module Google
|
|||
RequestOptions.default.skip_serialization = false
|
||||
RequestOptions.default.skip_deserialization = false
|
||||
RequestOptions.default.api_format_version = nil
|
||||
RequestOptions.default.use_opencensus = true
|
||||
end
|
||||
end
|
||||
|
|
|
@ -294,6 +294,71 @@ RSpec.describe Google::Apis::Core::HttpCommand do
|
|||
end
|
||||
end
|
||||
|
||||
context('with opencensus integration') do
|
||||
it 'should create an opencensus span for a successful call' do
|
||||
stub_request(:get, 'https://www.googleapis.com/zoo/animals').to_return(status: [200, ''], body: "Hello world")
|
||||
command = Google::Apis::Core::HttpCommand.new(:get, 'https://www.googleapis.com/zoo/animals')
|
||||
OpenCensus::Trace.start_request_trace do |span_context|
|
||||
command.execute(client)
|
||||
spans = span_context.build_contained_spans
|
||||
expect(spans.size).to eql 1
|
||||
span = spans.first
|
||||
expect(span.name.value).to eql '/zoo/animals'
|
||||
expect(span.status.code).to eql 0
|
||||
attrs = span.attributes
|
||||
expect(attrs['http.host'].value).to eql 'www.googleapis.com'
|
||||
expect(attrs['http.method'].value).to eql 'GET'
|
||||
expect(attrs['http.path'].value).to eql '/zoo/animals'
|
||||
expect(attrs['http.status_code']).to eql 200
|
||||
events = span.time_events
|
||||
expect(events.size).to eql 2
|
||||
expect(events[0].type).to eql :SENT
|
||||
expect(events[0].uncompressed_size).to eql 0
|
||||
expect(events[1].type).to eql :RECEIVED
|
||||
expect(events[1].uncompressed_size).to eql 11
|
||||
end
|
||||
end
|
||||
|
||||
it 'should create an opencensus span for a call failure' do
|
||||
stub_request(:get, 'https://www.googleapis.com/zoo/animals').to_return(status: [403, ''])
|
||||
command = Google::Apis::Core::HttpCommand.new(:get, 'https://www.googleapis.com/zoo/animals')
|
||||
OpenCensus::Trace.start_request_trace do |span_context|
|
||||
expect { command.execute(client) }.to raise_error(Google::Apis::ClientError)
|
||||
spans = span_context.build_contained_spans
|
||||
expect(spans.size).to eql 1
|
||||
span = spans.first
|
||||
expect(span.name.value).to eql '/zoo/animals'
|
||||
expect(span.status.code).to eql 7
|
||||
attrs = span.attributes
|
||||
expect(attrs['http.host'].value).to eql 'www.googleapis.com'
|
||||
expect(attrs['http.method'].value).to eql 'GET'
|
||||
expect(attrs['http.path'].value).to eql '/zoo/animals'
|
||||
expect(attrs['http.status_code']).to eql 403
|
||||
end
|
||||
end
|
||||
|
||||
it 'should propagate trace context header' do
|
||||
stub_request(:get, 'https://www.googleapis.com/zoo/animals').to_return(body: %(Hello world))
|
||||
command = Google::Apis::Core::HttpCommand.new(:get, 'https://www.googleapis.com/zoo/animals')
|
||||
OpenCensus::Trace.start_request_trace do |span_context|
|
||||
result = command.execute(client)
|
||||
expect(a_request(:get, 'https://www.googleapis.com/zoo/animals')
|
||||
.with { |req| !req.headers['Trace-Context'].empty? }).to have_been_made
|
||||
end
|
||||
end
|
||||
|
||||
it 'should not create an opencensus span if disabled' do
|
||||
stub_request(:get, 'https://www.googleapis.com/zoo/animals').to_return(status: [200, ''])
|
||||
command = Google::Apis::Core::HttpCommand.new(:get, 'https://www.googleapis.com/zoo/animals')
|
||||
command.options.use_opencensus = false
|
||||
OpenCensus::Trace.start_request_trace do |span_context|
|
||||
command.execute(client)
|
||||
spans = span_context.build_contained_spans
|
||||
expect(spans.size).to eql 0
|
||||
end
|
||||
end
|
||||
end if Google::Apis::Core::HttpCommand::OPENCENSUS_AVAILABLE
|
||||
|
||||
it 'should send repeated query parameters' do
|
||||
stub_request(:get, 'https://www.googleapis.com/zoo/animals?a=1&a=2&a=3')
|
||||
.to_return(status: [200, ''])
|
||||
|
|
Loading…
Reference in New Issue