Repository: open-telemetry/opentelemetry-python

Issue:
OTLP gRPC exporter silently fails if scheme is not specified in endpoint
Issue arising from implementing https://github.com/open-telemetry/opentelemetry-python/pull/1771

**Steps to reproduce**
Supplying an remote collector hostname without scheme causes the OTLP exporter to silently not export spans.

https://github.com/open-telemetry/opentelemetry-python/blob/b3455cd1164f9c5f336cc26a52fb351cb422b0b2/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py#L210

`parsed_url.netloc` is an empty str if the scheme is not specified e.g. `localhost:55680`, this causes spans to not be exported to a remote collector as `endpoint` is empty.

**What is the expected behavior?**
Spans are correctly exported to remote collector via OTLP.

**What is the actual behavior?**
Spans are not exported to remote collector via OTLP.

**Additional context**
Per [opentelemetry specs](https://github.com/open-telemetry/opentelemetry-specification/blob/f62744a679814937214fd17394ab3fa8a9099424/specification/protocol/exporter.md#configuration-options), it was written that the scheme must be specified in the endpoint; this library should either enforce that the scheme is supplied (fail hard if not) or assume a sane default (http?) for the purposes of using this library.



Discussion:
Thanks for raising this issue. i did not expect url parse to set `localhost` in the scheme 😞 

Resolving patch:
diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py
--- a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py
+++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py
@@ -207,7 +207,8 @@ def __init__(
             else:
                 insecure = True
 
-        endpoint = parsed_url.netloc
+        if parsed_url.netloc:
+            endpoint = parsed_url.netloc
 
         self._headers = headers or environ.get(OTEL_EXPORTER_OTLP_HEADERS)
         if isinstance(self._headers, str):


Test patch:
diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_trace_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_trace_exporter.py
--- a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_trace_exporter.py
+++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_trace_exporter.py
@@ -239,6 +239,7 @@ def test_otlp_headers_from_env(self, mock_ssl_channel, mock_secure):
     @patch("opentelemetry.exporter.otlp.proto.grpc.exporter.secure_channel")
     def test_otlp_exporter_endpoint(self, mock_secure, mock_insecure):
         """Just OTEL_EXPORTER_OTLP_COMPRESSION should work"""
+        expected_endpoint = "localhost:4317"
         endpoints = [
             (
                 "http://localhost:4317",
@@ -275,6 +276,13 @@ def test_otlp_exporter_endpoint(self, mock_secure, mock_insecure):
                     mock_method, endpoint, insecure
                 ),
             )
+            self.assertEqual(
+                expected_endpoint,
+                mock_method.call_args[0][0],
+                "expected {} got {} {}".format(
+                    expected_endpoint, mock_method.call_args[0][0], endpoint
+                ),
+            )
             mock_method.reset_mock()
 
     # pylint: disable=no-self-use