OpenCensus integration for outgoing requests (#724)

This commit is contained in:
Daniel Azuma 2018-10-10 13:23:11 -07:00 committed by GitHub
parent deea81f7bf
commit 50c4897e18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 154 additions and 13 deletions

View File

@ -1,8 +1,6 @@
language: ruby
sudo: false
rvm:
- 2.0
- 2.1
- 2.2
- 2.3
- 2.4

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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, ''])