Skip to content

Commit

Permalink
feat: add opentelemetry instrumentation
Browse files Browse the repository at this point in the history
  • Loading branch information
robertlaurin committed Mar 23, 2021
1 parent d2f2abb commit 9683dfa
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 0 deletions.
1 change: 1 addition & 0 deletions google-apis-core/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ group :development do
gem 'redis', '~> 3.2'
gem 'logging', '~> 2.2'
gem 'opencensus', '~> 0.4'
gem 'opentelemetry-sdk', '~> 0.15.0'
gem 'httparty'
end

Expand Down
1 change: 1 addition & 0 deletions google-apis-core/google-apis-core.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Gem::Specification.new do |gem|
gem.require_paths = ["lib"]

gem.required_ruby_version = '>= 2.5'
gem.add_runtime_dependency "opentelemetry-api", '~> 0.15.0'
gem.add_runtime_dependency "representable", "~> 3.0"
gem.add_runtime_dependency "retriable", ">= 2.0", "< 4.0"
gem.add_runtime_dependency "addressable", "~> 2.5", ">= 2.5.1"
Expand Down
43 changes: 43 additions & 0 deletions google-apis-core/lib/google/apis/core/http_command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

require 'google/apis/core/opentelemetry'
require 'addressable/uri'
require 'addressable/template'
require 'google/apis/options'
Expand Down Expand Up @@ -97,6 +98,7 @@ def initialize(method, url, body: nil)
# @raise [Google::Apis::AuthorizationError] Authorization is required
def execute(client)
prepare!
opentelemetry_begin_span
opencensus_begin_span
begin
Retriable.retriable tries: options.retries + 1,
Expand Down Expand Up @@ -125,6 +127,7 @@ def execute(client)
end
end
ensure
opentelemetry_end_span
opencensus_end_span
@http_res = nil
release!
Expand Down Expand Up @@ -355,7 +358,46 @@ def safe_response_representation http_res
http_res.inspect
end

def opentelemetry_begin_span
return unless Google::Apis::Core::OpenTelemetry.instance.installed?
return if @opentelemetry_span

attributes = {
'http.host' => url.host.to_s,
'http.method' => method.to_s,
'http.target' => url.path.to_s,
'peer.service' => url.host.to_s
}

@opentelemetry_span = Google::Apis::Core::OpenTelemetry.instance.tracer.start_span(url.host.to_s, attributes: attributes)
rescue StandardError => e
# Log exceptions and continue, so OpenTelemetry failures don't cause
# the entire request to fail.
logger.debug("Error opening OpenTelemetry span: #{e}")
end

def opentelemetry_end_span
return unless Google::Apis::Core::OpenTelemetry.instance.installed?
return unless @opentelemetry_span

if @http_res
status_code = @http_res.status.to_i
@opentelemetry_span.set_attribute('http.status_code', status_code)
@opentelemetry_span.status = ::OpenTelemetry::Trace::Status.http_to_status(
status_code
)
end

@opentelemetry_span.finish
@opentelemetry_span = nil
rescue StandardError => e
# Log exceptions and continue, so failures don't cause leaks by
# aborting cleanup.
logger.debug("Error finishing OpenTelemetry span: #{e}")
end

def opencensus_begin_span
return if Google::Apis::Core::OpenTelemetry.instance.installed?
return unless OPENCENSUS_AVAILABLE && options.use_opencensus
return if @opencensus_span
return unless OpenCensus::Trace.span_context
Expand All @@ -381,6 +423,7 @@ def opencensus_begin_span
end

def opencensus_end_span
return if Google::Apis::Core::OpenTelemetry.instance.installed?
return unless OPENCENSUS_AVAILABLE
return unless @opencensus_span
return unless OpenCensus::Trace.span_context
Expand Down
26 changes: 26 additions & 0 deletions google-apis-core/lib/google/apis/core/opentelemetry.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

require 'opentelemetry-api'

module Google
module Apis
module Core
class OpenTelemetry < OpenTelemetry::Instrumentation::Base
install { true }
present { true }
end
end
end
end
83 changes: 83 additions & 0 deletions google-apis-core/spec/google/apis/core/http_command_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@

require 'spec_helper'
require 'google/apis/core/http_command'
require 'opentelemetry-sdk'

EXPORTER = OpenTelemetry::SDK::Trace::Export::InMemorySpanExporter.new
SPAN_PROCESSOR = OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(EXPORTER)

OpenTelemetry::SDK.configure do |c|
c.add_span_processor SPAN_PROCESSOR
end

module Google
module Apis
Expand Down Expand Up @@ -392,8 +400,83 @@ class DecryptResponse
expect(spans.size).to eql 0
end
end

it 'should not attempt to create an opentelemetry span' 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')

expect(Google::Apis::Core::OpenTelemetry.instance.tracer).not_to receive(:start_span)
command.execute(client)
end
end if Google::Apis::Core::HttpCommand::OPENCENSUS_AVAILABLE

context('with opentelemetry integration installed') do
let(:instrumentation) { Google::Apis::Core::OpenTelemetry.instance }
let(:span) { EXPORTER.finished_spans.first }

before do
EXPORTER.reset
instrumentation.install
end

after { instrumentation.instance_variable_set(:@installed, false) }

it 'should not attempt to create an opencesus span' 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')
command.execute(client)

expect(Google::Apis::Core::HttpCommand::OPENCENSUS_AVAILABLE).to be(true)
expect(OpenCensus::Trace).not_to receive(:start_span)
expect(OpenCensus::Trace).not_to receive(:end)
command.execute(client)
end

it 'should create an OpenTelemetry 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')
command.execute(client)

expect(span.name).to eq('www.googleapis.com')
expect(span.status).to be_ok
expect(span.instrumentation_library.name).to eq('Google::Apis::Core')
expect(span.instrumentation_library.version).to eq(Google::Apis::Core::VERSION)
expect(span.attributes).to include(
'http.host' => 'www.googleapis.com',
'http.method' => 'get',
'http.target' => '/zoo/animals',
'peer.service' => 'www.googleapis.com',
'http.status_code' => 200
)
end

it 'should create an OpenTelemetry 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')
expect { command.execute(client) }.to raise_error(Google::Apis::ClientError)

expect(span.name).to eq('www.googleapis.com')
expect(span.status).not_to be_ok
expect(span.attributes).to include(
'http.host' => 'www.googleapis.com',
'http.method' => 'get',
'http.target' => '/zoo/animals',
'peer.service' => 'www.googleapis.com',
'http.status_code' => 403
)
end

it 'should not create an OpenTelemetry span if not installed' do
instrumentation.instance_variable_set(:@installed, false)
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')

expect(instrumentation.tracer).not_to receive(:start_span)
command.execute(client)
expect(span).to be_nil
end
end

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

0 comments on commit 9683dfa

Please sign in to comment.