Repository: open-telemetry/opentelemetry-ruby (Ruby)

Issue:
HTTP Span Attributes: `http.url` must not contain username / password
As is stated in the recent [specification change](https://github.com/open-telemetry/opentelemetry-specification/pull/1502) :

> `http.url` MUST NOT contain credentials passed via URL in form of `https://username:password@www.example.com/`. In such case the attribute's value should be `https://www.example.com/`


Discussion:
Thanks for the issue. I confirmed 2 of our instrumentations violate this requirement. I've pushed up tests in #648 and I'll work on a fix.



Patch:
diff --git a/common/lib/opentelemetry/common/utilities.rb b/common/lib/opentelemetry/common/utilities.rb
index 547dadeca0..b60eb413d0 100644
--- a/common/lib/opentelemetry/common/utilities.rb
+++ b/common/lib/opentelemetry/common/utilities.rb
@@ -59,6 +59,20 @@ def truncate(string, size)
       def untraced
         OpenTelemetry::Trace.with_span(OpenTelemetry::Trace::Span.new) { yield }
       end
+
+      # Returns a URL string with userinfo removed.
+      #
+      # @param [String] url The URL string to cleanse.
+      #
+      # @return [String] the cleansed URL.
+      def cleanse_url(url)
+        cleansed_url = URI.parse(url)
+        cleansed_url.password = nil
+        cleansed_url.user = nil
+        cleansed_url.to_s
+      rescue URI::Error
+        url
+      end
     end
   end
 end
diff --git a/instrumentation/ethon/lib/opentelemetry/instrumentation/ethon/patches/easy.rb b/instrumentation/ethon/lib/opentelemetry/instrumentation/ethon/patches/easy.rb
index a392760c6d..59179f63a3 100644
--- a/instrumentation/ethon/lib/opentelemetry/instrumentation/ethon/patches/easy.rb
+++ b/instrumentation/ethon/lib/opentelemetry/instrumentation/ethon/patches/easy.rb
@@ -72,7 +72,7 @@ def otel_before_request
               HTTP_METHODS_TO_SPAN_NAMES[method],
               attributes: {
                 'http.method' => method,
-                'http.url' => url
+                'http.url' => OpenTelemetry::Common::Utilities.cleanse_url(url)
               },
               kind: :client
             )
diff --git a/instrumentation/restclient/lib/opentelemetry/instrumentation/restclient/patches/request.rb b/instrumentation/restclient/lib/opentelemetry/instrumentation/restclient/patches/request.rb
index 99b0859abc..5631788401 100644
--- a/instrumentation/restclient/lib/opentelemetry/instrumentation/restclient/patches/request.rb
+++ b/instrumentation/restclient/lib/opentelemetry/instrumentation/restclient/patches/request.rb
@@ -24,7 +24,7 @@ def trace_request # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
               "HTTP #{http_method}",
               attributes: {
                 'http.method' => http_method,
-                'http.url' => url
+                'http.url' => OpenTelemetry::Common::Utilities.cleanse_url(url)
               },
               kind: :client
             )


Test patch:
diff --git a/instrumentation/ethon/test/opentelemetry/instrumentation/ethon/instrumentation_test.rb b/instrumentation/ethon/test/opentelemetry/instrumentation/ethon/instrumentation_test.rb
index 7d2695039d..9404721615 100644
--- a/instrumentation/ethon/test/opentelemetry/instrumentation/ethon/instrumentation_test.rb
+++ b/instrumentation/ethon/test/opentelemetry/instrumentation/ethon/instrumentation_test.rb
@@ -43,7 +43,7 @@
     end
 
     describe 'easy' do
-      let(:easy) { ::Ethon::Easy.new(url: 'http://example.com/test') }
+      let(:easy) { ::Ethon::Easy.new(url: 'http://username:password@example.com/test') }
 
       describe '#http_request' do
         it 'preserves HTTP request method on easy instance' do
diff --git a/instrumentation/faraday/test/opentelemetry/instrumentation/faraday/middlewares/tracer_middleware_test.rb b/instrumentation/faraday/test/opentelemetry/instrumentation/faraday/middlewares/tracer_middleware_test.rb
index ffce53aed0..fb997c88a0 100644
--- a/instrumentation/faraday/test/opentelemetry/instrumentation/faraday/middlewares/tracer_middleware_test.rb
+++ b/instrumentation/faraday/test/opentelemetry/instrumentation/faraday/middlewares/tracer_middleware_test.rb
@@ -16,7 +16,7 @@
   let(:span) { exporter.finished_spans.first }
 
   let(:client) do
-    ::Faraday.new('http://example.com') do |builder|
+    ::Faraday.new('http://username:password@example.com') do |builder|
       builder.adapter(:test) do |stub|
         stub.get('/success') { |_| [200, {}, 'OK'] }
         stub.get('/failure') { |_| [500, {}, 'OK'] }
diff --git a/instrumentation/http_client/test/instrumentation/http_client/patches/session_test.rb b/instrumentation/http_client/test/instrumentation/http_client/patches/session_test.rb
index 8a05a42b8c..d38bd7a721 100644
--- a/instrumentation/http_client/test/instrumentation/http_client/patches/session_test.rb
+++ b/instrumentation/http_client/test/instrumentation/http_client/patches/session_test.rb
@@ -32,7 +32,7 @@
         assert_raises(HTTPClient::ReceiveTimeoutError) do
           http = HTTPClient.new
           http.receive_timeout = 0.01
-          http.get("http://localhost:#{port}/example")
+          http.get("http://username:password@localhost:#{port}/example")
         end
       end
 
diff --git a/instrumentation/restclient/test/opentelemetry/instrumentation/restclient/instrumentation_test.rb b/instrumentation/restclient/test/opentelemetry/instrumentation/restclient/instrumentation_test.rb
index ce41cc2048..39146edc81 100644
--- a/instrumentation/restclient/test/opentelemetry/instrumentation/restclient/instrumentation_test.rb
+++ b/instrumentation/restclient/test/opentelemetry/instrumentation/restclient/instrumentation_test.rb
@@ -45,7 +45,7 @@
     end
 
     it 'after request with success code' do
-      ::RestClient.get('http://example.com/success')
+      ::RestClient.get('http://username:password@example.com/success')
 
       _(exporter.finished_spans.size).must_equal 1
       _(span.name).must_equal 'HTTP GET'
@@ -61,7 +61,7 @@
 
     it 'after request with failure code' do
       expect do
-        ::RestClient.get('http://example.com/failure')
+        ::RestClient.get('http://username:password@example.com/failure')
       end.must_raise RestClient::InternalServerError
 
       _(exporter.finished_spans.size).must_equal 1