We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN 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.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py`
Content:
```
1 # Copyright The OpenTelemetry Authors
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 """OTLP Exporter"""
16 
17 import logging
18 from abc import ABC, abstractmethod
19 from collections.abc import Mapping, Sequence
20 from os import environ
21 from time import sleep
22 from typing import Any, Callable, Dict, Generic, List, Optional
23 from typing import Sequence as TypingSequence
24 from typing import Text, TypeVar
25 from urllib import parse
26 from urllib.parse import urlparse
27 
28 from backoff import expo
29 from google.rpc.error_details_pb2 import RetryInfo
30 from grpc import (
31     ChannelCredentials,
32     Compression,
33     RpcError,
34     StatusCode,
35     insecure_channel,
36     secure_channel,
37     ssl_channel_credentials,
38 )
39 
40 from opentelemetry.proto.common.v1.common_pb2 import AnyValue, KeyValue
41 from opentelemetry.proto.resource.v1.resource_pb2 import Resource
42 from opentelemetry.sdk.environment_variables import (
43     OTEL_EXPORTER_OTLP_CERTIFICATE,
44     OTEL_EXPORTER_OTLP_COMPRESSION,
45     OTEL_EXPORTER_OTLP_ENDPOINT,
46     OTEL_EXPORTER_OTLP_HEADERS,
47     OTEL_EXPORTER_OTLP_TIMEOUT,
48 )
49 from opentelemetry.sdk.resources import Resource as SDKResource
50 
51 logger = logging.getLogger(__name__)
52 SDKDataT = TypeVar("SDKDataT")
53 ResourceDataT = TypeVar("ResourceDataT")
54 TypingResourceT = TypeVar("TypingResourceT")
55 ExportServiceRequestT = TypeVar("ExportServiceRequestT")
56 ExportResultT = TypeVar("ExportResultT")
57 
58 _ENVIRON_TO_COMPRESSION = {
59     None: None,
60     "gzip": Compression.Gzip,
61 }
62 
63 
64 class InvalidCompressionValueException(Exception):
65     def __init__(self, environ_key: str, environ_value: str):
66         super().__init__(
67             'Invalid value "{}" for compression envvar {}'.format(
68                 environ_value, environ_key
69             )
70         )
71 
72 
73 def environ_to_compression(environ_key: str) -> Optional[Compression]:
74     environ_value = (
75         environ[environ_key].lower().strip()
76         if environ_key in environ
77         else None
78     )
79     if environ_value not in _ENVIRON_TO_COMPRESSION:
80         raise InvalidCompressionValueException(environ_key, environ_value)
81     return _ENVIRON_TO_COMPRESSION[environ_value]
82 
83 
84 def _translate_key_values(key: Text, value: Any) -> KeyValue:
85 
86     if isinstance(value, bool):
87         any_value = AnyValue(bool_value=value)
88 
89     elif isinstance(value, str):
90         any_value = AnyValue(string_value=value)
91 
92     elif isinstance(value, int):
93         any_value = AnyValue(int_value=value)
94 
95     elif isinstance(value, float):
96         any_value = AnyValue(double_value=value)
97 
98     elif isinstance(value, Sequence):
99         any_value = AnyValue(array_value=value)
100 
101     elif isinstance(value, Mapping):
102         any_value = AnyValue(kvlist_value=value)
103 
104     else:
105         raise Exception(
106             "Invalid type {} of value {}".format(type(value), value)
107         )
108 
109     return KeyValue(key=key, value=any_value)
110 
111 
112 def get_resource_data(
113     sdk_resource_instrumentation_library_data: Dict[
114         SDKResource, ResourceDataT
115     ],
116     resource_class: Callable[..., TypingResourceT],
117     name: str,
118 ) -> List[TypingResourceT]:
119 
120     resource_data = []
121 
122     for (
123         sdk_resource,
124         instrumentation_library_data,
125     ) in sdk_resource_instrumentation_library_data.items():
126 
127         collector_resource = Resource()
128 
129         for key, value in sdk_resource.attributes.items():
130 
131             try:
132                 # pylint: disable=no-member
133                 collector_resource.attributes.append(
134                     _translate_key_values(key, value)
135                 )
136             except Exception as error:  # pylint: disable=broad-except
137                 logger.exception(error)
138 
139         resource_data.append(
140             resource_class(
141                 **{
142                     "resource": collector_resource,
143                     "instrumentation_library_{}".format(name): [
144                         instrumentation_library_data
145                     ],
146                 }
147             )
148         )
149 
150     return resource_data
151 
152 
153 def _load_credential_from_file(filepath) -> ChannelCredentials:
154     try:
155         with open(filepath, "rb") as creds_file:
156             credential = creds_file.read()
157             return ssl_channel_credentials(credential)
158     except FileNotFoundError:
159         logger.exception("Failed to read credential file")
160         return None
161 
162 
163 def _get_credentials(creds, environ_key):
164     if creds is not None:
165         return creds
166     creds_env = environ.get(environ_key)
167     if creds_env:
168         return _load_credential_from_file(creds_env)
169     return ssl_channel_credentials()
170 
171 
172 # pylint: disable=no-member
173 class OTLPExporterMixin(
174     ABC, Generic[SDKDataT, ExportServiceRequestT, ExportResultT]
175 ):
176     """OTLP span exporter
177 
178     Args:
179         endpoint: OpenTelemetry Collector receiver endpoint
180         insecure: Connection type
181         credentials: ChannelCredentials object for server authentication
182         headers: Headers to send when exporting
183         timeout: Backend request timeout in seconds
184         compression: gRPC compression method to use
185     """
186 
187     def __init__(
188         self,
189         endpoint: Optional[str] = None,
190         insecure: Optional[bool] = None,
191         credentials: Optional[ChannelCredentials] = None,
192         headers: Optional[Sequence] = None,
193         timeout: Optional[int] = None,
194         compression: Optional[Compression] = None,
195     ):
196         super().__init__()
197 
198         endpoint = endpoint or environ.get(
199             OTEL_EXPORTER_OTLP_ENDPOINT, "http://localhost:4317"
200         )
201 
202         parsed_url = urlparse(endpoint)
203 
204         if insecure is None:
205             if parsed_url.scheme == "https":
206                 insecure = False
207             else:
208                 insecure = True
209 
210         endpoint = parsed_url.netloc
211 
212         self._headers = headers or environ.get(OTEL_EXPORTER_OTLP_HEADERS)
213         if isinstance(self._headers, str):
214             self._headers = tuple(
215                 tuple(item.split("=")) for item in self._headers.split(",")
216             )
217         self._timeout = timeout or int(
218             environ.get(OTEL_EXPORTER_OTLP_TIMEOUT, 10)
219         )
220         self._collector_span_kwargs = None
221 
222         compression = (
223             environ_to_compression(OTEL_EXPORTER_OTLP_COMPRESSION)
224             if compression is None
225             else compression
226         ) or Compression.NoCompression
227 
228         if insecure:
229             self._client = self._stub(
230                 insecure_channel(endpoint, compression=compression)
231             )
232         else:
233             credentials = _get_credentials(
234                 credentials, OTEL_EXPORTER_OTLP_CERTIFICATE
235             )
236             self._client = self._stub(
237                 secure_channel(endpoint, credentials, compression=compression)
238             )
239 
240     @abstractmethod
241     def _translate_data(
242         self, data: TypingSequence[SDKDataT]
243     ) -> ExportServiceRequestT:
244         pass
245 
246     def _export(self, data: TypingSequence[SDKDataT]) -> ExportResultT:
247         # expo returns a generator that yields delay values which grow
248         # exponentially. Once delay is greater than max_value, the yielded
249         # value will remain constant.
250         # max_value is set to 900 (900 seconds is 15 minutes) to use the same
251         # value as used in the Go implementation.
252 
253         max_value = 900
254 
255         for delay in expo(max_value=max_value):
256 
257             if delay == max_value:
258                 return self._result.FAILURE
259 
260             try:
261                 self._client.Export(
262                     request=self._translate_data(data),
263                     metadata=self._headers,
264                     timeout=self._timeout,
265                 )
266 
267                 return self._result.SUCCESS
268 
269             except RpcError as error:
270 
271                 if error.code() in [
272                     StatusCode.CANCELLED,
273                     StatusCode.DEADLINE_EXCEEDED,
274                     StatusCode.PERMISSION_DENIED,
275                     StatusCode.UNAUTHENTICATED,
276                     StatusCode.RESOURCE_EXHAUSTED,
277                     StatusCode.ABORTED,
278                     StatusCode.OUT_OF_RANGE,
279                     StatusCode.UNAVAILABLE,
280                     StatusCode.DATA_LOSS,
281                 ]:
282 
283                     retry_info_bin = dict(error.trailing_metadata()).get(
284                         "google.rpc.retryinfo-bin"
285                     )
286                     if retry_info_bin is not None:
287                         retry_info = RetryInfo()
288                         retry_info.ParseFromString(retry_info_bin)
289                         delay = (
290                             retry_info.retry_delay.seconds
291                             + retry_info.retry_delay.nanos / 1.0e9
292                         )
293 
294                     logger.debug(
295                         "Waiting %ss before retrying export of span", delay
296                     )
297                     sleep(delay)
298                     continue
299 
300                 if error.code() == StatusCode.OK:
301                     return self._result.SUCCESS
302 
303                 return self._result.FAILURE
304 
305         return self._result.FAILURE
306 
307     def shutdown(self) -> None:
308         pass
309
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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 @@
             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):

{"golden_diff": "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\n--- a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py\n+++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py\n@@ -207,7 +207,8 @@\n             else:\n                 insecure = True\n \n-        endpoint = parsed_url.netloc\n+        if parsed_url.netloc:\n+            endpoint = parsed_url.netloc\n \n         self._headers = headers or environ.get(OTEL_EXPORTER_OTLP_HEADERS)\n         if isinstance(self._headers, str):\n", "issue": "OTLP gRPC exporter silently fails if scheme is not specified in endpoint\nIssue arising from implementing https://github.com/open-telemetry/opentelemetry-python/pull/1771\r\n\r\n**Steps to reproduce**\r\nSupplying an remote collector hostname without scheme causes the OTLP exporter to silently not export spans.\r\n\r\nhttps://github.com/open-telemetry/opentelemetry-python/blob/b3455cd1164f9c5f336cc26a52fb351cb422b0b2/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py#L210\r\n\r\n`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.\r\n\r\n**What is the expected behavior?**\r\nSpans are correctly exported to remote collector via OTLP.\r\n\r\n**What is the actual behavior?**\r\nSpans are not exported to remote collector via OTLP.\r\n\r\n**Additional context**\r\nPer [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.\r\n\n", "before_files": [{"content": "# Copyright The OpenTelemetry Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"OTLP Exporter\"\"\"\n\nimport logging\nfrom abc import ABC, abstractmethod\nfrom collections.abc import Mapping, Sequence\nfrom os import environ\nfrom time import sleep\nfrom typing import Any, Callable, Dict, Generic, List, Optional\nfrom typing import Sequence as TypingSequence\nfrom typing import Text, TypeVar\nfrom urllib import parse\nfrom urllib.parse import urlparse\n\nfrom backoff import expo\nfrom google.rpc.error_details_pb2 import RetryInfo\nfrom grpc import (\n    ChannelCredentials,\n    Compression,\n    RpcError,\n    StatusCode,\n    insecure_channel,\n    secure_channel,\n    ssl_channel_credentials,\n)\n\nfrom opentelemetry.proto.common.v1.common_pb2 import AnyValue, KeyValue\nfrom opentelemetry.proto.resource.v1.resource_pb2 import Resource\nfrom opentelemetry.sdk.environment_variables import (\n    OTEL_EXPORTER_OTLP_CERTIFICATE,\n    OTEL_EXPORTER_OTLP_COMPRESSION,\n    OTEL_EXPORTER_OTLP_ENDPOINT,\n    OTEL_EXPORTER_OTLP_HEADERS,\n    OTEL_EXPORTER_OTLP_TIMEOUT,\n)\nfrom opentelemetry.sdk.resources import Resource as SDKResource\n\nlogger = logging.getLogger(__name__)\nSDKDataT = TypeVar(\"SDKDataT\")\nResourceDataT = TypeVar(\"ResourceDataT\")\nTypingResourceT = TypeVar(\"TypingResourceT\")\nExportServiceRequestT = TypeVar(\"ExportServiceRequestT\")\nExportResultT = TypeVar(\"ExportResultT\")\n\n_ENVIRON_TO_COMPRESSION = {\n    None: None,\n    \"gzip\": Compression.Gzip,\n}\n\n\nclass InvalidCompressionValueException(Exception):\n    def __init__(self, environ_key: str, environ_value: str):\n        super().__init__(\n            'Invalid value \"{}\" for compression envvar {}'.format(\n                environ_value, environ_key\n            )\n        )\n\n\ndef environ_to_compression(environ_key: str) -> Optional[Compression]:\n    environ_value = (\n        environ[environ_key].lower().strip()\n        if environ_key in environ\n        else None\n    )\n    if environ_value not in _ENVIRON_TO_COMPRESSION:\n        raise InvalidCompressionValueException(environ_key, environ_value)\n    return _ENVIRON_TO_COMPRESSION[environ_value]\n\n\ndef _translate_key_values(key: Text, value: Any) -> KeyValue:\n\n    if isinstance(value, bool):\n        any_value = AnyValue(bool_value=value)\n\n    elif isinstance(value, str):\n        any_value = AnyValue(string_value=value)\n\n    elif isinstance(value, int):\n        any_value = AnyValue(int_value=value)\n\n    elif isinstance(value, float):\n        any_value = AnyValue(double_value=value)\n\n    elif isinstance(value, Sequence):\n        any_value = AnyValue(array_value=value)\n\n    elif isinstance(value, Mapping):\n        any_value = AnyValue(kvlist_value=value)\n\n    else:\n        raise Exception(\n            \"Invalid type {} of value {}\".format(type(value), value)\n        )\n\n    return KeyValue(key=key, value=any_value)\n\n\ndef get_resource_data(\n    sdk_resource_instrumentation_library_data: Dict[\n        SDKResource, ResourceDataT\n    ],\n    resource_class: Callable[..., TypingResourceT],\n    name: str,\n) -> List[TypingResourceT]:\n\n    resource_data = []\n\n    for (\n        sdk_resource,\n        instrumentation_library_data,\n    ) in sdk_resource_instrumentation_library_data.items():\n\n        collector_resource = Resource()\n\n        for key, value in sdk_resource.attributes.items():\n\n            try:\n                # pylint: disable=no-member\n                collector_resource.attributes.append(\n                    _translate_key_values(key, value)\n                )\n            except Exception as error:  # pylint: disable=broad-except\n                logger.exception(error)\n\n        resource_data.append(\n            resource_class(\n                **{\n                    \"resource\": collector_resource,\n                    \"instrumentation_library_{}\".format(name): [\n                        instrumentation_library_data\n                    ],\n                }\n            )\n        )\n\n    return resource_data\n\n\ndef _load_credential_from_file(filepath) -> ChannelCredentials:\n    try:\n        with open(filepath, \"rb\") as creds_file:\n            credential = creds_file.read()\n            return ssl_channel_credentials(credential)\n    except FileNotFoundError:\n        logger.exception(\"Failed to read credential file\")\n        return None\n\n\ndef _get_credentials(creds, environ_key):\n    if creds is not None:\n        return creds\n    creds_env = environ.get(environ_key)\n    if creds_env:\n        return _load_credential_from_file(creds_env)\n    return ssl_channel_credentials()\n\n\n# pylint: disable=no-member\nclass OTLPExporterMixin(\n    ABC, Generic[SDKDataT, ExportServiceRequestT, ExportResultT]\n):\n    \"\"\"OTLP span exporter\n\n    Args:\n        endpoint: OpenTelemetry Collector receiver endpoint\n        insecure: Connection type\n        credentials: ChannelCredentials object for server authentication\n        headers: Headers to send when exporting\n        timeout: Backend request timeout in seconds\n        compression: gRPC compression method to use\n    \"\"\"\n\n    def __init__(\n        self,\n        endpoint: Optional[str] = None,\n        insecure: Optional[bool] = None,\n        credentials: Optional[ChannelCredentials] = None,\n        headers: Optional[Sequence] = None,\n        timeout: Optional[int] = None,\n        compression: Optional[Compression] = None,\n    ):\n        super().__init__()\n\n        endpoint = endpoint or environ.get(\n            OTEL_EXPORTER_OTLP_ENDPOINT, \"http://localhost:4317\"\n        )\n\n        parsed_url = urlparse(endpoint)\n\n        if insecure is None:\n            if parsed_url.scheme == \"https\":\n                insecure = False\n            else:\n                insecure = True\n\n        endpoint = parsed_url.netloc\n\n        self._headers = headers or environ.get(OTEL_EXPORTER_OTLP_HEADERS)\n        if isinstance(self._headers, str):\n            self._headers = tuple(\n                tuple(item.split(\"=\")) for item in self._headers.split(\",\")\n            )\n        self._timeout = timeout or int(\n            environ.get(OTEL_EXPORTER_OTLP_TIMEOUT, 10)\n        )\n        self._collector_span_kwargs = None\n\n        compression = (\n            environ_to_compression(OTEL_EXPORTER_OTLP_COMPRESSION)\n            if compression is None\n            else compression\n        ) or Compression.NoCompression\n\n        if insecure:\n            self._client = self._stub(\n                insecure_channel(endpoint, compression=compression)\n            )\n        else:\n            credentials = _get_credentials(\n                credentials, OTEL_EXPORTER_OTLP_CERTIFICATE\n            )\n            self._client = self._stub(\n                secure_channel(endpoint, credentials, compression=compression)\n            )\n\n    @abstractmethod\n    def _translate_data(\n        self, data: TypingSequence[SDKDataT]\n    ) -> ExportServiceRequestT:\n        pass\n\n    def _export(self, data: TypingSequence[SDKDataT]) -> ExportResultT:\n        # expo returns a generator that yields delay values which grow\n        # exponentially. Once delay is greater than max_value, the yielded\n        # value will remain constant.\n        # max_value is set to 900 (900 seconds is 15 minutes) to use the same\n        # value as used in the Go implementation.\n\n        max_value = 900\n\n        for delay in expo(max_value=max_value):\n\n            if delay == max_value:\n                return self._result.FAILURE\n\n            try:\n                self._client.Export(\n                    request=self._translate_data(data),\n                    metadata=self._headers,\n                    timeout=self._timeout,\n                )\n\n                return self._result.SUCCESS\n\n            except RpcError as error:\n\n                if error.code() in [\n                    StatusCode.CANCELLED,\n                    StatusCode.DEADLINE_EXCEEDED,\n                    StatusCode.PERMISSION_DENIED,\n                    StatusCode.UNAUTHENTICATED,\n                    StatusCode.RESOURCE_EXHAUSTED,\n                    StatusCode.ABORTED,\n                    StatusCode.OUT_OF_RANGE,\n                    StatusCode.UNAVAILABLE,\n                    StatusCode.DATA_LOSS,\n                ]:\n\n                    retry_info_bin = dict(error.trailing_metadata()).get(\n                        \"google.rpc.retryinfo-bin\"\n                    )\n                    if retry_info_bin is not None:\n                        retry_info = RetryInfo()\n                        retry_info.ParseFromString(retry_info_bin)\n                        delay = (\n                            retry_info.retry_delay.seconds\n                            + retry_info.retry_delay.nanos / 1.0e9\n                        )\n\n                    logger.debug(\n                        \"Waiting %ss before retrying export of span\", delay\n                    )\n                    sleep(delay)\n                    continue\n\n                if error.code() == StatusCode.OK:\n                    return self._result.SUCCESS\n\n                return self._result.FAILURE\n\n        return self._result.FAILURE\n\n    def shutdown(self) -> None:\n        pass\n", "path": "exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py"}], "after_files": [{"content": "# Copyright The OpenTelemetry Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"OTLP Exporter\"\"\"\n\nimport logging\nfrom abc import ABC, abstractmethod\nfrom collections.abc import Mapping, Sequence\nfrom os import environ\nfrom time import sleep\nfrom typing import Any, Callable, Dict, Generic, List, Optional\nfrom typing import Sequence as TypingSequence\nfrom typing import Text, TypeVar\nfrom urllib import parse\nfrom urllib.parse import urlparse\n\nfrom backoff import expo\nfrom google.rpc.error_details_pb2 import RetryInfo\nfrom grpc import (\n    ChannelCredentials,\n    Compression,\n    RpcError,\n    StatusCode,\n    insecure_channel,\n    secure_channel,\n    ssl_channel_credentials,\n)\n\nfrom opentelemetry.proto.common.v1.common_pb2 import AnyValue, KeyValue\nfrom opentelemetry.proto.resource.v1.resource_pb2 import Resource\nfrom opentelemetry.sdk.environment_variables import (\n    OTEL_EXPORTER_OTLP_CERTIFICATE,\n    OTEL_EXPORTER_OTLP_COMPRESSION,\n    OTEL_EXPORTER_OTLP_ENDPOINT,\n    OTEL_EXPORTER_OTLP_HEADERS,\n    OTEL_EXPORTER_OTLP_TIMEOUT,\n)\nfrom opentelemetry.sdk.resources import Resource as SDKResource\n\nlogger = logging.getLogger(__name__)\nSDKDataT = TypeVar(\"SDKDataT\")\nResourceDataT = TypeVar(\"ResourceDataT\")\nTypingResourceT = TypeVar(\"TypingResourceT\")\nExportServiceRequestT = TypeVar(\"ExportServiceRequestT\")\nExportResultT = TypeVar(\"ExportResultT\")\n\n_ENVIRON_TO_COMPRESSION = {\n    None: None,\n    \"gzip\": Compression.Gzip,\n}\n\n\nclass InvalidCompressionValueException(Exception):\n    def __init__(self, environ_key: str, environ_value: str):\n        super().__init__(\n            'Invalid value \"{}\" for compression envvar {}'.format(\n                environ_value, environ_key\n            )\n        )\n\n\ndef environ_to_compression(environ_key: str) -> Optional[Compression]:\n    environ_value = (\n        environ[environ_key].lower().strip()\n        if environ_key in environ\n        else None\n    )\n    if environ_value not in _ENVIRON_TO_COMPRESSION:\n        raise InvalidCompressionValueException(environ_key, environ_value)\n    return _ENVIRON_TO_COMPRESSION[environ_value]\n\n\ndef _translate_key_values(key: Text, value: Any) -> KeyValue:\n\n    if isinstance(value, bool):\n        any_value = AnyValue(bool_value=value)\n\n    elif isinstance(value, str):\n        any_value = AnyValue(string_value=value)\n\n    elif isinstance(value, int):\n        any_value = AnyValue(int_value=value)\n\n    elif isinstance(value, float):\n        any_value = AnyValue(double_value=value)\n\n    elif isinstance(value, Sequence):\n        any_value = AnyValue(array_value=value)\n\n    elif isinstance(value, Mapping):\n        any_value = AnyValue(kvlist_value=value)\n\n    else:\n        raise Exception(\n            \"Invalid type {} of value {}\".format(type(value), value)\n        )\n\n    return KeyValue(key=key, value=any_value)\n\n\ndef get_resource_data(\n    sdk_resource_instrumentation_library_data: Dict[\n        SDKResource, ResourceDataT\n    ],\n    resource_class: Callable[..., TypingResourceT],\n    name: str,\n) -> List[TypingResourceT]:\n\n    resource_data = []\n\n    for (\n        sdk_resource,\n        instrumentation_library_data,\n    ) in sdk_resource_instrumentation_library_data.items():\n\n        collector_resource = Resource()\n\n        for key, value in sdk_resource.attributes.items():\n\n            try:\n                # pylint: disable=no-member\n                collector_resource.attributes.append(\n                    _translate_key_values(key, value)\n                )\n            except Exception as error:  # pylint: disable=broad-except\n                logger.exception(error)\n\n        resource_data.append(\n            resource_class(\n                **{\n                    \"resource\": collector_resource,\n                    \"instrumentation_library_{}\".format(name): [\n                        instrumentation_library_data\n                    ],\n                }\n            )\n        )\n\n    return resource_data\n\n\ndef _load_credential_from_file(filepath) -> ChannelCredentials:\n    try:\n        with open(filepath, \"rb\") as creds_file:\n            credential = creds_file.read()\n            return ssl_channel_credentials(credential)\n    except FileNotFoundError:\n        logger.exception(\"Failed to read credential file\")\n        return None\n\n\ndef _get_credentials(creds, environ_key):\n    if creds is not None:\n        return creds\n    creds_env = environ.get(environ_key)\n    if creds_env:\n        return _load_credential_from_file(creds_env)\n    return ssl_channel_credentials()\n\n\n# pylint: disable=no-member\nclass OTLPExporterMixin(\n    ABC, Generic[SDKDataT, ExportServiceRequestT, ExportResultT]\n):\n    \"\"\"OTLP span exporter\n\n    Args:\n        endpoint: OpenTelemetry Collector receiver endpoint\n        insecure: Connection type\n        credentials: ChannelCredentials object for server authentication\n        headers: Headers to send when exporting\n        timeout: Backend request timeout in seconds\n        compression: gRPC compression method to use\n    \"\"\"\n\n    def __init__(\n        self,\n        endpoint: Optional[str] = None,\n        insecure: Optional[bool] = None,\n        credentials: Optional[ChannelCredentials] = None,\n        headers: Optional[Sequence] = None,\n        timeout: Optional[int] = None,\n        compression: Optional[Compression] = None,\n    ):\n        super().__init__()\n\n        endpoint = endpoint or environ.get(\n            OTEL_EXPORTER_OTLP_ENDPOINT, \"http://localhost:4317\"\n        )\n\n        parsed_url = urlparse(endpoint)\n\n        if insecure is None:\n            if parsed_url.scheme == \"https\":\n                insecure = False\n            else:\n                insecure = True\n\n        if parsed_url.netloc:\n            endpoint = parsed_url.netloc\n\n        self._headers = headers or environ.get(OTEL_EXPORTER_OTLP_HEADERS)\n        if isinstance(self._headers, str):\n            self._headers = tuple(\n                tuple(item.split(\"=\")) for item in self._headers.split(\",\")\n            )\n        self._timeout = timeout or int(\n            environ.get(OTEL_EXPORTER_OTLP_TIMEOUT, 10)\n        )\n        self._collector_span_kwargs = None\n\n        compression = (\n            environ_to_compression(OTEL_EXPORTER_OTLP_COMPRESSION)\n            if compression is None\n            else compression\n        ) or Compression.NoCompression\n\n        if insecure:\n            self._client = self._stub(\n                insecure_channel(endpoint, compression=compression)\n            )\n        else:\n            credentials = _get_credentials(\n                credentials, OTEL_EXPORTER_OTLP_CERTIFICATE\n            )\n            self._client = self._stub(\n                secure_channel(endpoint, credentials, compression=compression)\n            )\n\n    @abstractmethod\n    def _translate_data(\n        self, data: TypingSequence[SDKDataT]\n    ) -> ExportServiceRequestT:\n        pass\n\n    def _export(self, data: TypingSequence[SDKDataT]) -> ExportResultT:\n        # expo returns a generator that yields delay values which grow\n        # exponentially. Once delay is greater than max_value, the yielded\n        # value will remain constant.\n        # max_value is set to 900 (900 seconds is 15 minutes) to use the same\n        # value as used in the Go implementation.\n\n        max_value = 900\n\n        for delay in expo(max_value=max_value):\n\n            if delay == max_value:\n                return self._result.FAILURE\n\n            try:\n                self._client.Export(\n                    request=self._translate_data(data),\n                    metadata=self._headers,\n                    timeout=self._timeout,\n                )\n\n                return self._result.SUCCESS\n\n            except RpcError as error:\n\n                if error.code() in [\n                    StatusCode.CANCELLED,\n                    StatusCode.DEADLINE_EXCEEDED,\n                    StatusCode.PERMISSION_DENIED,\n                    StatusCode.UNAUTHENTICATED,\n                    StatusCode.RESOURCE_EXHAUSTED,\n                    StatusCode.ABORTED,\n                    StatusCode.OUT_OF_RANGE,\n                    StatusCode.UNAVAILABLE,\n                    StatusCode.DATA_LOSS,\n                ]:\n\n                    retry_info_bin = dict(error.trailing_metadata()).get(\n                        \"google.rpc.retryinfo-bin\"\n                    )\n                    if retry_info_bin is not None:\n                        retry_info = RetryInfo()\n                        retry_info.ParseFromString(retry_info_bin)\n                        delay = (\n                            retry_info.retry_delay.seconds\n                            + retry_info.retry_delay.nanos / 1.0e9\n                        )\n\n                    logger.debug(\n                        \"Waiting %ss before retrying export of span\", delay\n                    )\n                    sleep(delay)\n                    continue\n\n                if error.code() == StatusCode.OK:\n                    return self._result.SUCCESS\n\n                return self._result.FAILURE\n\n        return self._result.FAILURE\n\n    def shutdown(self) -> None:\n        pass\n", "path": "exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py"}]}