diff --git a/exporter/opentelemetry-exporter-otlp/CHANGELOG.md b/exporter/opentelemetry-exporter-otlp/CHANGELOG.md index 479f2105e71..0e9fbba8a83 100644 --- a/exporter/opentelemetry-exporter-otlp/CHANGELOG.md +++ b/exporter/opentelemetry-exporter-otlp/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +- Add Env variables in OTLP exporter + ([#1101](https://github.com/open-telemetry/opentelemetry-python/pull/1101)) + ## Version 0.14b0 Released 2020-10-13 diff --git a/exporter/opentelemetry-exporter-otlp/src/opentelemetry/exporter/otlp/exporter.py b/exporter/opentelemetry-exporter-otlp/src/opentelemetry/exporter/otlp/exporter.py index 079557f831a..9595c35f64f 100644 --- a/exporter/opentelemetry-exporter-otlp/src/opentelemetry/exporter/otlp/exporter.py +++ b/exporter/opentelemetry-exporter-otlp/src/opentelemetry/exporter/otlp/exporter.py @@ -15,6 +15,7 @@ """OTLP Exporter""" import logging +import os from abc import ABC, abstractmethod from collections.abc import Mapping, Sequence from time import sleep @@ -30,8 +31,10 @@ StatusCode, insecure_channel, secure_channel, + ssl_channel_credentials, ) +from opentelemetry.configuration import Configuration from opentelemetry.proto.common.v1.common_pb2 import AnyValue, KeyValue from opentelemetry.proto.resource.v1.resource_pb2 import Resource from opentelemetry.sdk.resources import Resource as SDKResource @@ -113,6 +116,16 @@ def _get_resource_data( return resource_data +def _load_credential_from_file(filepath) -> ChannelCredentials: + try: + with open(filepath, "rb") as f: + credential = f.read() + return ssl_channel_credentials(credential) + except FileNotFoundError: + logger.exception("Failed to read credential file") + return None + + # pylint: disable=no-member class OTLPExporterMixin( ABC, Generic[SDKDataT, ExportServiceRequestT, ExportResultT] @@ -121,24 +134,47 @@ class OTLPExporterMixin( Args: endpoint: OpenTelemetry Collector receiver endpoint + insecure: Connection type credentials: ChannelCredentials object for server authentication metadata: Metadata to send when exporting + timeout: Backend request timeout in seconds """ def __init__( self, - endpoint: str = "localhost:55680", - credentials: ChannelCredentials = None, - metadata: Optional[Tuple[Any]] = None, + endpoint: Optional[str] = None, + insecure: Optional[bool] = None, + credentials: Optional[ChannelCredentials] = None, + headers: Optional[str] = None, + timeout: Optional[int] = None, ): super().__init__() - self._metadata = metadata + endpoint = ( + endpoint + or Configuration().EXPORTER_OTLP_ENDPOINT + or "localhost:55680" + ) + + if insecure is None: + insecure = Configuration().EXPORTER_OTLP_INSECURE + if insecure is None: + insecure = False + + self._headers = headers or Configuration().EXPORTER_OTLP_HEADERS + self._timeout = ( + timeout + or Configuration().EXPORTER_OTLP_TIMEOUT + or 10 # default: 10 seconds + ) self._collector_span_kwargs = None - if credentials is None: + if insecure: self._client = self._stub(insecure_channel(endpoint)) else: + credentials = credentials or _load_credential_from_file( + Configuration().EXPORTER_OTLP_CERTIFICATE + ) self._client = self._stub(secure_channel(endpoint, credentials)) @abstractmethod @@ -164,7 +200,8 @@ def _export(self, data: TypingSequence[SDKDataT]) -> ExportResultT: try: self._client.Export( request=self._translate_data(data), - metadata=self._metadata, + metadata=self._headers, + timeout=self._timeout, ) return self._result.SUCCESS diff --git a/exporter/opentelemetry-exporter-otlp/src/opentelemetry/exporter/otlp/metrics_exporter/__init__.py b/exporter/opentelemetry-exporter-otlp/src/opentelemetry/exporter/otlp/metrics_exporter/__init__.py index 40feb222fb5..18ce772ea41 100644 --- a/exporter/opentelemetry-exporter-otlp/src/opentelemetry/exporter/otlp/metrics_exporter/__init__.py +++ b/exporter/opentelemetry-exporter-otlp/src/opentelemetry/exporter/otlp/metrics_exporter/__init__.py @@ -15,12 +15,16 @@ """OTLP Metrics Exporter""" import logging -from typing import List, Sequence, Type, TypeVar +import os +from typing import List, Optional, Sequence, Type, TypeVar, Union -# pylint: disable=duplicate-code +from grpc import ChannelCredentials + +from opentelemetry.configuration import Configuration from opentelemetry.exporter.otlp.exporter import ( OTLPExporterMixin, _get_resource_data, + _load_credential_from_file, ) from opentelemetry.proto.collector.metrics.v1.metrics_service_pb2 import ( ExportMetricsServiceRequest, @@ -109,13 +113,47 @@ class OTLPMetricsExporter( Args: endpoint: OpenTelemetry Collector receiver endpoint + insecure: Connection type credentials: Credentials object for server authentication metadata: Metadata to send when exporting + timeout: Backend request timeout in seconds """ _stub = MetricsServiceStub _result = MetricsExportResult + def __init__( + self, + endpoint: Optional[str] = None, + insecure: Optional[bool] = None, + credentials: Optional[ChannelCredentials] = None, + headers: Optional[str] = None, + timeout: Optional[int] = None, + ): + if insecure is None: + insecure = Configuration().EXPORTER_OTLP_METRIC_INSECURE + + if ( + not insecure + and Configuration().EXPORTER_OTLP_METRIC_CERTIFICATE is not None + ): + credentials = credentials or _load_credential_from_file( + Configuration().EXPORTER_OTLP_METRIC_CERTIFICATE + ) + + super().__init__( + **{ + "endpoint": endpoint + or Configuration().EXPORTER_OTLP_METRIC_ENDPOINT, + "insecure": insecure, + "credentials": credentials, + "headers": headers + or Configuration().EXPORTER_OTLP_METRIC_HEADERS, + "timeout": timeout + or Configuration().EXPORTER_OTLP_METRIC_TIMEOUT, + } + ) + # pylint: disable=no-self-use def _translate_data( self, data: Sequence[MetricRecord] diff --git a/exporter/opentelemetry-exporter-otlp/src/opentelemetry/exporter/otlp/trace_exporter/__init__.py b/exporter/opentelemetry-exporter-otlp/src/opentelemetry/exporter/otlp/trace_exporter/__init__.py index e518716d399..8acd2dea55a 100644 --- a/exporter/opentelemetry-exporter-otlp/src/opentelemetry/exporter/otlp/trace_exporter/__init__.py +++ b/exporter/opentelemetry-exporter-otlp/src/opentelemetry/exporter/otlp/trace_exporter/__init__.py @@ -14,11 +14,16 @@ """OTLP Span Exporter""" import logging -from typing import Sequence +import os +from typing import Optional, Sequence +from grpc import ChannelCredentials + +from opentelemetry.configuration import Configuration from opentelemetry.exporter.otlp.exporter import ( OTLPExporterMixin, _get_resource_data, + _load_credential_from_file, _translate_key_values, ) from opentelemetry.proto.collector.trace.v1.trace_service_pb2 import ( @@ -50,13 +55,47 @@ class OTLPSpanExporter( Args: endpoint: OpenTelemetry Collector receiver endpoint + insecure: Connection type credentials: Credentials object for server authentication metadata: Metadata to send when exporting + timeout: Backend request timeout in seconds """ _result = SpanExportResult _stub = TraceServiceStub + def __init__( + self, + endpoint: Optional[str] = None, + insecure: Optional[bool] = None, + credentials: Optional[ChannelCredentials] = None, + headers: Optional[str] = None, + timeout: Optional[int] = None, + ): + if insecure is None: + insecure = Configuration().EXPORTER_OTLP_SPAN_INSECURE + + if ( + not insecure + and Configuration().EXPORTER_OTLP_SPAN_CERTIFICATE is not None + ): + credentials = credentials or _load_credential_from_file( + Configuration().EXPORTER_OTLP_SPAN_CERTIFICATE + ) + + super().__init__( + **{ + "endpoint": endpoint + or Configuration().EXPORTER_OTLP_SPAN_ENDPOINT, + "insecure": insecure, + "credentials": credentials, + "headers": headers + or Configuration().EXPORTER_OTLP_SPAN_HEADERS, + "timeout": timeout + or Configuration().EXPORTER_OTLP_SPAN_TIMEOUT, + } + ) + def _translate_name(self, sdk_span: SDKSpan) -> None: self._collector_span_kwargs["name"] = sdk_span.name diff --git a/exporter/opentelemetry-exporter-otlp/tests/fixtures/test.cert b/exporter/opentelemetry-exporter-otlp/tests/fixtures/test.cert new file mode 100644 index 00000000000..e69de29bb2d diff --git a/exporter/opentelemetry-exporter-otlp/tests/test_otlp_metric_exporter.py b/exporter/opentelemetry-exporter-otlp/tests/test_otlp_metric_exporter.py index 21a718b84ea..530f9a430ae 100644 --- a/exporter/opentelemetry-exporter-otlp/tests/test_otlp_metric_exporter.py +++ b/exporter/opentelemetry-exporter-otlp/tests/test_otlp_metric_exporter.py @@ -16,6 +16,9 @@ from unittest import TestCase from unittest.mock import patch +from grpc import ChannelCredentials + +from opentelemetry.configuration import Configuration from opentelemetry.exporter.otlp.metrics_exporter import OTLPMetricsExporter from opentelemetry.proto.collector.metrics.v1.metrics_service_pb2 import ( ExportMetricsServiceRequest, @@ -44,8 +47,9 @@ class TestOTLPMetricExporter(TestCase): def setUp(self): - self.exporter = OTLPMetricsExporter() + self.exporter = OTLPMetricsExporter(insecure=True) resource = SDKResource(OrderedDict([("a", 1), ("b", False)])) + self.counter_metric_record = MetricRecord( Counter( "a", @@ -60,6 +64,33 @@ def setUp(self): resource, ) + Configuration._reset() # pylint: disable=protected-access + + def tearDown(self): + Configuration._reset() # pylint: disable=protected-access + + @patch.dict( + "os.environ", + { + "OTEL_EXPORTER_OTLP_METRIC_ENDPOINT": "collector:55680", + "OTEL_EXPORTER_OTLP_METRIC_CERTIFICATE": "fixtures/test.cert", + "OTEL_EXPORTER_OTLP_METRIC_HEADERS": "key1:value1;key2:value2", + "OTEL_EXPORTER_OTLP_METRIC_TIMEOUT": "10", + }, + ) + @patch("opentelemetry.exporter.otlp.exporter.OTLPExporterMixin.__init__") + def test_env_variables(self, mock_exporter_mixin): + OTLPMetricsExporter() + + self.assertTrue(len(mock_exporter_mixin.call_args_list) == 1) + _, kwargs = mock_exporter_mixin.call_args_list[0] + + self.assertEqual(kwargs["endpoint"], "collector:55680") + self.assertEqual(kwargs["headers"], "key1:value1;key2:value2") + self.assertEqual(kwargs["timeout"], 10) + self.assertIsNotNone(kwargs["credentials"]) + self.assertIsInstance(kwargs["credentials"], ChannelCredentials) + @patch("opentelemetry.sdk.metrics.export.aggregate.time_ns") def test_translate_metrics(self, mock_time_ns): # pylint: disable=no-member diff --git a/exporter/opentelemetry-exporter-otlp/tests/test_otlp_trace_exporter.py b/exporter/opentelemetry-exporter-otlp/tests/test_otlp_trace_exporter.py index e8c449c9df6..9c99056c445 100644 --- a/exporter/opentelemetry-exporter-otlp/tests/test_otlp_trace_exporter.py +++ b/exporter/opentelemetry-exporter-otlp/tests/test_otlp_trace_exporter.py @@ -19,8 +19,9 @@ from google.protobuf.duration_pb2 import Duration from google.rpc.error_details_pb2 import RetryInfo -from grpc import StatusCode, server +from grpc import ChannelCredentials, StatusCode, server +from opentelemetry.configuration import Configuration from opentelemetry.exporter.otlp.trace_exporter import OTLPSpanExporter from opentelemetry.proto.collector.trace.v1.trace_service_pb2 import ( ExportTraceServiceRequest, @@ -102,7 +103,7 @@ def Export(self, request, context): class TestOTLPSpanExporter(TestCase): def setUp(self): tracer_provider = TracerProvider() - self.exporter = OTLPSpanExporter() + self.exporter = OTLPSpanExporter(insecure=True) tracer_provider.add_span_processor( SimpleExportSpanProcessor(self.exporter) ) @@ -154,8 +155,33 @@ def setUp(self): self.span.start() self.span.end() + Configuration._reset() # pylint: disable=protected-access + def tearDown(self): self.server.stop(None) + Configuration._reset() # pylint: disable=protected-access + + @patch.dict( + "os.environ", + { + "OTEL_EXPORTER_OTLP_SPAN_ENDPOINT": "collector:55680", + "OTEL_EXPORTER_OTLP_SPAN_CERTIFICATE": "fixtures/test.cert", + "OTEL_EXPORTER_OTLP_SPAN_HEADERS": "key1:value1;key2:value2", + "OTEL_EXPORTER_OTLP_SPAN_TIMEOUT": "10", + }, + ) + @patch("opentelemetry.exporter.otlp.exporter.OTLPExporterMixin.__init__") + def test_env_variables(self, mock_exporter_mixin): + OTLPSpanExporter() + + self.assertTrue(len(mock_exporter_mixin.call_args_list) == 1) + _, kwargs = mock_exporter_mixin.call_args_list[0] + + self.assertEqual(kwargs["endpoint"], "collector:55680") + self.assertEqual(kwargs["headers"], "key1:value1;key2:value2") + self.assertEqual(kwargs["timeout"], 10) + self.assertIsNotNone(kwargs["credentials"]) + self.assertIsInstance(kwargs["credentials"], ChannelCredentials) @patch("opentelemetry.exporter.otlp.exporter.expo") @patch("opentelemetry.exporter.otlp.exporter.sleep")