diff --git a/exporter/otlp-http/lib/opentelemetry/exporter/otlp/http/trace_exporter.rb b/exporter/otlp-http/lib/opentelemetry/exporter/otlp/http/trace_exporter.rb index f6f0ecd98e..c9744ed354 100644 --- a/exporter/otlp-http/lib/opentelemetry/exporter/otlp/http/trace_exporter.rb +++ b/exporter/otlp-http/lib/opentelemetry/exporter/otlp/http/trace_exporter.rb @@ -109,7 +109,7 @@ def fetch_ssl_verify_mode end def http_connection(uri, ssl_verify_mode, certificate_file, client_certificate_file, client_key_file) - http = Net::HTTP.new(uri.host, uri.port) + http = Net::HTTP.new(uri.hostname, uri.port) http.use_ssl = uri.scheme == 'https' http.verify_mode = ssl_verify_mode http.ca_file = certificate_file unless certificate_file.nil? diff --git a/exporter/otlp-logs/lib/opentelemetry/exporter/otlp/logs/logs_exporter.rb b/exporter/otlp-logs/lib/opentelemetry/exporter/otlp/logs/logs_exporter.rb index 9a53f69357..95124b58db 100644 --- a/exporter/otlp-logs/lib/opentelemetry/exporter/otlp/logs/logs_exporter.rb +++ b/exporter/otlp-logs/lib/opentelemetry/exporter/otlp/logs/logs_exporter.rb @@ -115,7 +115,7 @@ def handle_http_error(response) end def http_connection(uri, ssl_verify_mode, certificate_file, client_certificate_file, client_key_file) - http = Net::HTTP.new(uri.host, uri.port) + http = Net::HTTP.new(uri.hostname, uri.port) http.use_ssl = uri.scheme == 'https' http.verify_mode = ssl_verify_mode http.ca_file = certificate_file unless certificate_file.nil? diff --git a/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/metrics/util.rb b/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/metrics/util.rb index 14e0e395c2..38cdc99c5b 100644 --- a/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/metrics/util.rb +++ b/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/metrics/util.rb @@ -16,7 +16,7 @@ module Util # rubocop:disable Metrics/ModuleLength DEFAULT_USER_AGENT = "OTel-OTLP-MetricsExporter-Ruby/#{OpenTelemetry::Exporter::OTLP::Metrics::VERSION} Ruby/#{RUBY_VERSION} (#{RUBY_PLATFORM}; #{RUBY_ENGINE}/#{RUBY_ENGINE_VERSION})".freeze def http_connection(uri, ssl_verify_mode, certificate_file, client_certificate_file, client_key_file) - http = Net::HTTP.new(uri.host, uri.port) + http = Net::HTTP.new(uri.hostname, uri.port) http.use_ssl = uri.scheme == 'https' http.verify_mode = ssl_verify_mode http.ca_file = certificate_file unless certificate_file.nil? diff --git a/exporter/otlp/lib/opentelemetry/exporter/otlp/exporter.rb b/exporter/otlp/lib/opentelemetry/exporter/otlp/exporter.rb index 603c95142e..6a42ba8b64 100644 --- a/exporter/otlp/lib/opentelemetry/exporter/otlp/exporter.rb +++ b/exporter/otlp/lib/opentelemetry/exporter/otlp/exporter.rb @@ -124,7 +124,7 @@ def build_span_flags(parent_span_is_remote, base_flags) end def http_connection(uri, ssl_verify_mode, certificate_file, client_certificate_file, client_key_file) - http = Net::HTTP.new(uri.host, uri.port) + http = Net::HTTP.new(uri.hostname, uri.port) http.use_ssl = uri.scheme == 'https' http.verify_mode = ssl_verify_mode http.ca_file = certificate_file unless certificate_file.nil? diff --git a/exporter/otlp/test/opentelemetry/exporter/otlp/exporter_test.rb b/exporter/otlp/test/opentelemetry/exporter/otlp/exporter_test.rb index 66328ff442..5e7dfb2ca2 100644 --- a/exporter/otlp/test/opentelemetry/exporter/otlp/exporter_test.rb +++ b/exporter/otlp/test/opentelemetry/exporter/otlp/exporter_test.rb @@ -357,6 +357,148 @@ end end + describe 'IPv4/IPv6 compatibility' do + it 'handles IPv6 loopback address with brackets' do + exp = OpenTelemetry::Exporter::OTLP::Exporter.new(endpoint: 'http://[::1]:4318/v1/traces') + http = exp.instance_variable_get(:@http) + _(http.address).must_equal '::1' + _(http.port).must_equal 4318 + _(exp.instance_variable_get(:@path)).must_equal '/v1/traces' + end + + it 'handles IPv6 full address with brackets' do + exp = OpenTelemetry::Exporter::OTLP::Exporter.new(endpoint: 'http://[2001:db8::1]:4318/v1/traces') + http = exp.instance_variable_get(:@http) + _(http.address).must_equal '2001:db8::1' + _(http.port).must_equal 4318 + end + + it 'handles IPv6 address with https' do + exp = OpenTelemetry::Exporter::OTLP::Exporter.new(endpoint: 'https://[::1]:4318/v1/traces') + http = exp.instance_variable_get(:@http) + _(http.address).must_equal '::1' + _(http.port).must_equal 4318 + _(http.use_ssl?).must_equal true + end + + it 'handles IPv6 address with custom path' do + exp = OpenTelemetry::Exporter::OTLP::Exporter.new(endpoint: 'http://[::1]:8080/custom/path') + http = exp.instance_variable_get(:@http) + _(http.address).must_equal '::1' + _(http.port).must_equal 8080 + _(exp.instance_variable_get(:@path)).must_equal '/custom/path' + end + + it 'handles IPv4 loopback address' do + exp = OpenTelemetry::Exporter::OTLP::Exporter.new(endpoint: 'http://127.0.0.1:4318/v1/traces') + http = exp.instance_variable_get(:@http) + _(http.address).must_equal '127.0.0.1' + _(http.port).must_equal 4318 + _(exp.instance_variable_get(:@path)).must_equal '/v1/traces' + end + + it 'handles IPv4 address with custom port' do + exp = OpenTelemetry::Exporter::OTLP::Exporter.new(endpoint: 'http://192.168.1.100:8080/v1/traces') + http = exp.instance_variable_get(:@http) + _(http.address).must_equal '192.168.1.100' + _(http.port).must_equal 8080 + end + + it 'handles IPv4 address with https' do + exp = OpenTelemetry::Exporter::OTLP::Exporter.new(endpoint: 'https://10.0.0.1:4318/v1/traces') + http = exp.instance_variable_get(:@http) + _(http.address).must_equal '10.0.0.1' + _(http.port).must_equal 4318 + _(http.use_ssl?).must_equal true + end + + it 'handles IPv4 address with custom path' do + exp = OpenTelemetry::Exporter::OTLP::Exporter.new(endpoint: 'http://127.0.0.1:9090/custom/path') + http = exp.instance_variable_get(:@http) + _(http.address).must_equal '127.0.0.1' + _(http.port).must_equal 9090 + _(exp.instance_variable_get(:@path)).must_equal '/custom/path' + end + + it 'handles IPv4 address from environment variable' do + exp = OpenTelemetry::TestHelpers.with_env('OTEL_EXPORTER_OTLP_ENDPOINT' => 'http://192.168.1.1:4318') do + OpenTelemetry::Exporter::OTLP::Exporter.new + end + http = exp.instance_variable_get(:@http) + _(http.address).must_equal '192.168.1.1' + _(http.port).must_equal 4318 + _(exp.instance_variable_get(:@path)).must_equal '/v1/traces' + end + + it 'handles hostnames' do + exp = OpenTelemetry::Exporter::OTLP::Exporter.new(endpoint: 'http://localhost:4318/v1/traces') + http = exp.instance_variable_get(:@http) + _(http.address).must_equal 'localhost' + _(http.port).must_equal 4318 + end + + it 'handles fully qualified domain names' do + exp = OpenTelemetry::Exporter::OTLP::Exporter.new(endpoint: 'http://otel.example.com:4318/v1/traces') + http = exp.instance_variable_get(:@http) + _(http.address).must_equal 'otel.example.com' + _(http.port).must_equal 4318 + end + + it 'handles hostnames with https' do + exp = OpenTelemetry::Exporter::OTLP::Exporter.new(endpoint: 'https://otel-collector.prod.example.com:443/v1/traces') + http = exp.instance_variable_get(:@http) + _(http.address).must_equal 'otel-collector.prod.example.com' + _(http.port).must_equal 443 + _(http.use_ssl?).must_equal true + end + + it 'handles IPv6 address from environment variable' do + exp = OpenTelemetry::TestHelpers.with_env('OTEL_EXPORTER_OTLP_ENDPOINT' => 'http://[::1]:4318') do + OpenTelemetry::Exporter::OTLP::Exporter.new + end + http = exp.instance_variable_get(:@http) + _(http.address).must_equal '::1' + _(http.port).must_equal 4318 + _(exp.instance_variable_get(:@path)).must_equal '/v1/traces' + end + + it 'exports span data with IPv6 address' do + stub_request(:post, 'http://[::1]:4318/v1/traces').to_return(status: 200) + exp = OpenTelemetry::Exporter::OTLP::Exporter.new(endpoint: 'http://[::1]:4318/v1/traces') + span_data = OpenTelemetry::TestHelpers.create_span_data + result = exp.export([span_data]) + _(result).must_equal(SUCCESS) + end + + it 'exports span data with IPv4 address' do + stub_request(:post, 'http://127.0.0.1:4318/v1/traces').to_return(status: 200) + exp = OpenTelemetry::Exporter::OTLP::Exporter.new(endpoint: 'http://127.0.0.1:4318/v1/traces') + span_data = OpenTelemetry::TestHelpers.create_span_data + result = exp.export([span_data]) + _(result).must_equal(SUCCESS) + end + + it 'exports span data with IPv6 address via full URL' do + stub_request(:post, 'http://[2001:db8::8a2e:370:7334]:4318/v1/traces').to_return(status: 200) + exp = OpenTelemetry::Exporter::OTLP::Exporter.new(endpoint: 'http://[2001:db8::8a2e:370:7334]:4318/v1/traces') + span_data = OpenTelemetry::TestHelpers.create_span_data + result = exp.export([span_data]) + _(result).must_equal(SUCCESS) + end + + it 'handles connection errors with IPv6 address gracefully' do + stub_request(:post, 'http://[::1]:4318/v1/traces').to_raise(SocketError.new('getaddrinfo: nodename nor servname provided, or not known')) + exp = OpenTelemetry::Exporter::OTLP::Exporter.new(endpoint: 'http://[::1]:4318/v1/traces') + span_data = OpenTelemetry::TestHelpers.create_span_data + + # Mock backoff to prevent retries + exp.stub(:backoff?, ->(**_) { false }) do + result = exp.export([span_data]) + _(result).must_equal(FAILURE) + end + end + end + describe 'ssl_verify_mode:' do it 'can be set to VERIFY_NONE by an envvar' do exp = OpenTelemetry::TestHelpers.with_env('OTEL_RUBY_EXPORTER_OTLP_SSL_VERIFY_NONE' => 'true') do