diff --git a/instrumentation/restclient/Appraisals b/instrumentation/restclient/Appraisals index 5a7c3ae064..649adb4c58 100644 --- a/instrumentation/restclient/Appraisals +++ b/instrumentation/restclient/Appraisals @@ -1,9 +1,18 @@ # frozen_string_literal: true -appraise 'rest-client-2.1' do - gem 'rest-client', '~> 2.1.0' -end +# To faclitate HTTP semantic convention stability migration, we are using +# appraisal to test the different semantic convention modes along with different +# gem versions. For more information on the semantic convention modes, see: +# https://opentelemetry.io/docs/specs/semconv/non-normative/http-migration/ + +semconv_stability = %w[dup stable old] + +semconv_stability.each do |mode| + appraise "rest-client-2.1_#{mode}" do + gem 'rest-client', '~> 2.1.0' + end -appraise 'rest-client-2.0' do - gem 'rest-client', '~> 2.0.0' + appraise "rest-client-2.0_#{mode}" do + gem 'rest-client', '~> 2.0.0' + end end diff --git a/instrumentation/restclient/README.md b/instrumentation/restclient/README.md index 398aae4383..f1176e2f4c 100644 --- a/instrumentation/restclient/README.md +++ b/instrumentation/restclient/README.md @@ -48,3 +48,19 @@ Apache 2.0 license. See [LICENSE][license-github] for more information. [community-meetings]: https://github.com/open-telemetry/community#community-meetings [slack-channel]: https://cloud-native.slack.com/archives/C01NWKKMKMY [discussions-url]: https://github.com/open-telemetry/opentelemetry-ruby/discussions + +## HTTP semantic convention stability + +In the OpenTelemetry ecosystem, HTTP semantic conventions have now reached a stable state. However, the initial Faraday instrumentation was introduced before this stability was achieved, which resulted in HTTP attributes being based on an older version of the semantic conventions. + +To facilitate the migration to stable semantic conventions, you can use the `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable. This variable allows you to opt-in to the new stable conventions, ensuring compatibility and future-proofing your instrumentation. + +When setting the value for `OTEL_SEMCONV_STABILITY_OPT_IN`, you can specify which conventions you wish to adopt: + +- `http` - Emits the stable HTTP and networking conventions and ceases emitting the old conventions previously emitted by the instrumentation. +- `http/dup` - Emits both the old and stable HTTP and networking conventions, enabling a phased rollout of the stable semantic conventions. +- Default behavior (in the absence of either value) is to continue emitting the old HTTP and networking conventions the instrumentation previously emitted. + +During the transition from old to stable conventions, RestClient instrumentation code comes in three patch versions: `dup`, `old`, and `stable`. These versions are identical except for the attributes they send. Any changes to RestClient instrumentation should consider all three patches. + +For additional information on migration, please refer to our [documentation](https://opentelemetry.io/docs/specs/semconv/non-normative/http-migration/). \ No newline at end of file diff --git a/instrumentation/restclient/lib/opentelemetry/instrumentation/restclient/instrumentation.rb b/instrumentation/restclient/lib/opentelemetry/instrumentation/restclient/instrumentation.rb index 15d3c10ece..3ca0d13bce 100644 --- a/instrumentation/restclient/lib/opentelemetry/instrumentation/restclient/instrumentation.rb +++ b/instrumentation/restclient/lib/opentelemetry/instrumentation/restclient/instrumentation.rb @@ -11,8 +11,9 @@ module RestClient # instrumentation class Instrumentation < OpenTelemetry::Instrumentation::Base install do |_config| - require_dependencies - patch_request + patch_type = determine_semconv + send(:"require_dependencies_#{patch_type}") + send(:"patch_request_#{patch_type}") end present do @@ -23,12 +24,41 @@ class Instrumentation < OpenTelemetry::Instrumentation::Base private - def require_dependencies - require_relative 'patches/request' + def determine_semconv + stability_opt_in = ENV.fetch('OTEL_SEMCONV_STABILITY_OPT_IN', '') + values = stability_opt_in.split(',').map(&:strip) + + if values.include?('http/dup') + 'dup' + elsif values.include?('http') + 'stable' + else + 'old' + end + end + + def require_dependencies_dup + require_relative 'patches/dup/request' + end + + def require_dependencies_stable + require_relative 'patches/stable/request' + end + + def require_dependencies_old + require_relative 'patches/old/request' + end + + def patch_request_dup + ::RestClient::Request.prepend(Patches::Dup::Request) + end + + def patch_request_stable + ::RestClient::Request.prepend(Patches::Stable::Request) end - def patch_request - ::RestClient::Request.prepend(Patches::Request) + def patch_request_old + ::RestClient::Request.prepend(Patches::Old::Request) end end end diff --git a/instrumentation/restclient/lib/opentelemetry/instrumentation/restclient/patches/dup/request.rb b/instrumentation/restclient/lib/opentelemetry/instrumentation/restclient/patches/dup/request.rb new file mode 100644 index 0000000000..5c12bcc88a --- /dev/null +++ b/instrumentation/restclient/lib/opentelemetry/instrumentation/restclient/patches/dup/request.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Instrumentation + module RestClient + module Patches + module Dup + # Module to prepend to RestClient::Request for instrumentation + module Request + # Constant for the HTTP status range + HTTP_STATUS_SUCCESS_RANGE = (100..399) + + def execute(&) + trace_request do |_span| + super + end + end + + private + + def create_request_span + http_method = method.upcase + instrumentation_attrs = { + 'http.method' => http_method.to_s, + 'http.request.method' => http_method.to_s, + 'http.url' => OpenTelemetry::Common::Utilities.cleanse_url(url), + 'url.full' => OpenTelemetry::Common::Utilities.cleanse_url(url) + } + instrumentation_config = RestClient::Instrumentation.instance.config + instrumentation_attrs['peer.service'] = instrumentation_config[:peer_service] if instrumentation_config[:peer_service] + span = tracer.start_span( + http_method.to_s, + attributes: instrumentation_attrs.merge( + OpenTelemetry::Common::HTTP::ClientContext.attributes + ), + kind: :client + ) + + OpenTelemetry::Trace.with_span(span) do + OpenTelemetry.propagation.inject(processed_headers) + end + + span + end + + def trace_request + span = create_request_span + + yield(span).tap do |response| + # Verify return value is a response. + # If so, add additional attributes. + if response.is_a?(::RestClient::Response) + span.set_attribute('http.status_code', response.code) + span.set_attribute('http.response.status_code', response.code) + span.status = OpenTelemetry::Trace::Status.error unless HTTP_STATUS_SUCCESS_RANGE.cover?(response.code.to_i) + end + end + rescue ::RestClient::ExceptionWithResponse => e + span.set_attribute('http.status_code', e.http_code) + span.set_attribute('http.response.status_code', e.http_code) + span.status = OpenTelemetry::Trace::Status.error unless HTTP_STATUS_SUCCESS_RANGE.cover?(e.http_code.to_i) + raise e + ensure + span.finish + end + + def tracer + RestClient::Instrumentation.instance.tracer + end + end + end + end + end + end +end diff --git a/instrumentation/restclient/lib/opentelemetry/instrumentation/restclient/patches/old/request.rb b/instrumentation/restclient/lib/opentelemetry/instrumentation/restclient/patches/old/request.rb new file mode 100644 index 0000000000..2c447f4091 --- /dev/null +++ b/instrumentation/restclient/lib/opentelemetry/instrumentation/restclient/patches/old/request.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Instrumentation + module RestClient + module Patches + module Old + # Module to prepend to RestClient::Request for instrumentation + module Request + # Constant for the HTTP status range + HTTP_STATUS_SUCCESS_RANGE = (100..399) + + def execute(&) + trace_request do |_span| + super + end + end + + private + + def create_request_span + http_method = method.upcase + instrumentation_attrs = { + 'http.method' => http_method.to_s, + 'http.url' => OpenTelemetry::Common::Utilities.cleanse_url(url) + } + instrumentation_config = RestClient::Instrumentation.instance.config + instrumentation_attrs['peer.service'] = instrumentation_config[:peer_service] if instrumentation_config[:peer_service] + span = tracer.start_span( + "HTTP #{http_method}", + attributes: instrumentation_attrs.merge( + OpenTelemetry::Common::HTTP::ClientContext.attributes + ), + kind: :client + ) + + OpenTelemetry::Trace.with_span(span) do + OpenTelemetry.propagation.inject(processed_headers) + end + + span + end + + def trace_request + span = create_request_span + + yield(span).tap do |response| + # Verify return value is a response. + # If so, add additional attributes. + if response.is_a?(::RestClient::Response) + span.set_attribute('http.status_code', response.code) + span.status = OpenTelemetry::Trace::Status.error unless HTTP_STATUS_SUCCESS_RANGE.cover?(response.code.to_i) + end + end + rescue ::RestClient::ExceptionWithResponse => e + span.set_attribute('http.status_code', e.http_code) + span.status = OpenTelemetry::Trace::Status.error unless HTTP_STATUS_SUCCESS_RANGE.cover?(e.http_code.to_i) + raise e + ensure + span.finish + end + + def tracer + RestClient::Instrumentation.instance.tracer + end + end + end + end + end + end +end diff --git a/instrumentation/restclient/lib/opentelemetry/instrumentation/restclient/patches/request.rb b/instrumentation/restclient/lib/opentelemetry/instrumentation/restclient/patches/request.rb deleted file mode 100644 index 94b74ccfef..0000000000 --- a/instrumentation/restclient/lib/opentelemetry/instrumentation/restclient/patches/request.rb +++ /dev/null @@ -1,73 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -module OpenTelemetry - module Instrumentation - module RestClient - module Patches - # Module to prepend to RestClient::Request for instrumentation - module Request - # Constant for the HTTP status range - HTTP_STATUS_SUCCESS_RANGE = (100..399) - - def execute(&) - trace_request do |_span| - super - end - end - - private - - def create_request_span - http_method = method.upcase - instrumentation_attrs = { - 'http.method' => http_method.to_s, - 'http.url' => OpenTelemetry::Common::Utilities.cleanse_url(url) - } - instrumentation_config = RestClient::Instrumentation.instance.config - instrumentation_attrs['peer.service'] = instrumentation_config[:peer_service] if instrumentation_config[:peer_service] - span = tracer.start_span( - "HTTP #{http_method}", - attributes: instrumentation_attrs.merge( - OpenTelemetry::Common::HTTP::ClientContext.attributes - ), - kind: :client - ) - - OpenTelemetry::Trace.with_span(span) do - OpenTelemetry.propagation.inject(processed_headers) - end - - span - end - - def trace_request - span = create_request_span - - yield(span).tap do |response| - # Verify return value is a response. - # If so, add additional attributes. - if response.is_a?(::RestClient::Response) - span.set_attribute('http.status_code', response.code) - span.status = OpenTelemetry::Trace::Status.error unless HTTP_STATUS_SUCCESS_RANGE.cover?(response.code.to_i) - end - end - rescue ::RestClient::ExceptionWithResponse => e - span.set_attribute('http.status_code', e.http_code) - span.status = OpenTelemetry::Trace::Status.error unless HTTP_STATUS_SUCCESS_RANGE.cover?(e.http_code.to_i) - raise e - ensure - span.finish - end - - def tracer - RestClient::Instrumentation.instance.tracer - end - end - end - end - end -end diff --git a/instrumentation/restclient/lib/opentelemetry/instrumentation/restclient/patches/stable/request.rb b/instrumentation/restclient/lib/opentelemetry/instrumentation/restclient/patches/stable/request.rb new file mode 100644 index 0000000000..9a36fdce04 --- /dev/null +++ b/instrumentation/restclient/lib/opentelemetry/instrumentation/restclient/patches/stable/request.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Instrumentation + module RestClient + module Patches + module Stable + # Module to prepend to RestClient::Request for instrumentation + module Request + # Constant for the HTTP status range + HTTP_STATUS_SUCCESS_RANGE = (100..399) + + def execute(&) + trace_request do |_span| + super + end + end + + private + + def create_request_span + http_method = method.upcase + instrumentation_attrs = { + 'http.request.method' => http_method.to_s, + 'url.full' => OpenTelemetry::Common::Utilities.cleanse_url(url) + } + instrumentation_config = RestClient::Instrumentation.instance.config + instrumentation_attrs['peer.service'] = instrumentation_config[:peer_service] if instrumentation_config[:peer_service] + span = tracer.start_span( + http_method.to_s, + attributes: instrumentation_attrs.merge( + OpenTelemetry::Common::HTTP::ClientContext.attributes + ), + kind: :client + ) + + OpenTelemetry::Trace.with_span(span) do + OpenTelemetry.propagation.inject(processed_headers) + end + + span + end + + def trace_request + span = create_request_span + + yield(span).tap do |response| + # Verify return value is a response. + # If so, add additional attributes. + if response.is_a?(::RestClient::Response) + span.set_attribute('http.response.status_code', response.code) + span.status = OpenTelemetry::Trace::Status.error unless HTTP_STATUS_SUCCESS_RANGE.cover?(response.code.to_i) + end + end + rescue ::RestClient::ExceptionWithResponse => e + span.set_attribute('http.response.status_code', e.http_code) + span.status = OpenTelemetry::Trace::Status.error unless HTTP_STATUS_SUCCESS_RANGE.cover?(e.http_code.to_i) + raise e + ensure + span.finish + end + + def tracer + RestClient::Instrumentation.instance.tracer + end + end + end + end + end + end +end diff --git a/instrumentation/restclient/test/opentelemetry/instrumentation/restclient/dup/instrumentation_test.rb b/instrumentation/restclient/test/opentelemetry/instrumentation/restclient/dup/instrumentation_test.rb new file mode 100644 index 0000000000..3cb6ef9de1 --- /dev/null +++ b/instrumentation/restclient/test/opentelemetry/instrumentation/restclient/dup/instrumentation_test.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +require_relative '../../../../../lib/opentelemetry/instrumentation/restclient' +require_relative '../../../../../lib/opentelemetry/instrumentation/restclient/patches/old/request' + +describe OpenTelemetry::Instrumentation::RestClient::Instrumentation do + let(:instrumentation) { OpenTelemetry::Instrumentation::RestClient::Instrumentation.instance } + let(:exporter) { EXPORTER } + let(:span) { exporter.finished_spans.first } + + before do + skip unless ENV['BUNDLE_GEMFILE'].include?('dup') + + ENV['OTEL_SEMCONV_STABILITY_OPT_IN'] = 'http/dup' + exporter.reset + stub_request(:get, 'http://example.com/success').to_return(status: 200) + stub_request(:get, 'http://example.com/failure').to_return(status: 500) + + # this is currently a noop but this will future proof the test + @orig_propagation = OpenTelemetry.propagation + propagator = OpenTelemetry::Trace::Propagation::TraceContext.text_map_propagator + OpenTelemetry.propagation = propagator + end + + after do + # Force re-install of instrumentation + instrumentation.instance_variable_set(:@installed, false) + + OpenTelemetry.propagation = @orig_propagation + end + + describe 'tracing' do + before do + instrumentation.install + end + + it 'before request' do + _(exporter.finished_spans.size).must_equal 0 + end + + it 'after request with success code' do + RestClient.get('http://username:password@example.com/success') + + _(exporter.finished_spans.size).must_equal 1 + _(span.name).must_equal 'GET' + _(span.attributes['http.method']).must_equal 'GET' + _(span.attributes['http.status_code']).must_equal 200 + _(span.attributes['http.url']).must_equal 'http://example.com/success' + _(span.attributes['http.request.method']).must_equal 'GET' + _(span.attributes['http.response.status_code']).must_equal 200 + _(span.attributes['url.full']).must_equal 'http://example.com/success' + assert_requested( + :get, + 'http://example.com/success', + headers: { 'Traceparent' => "00-#{span.hex_trace_id}-#{span.hex_span_id}-01" } + ) + end + + it 'after request with failure code' do + expect do + RestClient.get('http://username:password@example.com/failure') + end.must_raise RestClient::InternalServerError + + _(exporter.finished_spans.size).must_equal 1 + _(span.name).must_equal 'GET' + _(span.attributes['http.method']).must_equal 'GET' + _(span.attributes['http.status_code']).must_equal 500 + _(span.attributes['http.url']).must_equal 'http://example.com/failure' + _(span.attributes['http.request.method']).must_equal 'GET' + _(span.attributes['http.response.status_code']).must_equal 500 + _(span.attributes['url.full']).must_equal 'http://example.com/failure' + assert_requested( + :get, + 'http://example.com/failure', + headers: { 'Traceparent' => "00-#{span.hex_trace_id}-#{span.hex_span_id}-01" } + ) + end + + it 'merges HTTP client context' do + client_context_attrs = { + 'test.attribute' => 'test.value', 'http.method' => 'OVERRIDE', 'http.request.method' => 'OVERRIDE' + } + OpenTelemetry::Common::HTTP::ClientContext.with_attributes(client_context_attrs) do + RestClient.get('http://username:password@example.com/success') + end + + _(span.attributes['http.method']).must_equal 'OVERRIDE' + _(span.attributes['test.attribute']).must_equal 'test.value' + _(span.attributes['http.url']).must_equal 'http://example.com/success' + _(span.attributes['http.request.method']).must_equal 'OVERRIDE' + _(span.attributes['test.attribute']).must_equal 'test.value' + _(span.attributes['url.full']).must_equal 'http://example.com/success' + end + + it 'accepts peer service name from config' do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install(peer_service: 'example:faraday') + + RestClient.get('http://example.com/success') + _(span.attributes['peer.service']).must_equal 'example:faraday' + end + + it 'prioritizes context attributes over config for peer service name' do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install(peer_service: 'example:faraday') + + client_context_attrs = { 'peer.service' => 'example:custom' } + OpenTelemetry::Common::HTTP::ClientContext.with_attributes(client_context_attrs) do + RestClient.get('http://example.com/success') + end + _(span.attributes['peer.service']).must_equal 'example:custom' + end + + it 'creates valid http method span attribute when method is a Symbol' do + RestClient::Request.execute(method: :get, url: 'http://username:password@example.com/success') + + _(span.attributes['http.method']).must_equal 'GET' + end + end +end diff --git a/instrumentation/restclient/test/opentelemetry/instrumentation/restclient/instrumentation_test.rb b/instrumentation/restclient/test/opentelemetry/instrumentation/restclient/old/instrumentation_test.rb similarity index 94% rename from instrumentation/restclient/test/opentelemetry/instrumentation/restclient/instrumentation_test.rb rename to instrumentation/restclient/test/opentelemetry/instrumentation/restclient/old/instrumentation_test.rb index 4f467ad5c0..778677cc49 100644 --- a/instrumentation/restclient/test/opentelemetry/instrumentation/restclient/instrumentation_test.rb +++ b/instrumentation/restclient/test/opentelemetry/instrumentation/restclient/old/instrumentation_test.rb @@ -6,8 +6,8 @@ require 'test_helper' -require_relative '../../../../lib/opentelemetry/instrumentation/restclient' -require_relative '../../../../lib/opentelemetry/instrumentation/restclient/patches/request' +require_relative '../../../../../lib/opentelemetry/instrumentation/restclient' +require_relative '../../../../../lib/opentelemetry/instrumentation/restclient/patches/old/request' describe OpenTelemetry::Instrumentation::RestClient::Instrumentation do let(:instrumentation) { OpenTelemetry::Instrumentation::RestClient::Instrumentation.instance } @@ -15,6 +15,8 @@ let(:span) { exporter.finished_spans.first } before do + skip unless ENV['BUNDLE_GEMFILE'].include?('old') + exporter.reset stub_request(:get, 'http://example.com/success').to_return(status: 200) stub_request(:get, 'http://example.com/failure').to_return(status: 500) diff --git a/instrumentation/restclient/test/opentelemetry/instrumentation/restclient/stable/instrumentation_test.rb b/instrumentation/restclient/test/opentelemetry/instrumentation/restclient/stable/instrumentation_test.rb new file mode 100644 index 0000000000..eb3f3bd58f --- /dev/null +++ b/instrumentation/restclient/test/opentelemetry/instrumentation/restclient/stable/instrumentation_test.rb @@ -0,0 +1,117 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +require_relative '../../../../../lib/opentelemetry/instrumentation/restclient' +require_relative '../../../../../lib/opentelemetry/instrumentation/restclient/patches/old/request' + +describe OpenTelemetry::Instrumentation::RestClient::Instrumentation do + let(:instrumentation) { OpenTelemetry::Instrumentation::RestClient::Instrumentation.instance } + let(:exporter) { EXPORTER } + let(:span) { exporter.finished_spans.first } + + before do + skip unless ENV['BUNDLE_GEMFILE'].include?('stable') + + ENV['OTEL_SEMCONV_STABILITY_OPT_IN'] = 'http' + exporter.reset + stub_request(:get, 'http://example.com/success').to_return(status: 200) + stub_request(:get, 'http://example.com/failure').to_return(status: 500) + + # this is currently a noop but this will future proof the test + @orig_propagation = OpenTelemetry.propagation + propagator = OpenTelemetry::Trace::Propagation::TraceContext.text_map_propagator + OpenTelemetry.propagation = propagator + end + + after do + # Force re-install of instrumentation + instrumentation.instance_variable_set(:@installed, false) + + OpenTelemetry.propagation = @orig_propagation + end + + describe 'tracing' do + before do + instrumentation.install + end + + it 'before request' do + _(exporter.finished_spans.size).must_equal 0 + end + + it 'after request with success code' do + RestClient.get('http://username:password@example.com/success') + + _(exporter.finished_spans.size).must_equal 1 + _(span.name).must_equal 'GET' + _(span.attributes['http.request.method']).must_equal 'GET' + _(span.attributes['http.response.status_code']).must_equal 200 + _(span.attributes['url.full']).must_equal 'http://example.com/success' + assert_requested( + :get, + 'http://example.com/success', + headers: { 'Traceparent' => "00-#{span.hex_trace_id}-#{span.hex_span_id}-01" } + ) + end + + it 'after request with failure code' do + expect do + RestClient.get('http://username:password@example.com/failure') + end.must_raise RestClient::InternalServerError + + _(exporter.finished_spans.size).must_equal 1 + _(span.name).must_equal 'GET' + _(span.attributes['http.request.method']).must_equal 'GET' + _(span.attributes['http.response.status_code']).must_equal 500 + _(span.attributes['url.full']).must_equal 'http://example.com/failure' + assert_requested( + :get, + 'http://example.com/failure', + headers: { 'Traceparent' => "00-#{span.hex_trace_id}-#{span.hex_span_id}-01" } + ) + end + + it 'merges HTTP client context' do + client_context_attrs = { + 'test.attribute' => 'test.value', 'http.request.method' => 'OVERRIDE' + } + OpenTelemetry::Common::HTTP::ClientContext.with_attributes(client_context_attrs) do + RestClient.get('http://username:password@example.com/success') + end + + _(span.attributes['http.request.method']).must_equal 'OVERRIDE' + _(span.attributes['test.attribute']).must_equal 'test.value' + _(span.attributes['url.full']).must_equal 'http://example.com/success' + end + + it 'accepts peer service name from config' do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install(peer_service: 'example:faraday') + + RestClient.get('http://example.com/success') + _(span.attributes['peer.service']).must_equal 'example:faraday' + end + + it 'prioritizes context attributes over config for peer service name' do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install(peer_service: 'example:faraday') + + client_context_attrs = { 'peer.service' => 'example:custom' } + OpenTelemetry::Common::HTTP::ClientContext.with_attributes(client_context_attrs) do + RestClient.get('http://example.com/success') + end + _(span.attributes['peer.service']).must_equal 'example:custom' + end + + it 'creates valid http method span attribute when method is a Symbol' do + RestClient::Request.execute(method: :get, url: 'http://username:password@example.com/success') + + _(span.attributes['http.request.method']).must_equal 'GET' + end + end +end