diff --git a/CHANGELOG.md b/CHANGELOG.md index 93861d2b9dc..4edac969001 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#4103](https://github.com/open-telemetry/opentelemetry-python/pull/4103)) - Update semantic conventions to version 1.27.0 ([#4104](https://github.com/open-telemetry/opentelemetry-python/pull/4104)) +- Export ExponentialHistogram and ExponentialHistogramDataPoint + ([#4134](https://github.com/open-telemetry/opentelemetry-python/pull/4134)) +- Implement Client Key and Certificate File Support for All OTLP Exporters + ([#4116](https://github.com/open-telemetry/opentelemetry-python/pull/4116)) ## Version 1.26.0/0.47b0 (2024-07-25) diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/_log_exporter/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/_log_exporter/__init__.py index 3a87ef1223c..d8f2ba2efb6 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/_log_exporter/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/_log_exporter/__init__.py @@ -34,6 +34,8 @@ from opentelemetry.sdk.environment_variables import ( OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE, + OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE, + OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY, OTEL_EXPORTER_OTLP_LOGS_COMPRESSION, OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, OTEL_EXPORTER_OTLP_LOGS_HEADERS, @@ -71,7 +73,10 @@ def __init__( and environ.get(OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE) is not None ): credentials = _get_credentials( - credentials, OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE + credentials, + OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE, + OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY, + OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE, ) environ_timeout = environ.get(OTEL_EXPORTER_OTLP_LOGS_TIMEOUT) 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 index 0c398f33e65..b07d24e0d0e 100644 --- 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 @@ -61,6 +61,8 @@ ) from opentelemetry.proto.resource.v1.resource_pb2 import Resource # noqa: F401 from opentelemetry.sdk.environment_variables import ( + OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_KEY, OTEL_EXPORTER_OTLP_CERTIFICATE, OTEL_EXPORTER_OTLP_COMPRESSION, OTEL_EXPORTER_OTLP_ENDPOINT, @@ -118,22 +120,55 @@ def get_resource_data( return _get_resource_data(sdk_resource_scope_data, resource_class, name) -def _load_credential_from_file(filepath) -> ChannelCredentials: +def _read_file(file_path: str) -> Optional[bytes]: try: - with open(filepath, "rb") as creds_file: - credential = creds_file.read() - return ssl_channel_credentials(credential) - except FileNotFoundError: - logger.exception("Failed to read credential file") + with open(file_path, "rb") as file: + return file.read() + except FileNotFoundError as e: + logger.exception( + f"Failed to read file: {e.filename}. Please check if the file exists and is accessible." + ) return None -def _get_credentials(creds, environ_key): +def _load_credentials( + certificate_file: Optional[str], + client_key_file: Optional[str], + client_certificate_file: Optional[str], +) -> Optional[ChannelCredentials]: + root_certificates = ( + _read_file(certificate_file) if certificate_file else None + ) + private_key = _read_file(client_key_file) if client_key_file else None + certificate_chain = ( + _read_file(client_certificate_file) + if client_certificate_file + else None + ) + + return ssl_channel_credentials( + root_certificates=root_certificates, + private_key=private_key, + certificate_chain=certificate_chain, + ) + + +def _get_credentials( + creds: Optional[ChannelCredentials], + certificate_file_env_key: str, + client_key_file_env_key: str, + client_certificate_file_env_key: str, +) -> ChannelCredentials: if creds is not None: return creds - creds_env = environ.get(environ_key) - if creds_env: - return _load_credential_from_file(creds_env) + + certificate_file = environ.get(certificate_file_env_key) + if certificate_file: + client_key_file = environ.get(client_key_file_env_key) + client_certificate_file = environ.get(client_certificate_file_env_key) + return _load_credentials( + certificate_file, client_key_file, client_certificate_file + ) return ssl_channel_credentials() @@ -214,7 +249,10 @@ def __init__( ) else: credentials = _get_credentials( - credentials, OTEL_EXPORTER_OTLP_CERTIFICATE + credentials, + OTEL_EXPORTER_OTLP_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_KEY, + OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, ) self._client = self._stub( secure_channel( diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/metric_exporter/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/metric_exporter/__init__.py index 0ceca25c867..645885b6f28 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/metric_exporter/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/metric_exporter/__init__.py @@ -42,6 +42,8 @@ from opentelemetry.proto.metrics.v1 import metrics_pb2 as pb2 # noqa: F401 from opentelemetry.sdk.environment_variables import ( OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE, + OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE, + OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY, OTEL_EXPORTER_OTLP_METRICS_COMPRESSION, OTEL_EXPORTER_OTLP_METRICS_ENDPOINT, OTEL_EXPORTER_OTLP_METRICS_HEADERS, @@ -113,7 +115,10 @@ def __init__( and environ.get(OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE) is not None ): credentials = _get_credentials( - credentials, OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE + credentials, + OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE, + OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY, + OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE, ) environ_timeout = environ.get(OTEL_EXPORTER_OTLP_METRICS_TIMEOUT) diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/trace_exporter/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/trace_exporter/__init__.py index bd120ac7874..ce8dcaabdf7 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/trace_exporter/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/trace_exporter/__init__.py @@ -48,6 +48,8 @@ ) from opentelemetry.proto.trace.v1.trace_pb2 import Status # noqa: F401 from opentelemetry.sdk.environment_variables import ( + OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE, + OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY, OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE, OTEL_EXPORTER_OTLP_TRACES_COMPRESSION, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, @@ -105,7 +107,10 @@ def __init__( and environ.get(OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE) is not None ): credentials = _get_credentials( - credentials, OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE + credentials, + OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE, + OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY, + OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE, ) environ_timeout = environ.get(OTEL_EXPORTER_OTLP_TRACES_TIMEOUT) diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/fixtures/test-client-cert.pem b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/fixtures/test-client-cert.pem new file mode 100644 index 00000000000..e69de29bb2d diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/fixtures/test-client-key.pem b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/fixtures/test-client-key.pem new file mode 100644 index 00000000000..e69de29bb2d diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py index b81fa4e7bbc..fc2211c5aeb 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +# pylint: disable=too-many-lines + import time from concurrent.futures import ThreadPoolExecutor from os.path import dirname @@ -53,6 +55,8 @@ from opentelemetry.sdk._logs.export import LogExportResult from opentelemetry.sdk.environment_variables import ( OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE, + OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE, + OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY, OTEL_EXPORTER_OTLP_LOGS_COMPRESSION, OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, OTEL_EXPORTER_OTLP_LOGS_HEADERS, @@ -206,12 +210,40 @@ def test_exporting(self): # pylint: disable=protected-access self.assertEqual(self.exporter._exporting, "logs") + @patch.dict( + "os.environ", + { + OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: "logs:4317", + OTEL_EXPORTER_OTLP_LOGS_HEADERS: " key1=value1,KEY2 = VALUE=2", + OTEL_EXPORTER_OTLP_LOGS_TIMEOUT: "10", + OTEL_EXPORTER_OTLP_LOGS_COMPRESSION: "gzip", + }, + ) + @patch( + "opentelemetry.exporter.otlp.proto.grpc.exporter.OTLPExporterMixin.__init__" + ) + def test_env_variables(self, mock_exporter_mixin): + OTLPLogExporter() + + self.assertTrue(len(mock_exporter_mixin.call_args_list) == 1) + _, kwargs = mock_exporter_mixin.call_args_list[0] + self.assertEqual(kwargs["endpoint"], "logs:4317") + self.assertEqual(kwargs["headers"], " key1=value1,KEY2 = VALUE=2") + self.assertEqual(kwargs["timeout"], 10) + self.assertEqual(kwargs["compression"], Compression.Gzip) + self.assertIsNone(kwargs["credentials"]) + + # Create a new test method specifically for client certificates @patch.dict( "os.environ", { OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: "logs:4317", OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE: THIS_DIR + "/../fixtures/test.cert", + OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE: THIS_DIR + + "/../fixtures/test-client-cert.pem", + OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY: THIS_DIR + + "/../fixtures/test-client-key.pem", OTEL_EXPORTER_OTLP_LOGS_HEADERS: " key1=value1,KEY2 = VALUE=2", OTEL_EXPORTER_OTLP_LOGS_TIMEOUT: "10", OTEL_EXPORTER_OTLP_LOGS_COMPRESSION: "gzip", @@ -220,7 +252,7 @@ def test_exporting(self): @patch( "opentelemetry.exporter.otlp.proto.grpc.exporter.OTLPExporterMixin.__init__" ) - def test_env_variables(self, mock_exporter_mixin): + def test_env_variables_with_client_certificates(self, mock_exporter_mixin): OTLPLogExporter() self.assertTrue(len(mock_exporter_mixin.call_args_list) == 1) @@ -232,6 +264,37 @@ def test_env_variables(self, mock_exporter_mixin): self.assertIsNotNone(kwargs["credentials"]) self.assertIsInstance(kwargs["credentials"], ChannelCredentials) + @patch.dict( + "os.environ", + { + OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: "logs:4317", + OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE: THIS_DIR + + "/../fixtures/test.cert", + OTEL_EXPORTER_OTLP_LOGS_HEADERS: " key1=value1,KEY2 = VALUE=2", + OTEL_EXPORTER_OTLP_LOGS_TIMEOUT: "10", + OTEL_EXPORTER_OTLP_LOGS_COMPRESSION: "gzip", + }, + ) + @patch( + "opentelemetry.exporter.otlp.proto.grpc.exporter.OTLPExporterMixin.__init__" + ) + @patch("logging.Logger.error") + def test_env_variables_with_only_certificate( + self, mock_logger_error, mock_exporter_mixin + ): + OTLPLogExporter() + + self.assertTrue(len(mock_exporter_mixin.call_args_list) == 1) + _, kwargs = mock_exporter_mixin.call_args_list[0] + self.assertEqual(kwargs["endpoint"], "logs:4317") + self.assertEqual(kwargs["headers"], " key1=value1,KEY2 = VALUE=2") + self.assertEqual(kwargs["timeout"], 10) + self.assertEqual(kwargs["compression"], Compression.Gzip) + self.assertIsNotNone(kwargs["credentials"]) + self.assertIsInstance(kwargs["credentials"], ChannelCredentials) + + mock_logger_error.assert_not_called() + @patch( "opentelemetry.exporter.otlp.proto.grpc.exporter.ssl_channel_credentials" ) diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_metrics_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_metrics_exporter.py index 38304962ba0..f9f9427b776 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_metrics_exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_metrics_exporter.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +# pylint: disable=too-many-lines + import threading from concurrent.futures import ThreadPoolExecutor @@ -45,6 +47,8 @@ from opentelemetry.sdk.environment_variables import ( OTEL_EXPORTER_OTLP_COMPRESSION, OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE, + OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE, + OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY, OTEL_EXPORTER_OTLP_METRICS_COMPRESSION, OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION, OTEL_EXPORTER_OTLP_METRICS_ENDPOINT, @@ -217,12 +221,40 @@ def test_preferred_temporality(self): AggregationTemporality.CUMULATIVE, ) + @patch.dict( + "os.environ", + { + OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: "collector:4317", + OTEL_EXPORTER_OTLP_METRICS_HEADERS: " key1=value1,KEY2 = value=2", + OTEL_EXPORTER_OTLP_METRICS_TIMEOUT: "10", + OTEL_EXPORTER_OTLP_METRICS_COMPRESSION: "gzip", + }, + ) + @patch( + "opentelemetry.exporter.otlp.proto.grpc.exporter.OTLPExporterMixin.__init__" + ) + def test_env_variables(self, mock_exporter_mixin): + OTLPMetricExporter() + + self.assertTrue(len(mock_exporter_mixin.call_args_list) == 1) + _, kwargs = mock_exporter_mixin.call_args_list[0] + + self.assertEqual(kwargs["endpoint"], "collector:4317") + self.assertEqual(kwargs["headers"], " key1=value1,KEY2 = value=2") + self.assertEqual(kwargs["timeout"], 10) + self.assertEqual(kwargs["compression"], Compression.Gzip) + self.assertIsNone(kwargs["credentials"]) + @patch.dict( "os.environ", { OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: "collector:4317", OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE: THIS_DIR + "/fixtures/test.cert", + OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE: THIS_DIR + + "/fixtures/test-client-cert.pem", + OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY: THIS_DIR + + "/fixtures/test-client-key.pem", OTEL_EXPORTER_OTLP_METRICS_HEADERS: " key1=value1,KEY2 = value=2", OTEL_EXPORTER_OTLP_METRICS_TIMEOUT: "10", OTEL_EXPORTER_OTLP_METRICS_COMPRESSION: "gzip", @@ -231,7 +263,7 @@ def test_preferred_temporality(self): @patch( "opentelemetry.exporter.otlp.proto.grpc.exporter.OTLPExporterMixin.__init__" ) - def test_env_variables(self, mock_exporter_mixin): + def test_env_variables_with_client_certificates(self, mock_exporter_mixin): OTLPMetricExporter() self.assertTrue(len(mock_exporter_mixin.call_args_list) == 1) @@ -244,6 +276,37 @@ def test_env_variables(self, mock_exporter_mixin): self.assertIsNotNone(kwargs["credentials"]) self.assertIsInstance(kwargs["credentials"], ChannelCredentials) + @patch.dict( + "os.environ", + { + OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: "collector:4317", + OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE: THIS_DIR + + "/fixtures/test.cert", + OTEL_EXPORTER_OTLP_METRICS_HEADERS: " key1=value1,KEY2 = value=2", + OTEL_EXPORTER_OTLP_METRICS_TIMEOUT: "10", + OTEL_EXPORTER_OTLP_METRICS_COMPRESSION: "gzip", + }, + ) + @patch( + "opentelemetry.exporter.otlp.proto.grpc.exporter.OTLPExporterMixin.__init__" + ) + @patch("logging.Logger.error") + def test_env_variables_with_only_certificate( + self, mock_logger_error, mock_exporter_mixin + ): + OTLPMetricExporter() + + self.assertTrue(len(mock_exporter_mixin.call_args_list) == 1) + _, kwargs = mock_exporter_mixin.call_args_list[0] + self.assertEqual(kwargs["endpoint"], "collector:4317") + self.assertEqual(kwargs["headers"], " key1=value1,KEY2 = value=2") + self.assertEqual(kwargs["timeout"], 10) + self.assertEqual(kwargs["compression"], Compression.Gzip) + self.assertIsNotNone(kwargs["credentials"]) + self.assertIsInstance(kwargs["credentials"], ChannelCredentials) + + mock_logger_error.assert_not_called() + @patch( "opentelemetry.exporter.otlp.proto.grpc.exporter.ssl_channel_credentials" ) 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 index c71e5fbd57f..d618ffb13a3 100644 --- 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 @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +# pylint: disable=too-many-lines + import os import threading from concurrent.futures import ThreadPoolExecutor @@ -56,6 +58,8 @@ from opentelemetry.sdk.environment_variables import ( OTEL_EXPORTER_OTLP_COMPRESSION, OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE, + OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE, + OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY, OTEL_EXPORTER_OTLP_TRACES_COMPRESSION, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, OTEL_EXPORTER_OTLP_TRACES_HEADERS, @@ -227,12 +231,38 @@ def test_exporting(self): # pylint: disable=protected-access self.assertEqual(self.exporter._exporting, "traces") + @patch.dict( + "os.environ", + { + OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "collector:4317", + OTEL_EXPORTER_OTLP_TRACES_HEADERS: " key1=value1,KEY2 = value=2", + OTEL_EXPORTER_OTLP_TRACES_TIMEOUT: "10", + OTEL_EXPORTER_OTLP_TRACES_COMPRESSION: "gzip", + }, + ) + @patch( + "opentelemetry.exporter.otlp.proto.grpc.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:4317") + self.assertEqual(kwargs["headers"], " key1=value1,KEY2 = value=2") + self.assertEqual(kwargs["timeout"], 10) + self.assertEqual(kwargs["compression"], Compression.Gzip) + self.assertIsNone(kwargs["credentials"]) + @patch.dict( "os.environ", { OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "collector:4317", OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE: THIS_DIR + "/fixtures/test.cert", + OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE: THIS_DIR + + "/fixtures/test-client-cert.pem", + OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY: THIS_DIR + + "/fixtures/test-client-key.pem", OTEL_EXPORTER_OTLP_TRACES_HEADERS: " key1=value1,KEY2 = value=2", OTEL_EXPORTER_OTLP_TRACES_TIMEOUT: "10", OTEL_EXPORTER_OTLP_TRACES_COMPRESSION: "gzip", @@ -241,12 +271,40 @@ def test_exporting(self): @patch( "opentelemetry.exporter.otlp.proto.grpc.exporter.OTLPExporterMixin.__init__" ) - def test_env_variables(self, mock_exporter_mixin): + def test_env_variables_with_client_certificates(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:4317") + self.assertEqual(kwargs["headers"], " key1=value1,KEY2 = value=2") + self.assertEqual(kwargs["timeout"], 10) + self.assertEqual(kwargs["compression"], Compression.Gzip) + self.assertIsNotNone(kwargs["credentials"]) + self.assertIsInstance(kwargs["credentials"], ChannelCredentials) + @patch.dict( + "os.environ", + { + OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "collector:4317", + OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE: THIS_DIR + + "/fixtures/test.cert", + OTEL_EXPORTER_OTLP_TRACES_HEADERS: " key1=value1,KEY2 = value=2", + OTEL_EXPORTER_OTLP_TRACES_TIMEOUT: "10", + OTEL_EXPORTER_OTLP_TRACES_COMPRESSION: "gzip", + }, + ) + @patch( + "opentelemetry.exporter.otlp.proto.grpc.exporter.OTLPExporterMixin.__init__" + ) + @patch("logging.Logger.error") + def test_env_variables_with_only_certificate( + self, mock_logger_error, 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:4317") self.assertEqual(kwargs["headers"], " key1=value1,KEY2 = value=2") self.assertEqual(kwargs["timeout"], 10) @@ -254,6 +312,8 @@ def test_env_variables(self, mock_exporter_mixin): self.assertIsNotNone(kwargs["credentials"]) self.assertIsInstance(kwargs["credentials"], ChannelCredentials) + mock_logger_error.assert_not_called() + @patch( "opentelemetry.exporter.otlp.proto.grpc.exporter.ssl_channel_credentials" ) @@ -869,20 +929,6 @@ def test_translate_key_values(self): self.assertTrue(isinstance(arr_value.values[1], AnyValue)) self.assertEqual(arr_value.values[1].string_value, "123") - # Tracing specs currently does not support Mapping type attributes - # map_value = _translate_key_values( - # "map_type", {"asd": "123", "def": "456"} - # ) - # self.assertTrue(isinstance(map_value, KeyValue)) - # self.assertEqual(map_value.key, "map_type") - # self.assertTrue(isinstance(map_value.value, AnyValue)) - # self.assertTrue(isinstance(map_value.value.kvlist_value, KeyValueList)) - - # kvlist_value = map_value.value.kvlist_value - # self.assertTrue(isinstance(kvlist_value.values[0], KeyValue)) - # self.assertEqual(kvlist_value.values[0].key, "asd") - # self.assertEqual(kvlist_value.values[0].value.string_value, "123") - def test_dropped_values(self): span = get_span_with_dropped_attributes_events_links() # pylint:disable=protected-access diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py index b337aedfb0e..597b012a49a 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py @@ -28,12 +28,16 @@ from opentelemetry.exporter.otlp.proto.common._log_encoder import encode_logs from opentelemetry.sdk.environment_variables import ( OTEL_EXPORTER_OTLP_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_KEY, OTEL_EXPORTER_OTLP_COMPRESSION, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_TIMEOUT, OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE, + OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE, + OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY, OTEL_EXPORTER_OTLP_LOGS_HEADERS, OTEL_EXPORTER_OTLP_LOGS_TIMEOUT, OTEL_EXPORTER_OTLP_LOGS_COMPRESSION, @@ -67,6 +71,8 @@ def __init__( self, endpoint: Optional[str] = None, certificate_file: Optional[str] = None, + client_key_file: Optional[str] = None, + client_certificate_file: Optional[str] = None, headers: Optional[Dict[str, str]] = None, timeout: Optional[int] = None, compression: Optional[Compression] = None, @@ -78,10 +84,24 @@ def __init__( environ.get(OTEL_EXPORTER_OTLP_ENDPOINT, DEFAULT_ENDPOINT) ), ) + # Keeping these as instance variables because they are used in tests self._certificate_file = certificate_file or environ.get( OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE, environ.get(OTEL_EXPORTER_OTLP_CERTIFICATE, True), ) + self._client_key_file = client_key_file or environ.get( + OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY, + environ.get(OTEL_EXPORTER_OTLP_CLIENT_KEY, None), + ) + self._client_certificate_file = client_certificate_file or environ.get( + OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE, + environ.get(OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, None), + ) + self._client_cert = ( + (self._client_certificate_file, self._client_key_file) + if self._client_certificate_file and self._client_key_file + else self._client_certificate_file + ) headers_string = environ.get( OTEL_EXPORTER_OTLP_LOGS_HEADERS, environ.get(OTEL_EXPORTER_OTLP_HEADERS, ""), @@ -120,6 +140,7 @@ def _export(self, serialized_data: bytes): data=data, verify=self._certificate_file, timeout=self._timeout, + cert=self._client_cert, ) @staticmethod diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/metric_exporter/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/metric_exporter/__init__.py index ea59d1a2083..16ac042dd89 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/metric_exporter/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/metric_exporter/__init__.py @@ -51,11 +51,15 @@ from opentelemetry.sdk.environment_variables import ( OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_KEY, OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_TIMEOUT, OTEL_EXPORTER_OTLP_COMPRESSION, OTEL_EXPORTER_OTLP_METRICS_ENDPOINT, OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE, + OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE, + OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY, OTEL_EXPORTER_OTLP_METRICS_HEADERS, OTEL_EXPORTER_OTLP_METRICS_TIMEOUT, OTEL_EXPORTER_OTLP_METRICS_COMPRESSION, @@ -96,6 +100,8 @@ def __init__( self, endpoint: Optional[str] = None, certificate_file: Optional[str] = None, + client_key_file: Optional[str] = None, + client_certificate_file: Optional[str] = None, headers: Optional[Dict[str, str]] = None, timeout: Optional[int] = None, compression: Optional[Compression] = None, @@ -113,6 +119,19 @@ def __init__( OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE, environ.get(OTEL_EXPORTER_OTLP_CERTIFICATE, True), ) + self._client_key_file = client_key_file or environ.get( + OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY, + environ.get(OTEL_EXPORTER_OTLP_CLIENT_KEY, None), + ) + self._client_certificate_file = client_certificate_file or environ.get( + OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE, + environ.get(OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, None), + ) + self._client_cert = ( + (self._client_certificate_file, self._client_key_file) + if self._client_certificate_file and self._client_key_file + else self._client_certificate_file + ) headers_string = environ.get( OTEL_EXPORTER_OTLP_METRICS_HEADERS, environ.get(OTEL_EXPORTER_OTLP_HEADERS, ""), @@ -156,6 +175,7 @@ def _export(self, serialized_data: bytes): data=data, verify=self._certificate_file, timeout=self._timeout, + cert=self._client_cert, ) @staticmethod diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/trace_exporter/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/trace_exporter/__init__.py index b94e54ef4a0..ac69b6acde3 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/trace_exporter/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/trace_exporter/__init__.py @@ -31,11 +31,15 @@ from opentelemetry.sdk.environment_variables import ( OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE, OTEL_EXPORTER_OTLP_TRACES_COMPRESSION, + OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE, + OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, OTEL_EXPORTER_OTLP_TRACES_HEADERS, OTEL_EXPORTER_OTLP_TRACES_TIMEOUT, OTEL_EXPORTER_OTLP_CERTIFICATE, OTEL_EXPORTER_OTLP_COMPRESSION, + OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_KEY, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_TIMEOUT, @@ -65,6 +69,8 @@ def __init__( self, endpoint: Optional[str] = None, certificate_file: Optional[str] = None, + client_key_file: Optional[str] = None, + client_certificate_file: Optional[str] = None, headers: Optional[Dict[str, str]] = None, timeout: Optional[int] = None, compression: Optional[Compression] = None, @@ -80,6 +86,19 @@ def __init__( OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE, environ.get(OTEL_EXPORTER_OTLP_CERTIFICATE, True), ) + self._client_key_file = client_key_file or environ.get( + OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY, + environ.get(OTEL_EXPORTER_OTLP_CLIENT_KEY, None), + ) + self._client_certificate_file = client_certificate_file or environ.get( + OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE, + environ.get(OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, None), + ) + self._client_cert = ( + (self._client_certificate_file, self._client_key_file) + if self._client_certificate_file and self._client_key_file + else self._client_certificate_file + ) headers_string = environ.get( OTEL_EXPORTER_OTLP_TRACES_HEADERS, environ.get(OTEL_EXPORTER_OTLP_HEADERS, ""), @@ -118,6 +137,7 @@ def _export(self, serialized_data: bytes): data=data, verify=self._certificate_file, timeout=self._timeout, + cert=self._client_cert, ) @staticmethod diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/tests/metrics/test_otlp_metrics_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-http/tests/metrics/test_otlp_metrics_exporter.py index cb8f170b1c2..ac039d4ff81 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-http/tests/metrics/test_otlp_metrics_exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-http/tests/metrics/test_otlp_metrics_exporter.py @@ -34,10 +34,14 @@ ) from opentelemetry.sdk.environment_variables import ( OTEL_EXPORTER_OTLP_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_KEY, OTEL_EXPORTER_OTLP_COMPRESSION, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE, + OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE, + OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY, OTEL_EXPORTER_OTLP_METRICS_COMPRESSION, OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION, OTEL_EXPORTER_OTLP_METRICS_ENDPOINT, @@ -73,6 +77,8 @@ OS_ENV_ENDPOINT = "os.env.base" OS_ENV_CERTIFICATE = "os/env/base.crt" +OS_ENV_CLIENT_CERTIFICATE = "os/env/client-cert.pem" +OS_ENV_CLIENT_KEY = "os/env/client-key.pem" OS_ENV_HEADERS = "envHeader1=val1,envHeader2=val2" OS_ENV_TIMEOUT = "30" @@ -114,6 +120,8 @@ def test_constructor_default(self): exporter._endpoint, DEFAULT_ENDPOINT + DEFAULT_METRICS_EXPORT_PATH ) self.assertEqual(exporter._certificate_file, True) + self.assertEqual(exporter._client_certificate_file, None) + self.assertEqual(exporter._client_key_file, None) self.assertEqual(exporter._timeout, DEFAULT_TIMEOUT) self.assertIs(exporter._compression, DEFAULT_COMPRESSION) self.assertEqual(exporter._headers, {}) @@ -123,11 +131,15 @@ def test_constructor_default(self): "os.environ", { OTEL_EXPORTER_OTLP_CERTIFICATE: OS_ENV_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE: OS_ENV_CLIENT_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_KEY: OS_ENV_CLIENT_KEY, OTEL_EXPORTER_OTLP_COMPRESSION: Compression.Gzip.value, OTEL_EXPORTER_OTLP_ENDPOINT: OS_ENV_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS: OS_ENV_HEADERS, OTEL_EXPORTER_OTLP_TIMEOUT: OS_ENV_TIMEOUT, OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE: "metrics/certificate.env", + OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE: "metrics/client-cert.pem", + OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY: "metrics/client-key.pem", OTEL_EXPORTER_OTLP_METRICS_COMPRESSION: Compression.Deflate.value, OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: "https://metrics.endpoint.env", OTEL_EXPORTER_OTLP_METRICS_HEADERS: "metricsEnv1=val1,metricsEnv2=val2,metricEnv3===val3==", @@ -139,6 +151,10 @@ def test_exporter_metrics_env_take_priority(self): self.assertEqual(exporter._endpoint, "https://metrics.endpoint.env") self.assertEqual(exporter._certificate_file, "metrics/certificate.env") + self.assertEqual( + exporter._client_certificate_file, "metrics/client-cert.pem" + ) + self.assertEqual(exporter._client_key_file, "metrics/client-key.pem") self.assertEqual(exporter._timeout, 40) self.assertIs(exporter._compression, Compression.Deflate) self.assertEqual( @@ -155,6 +171,8 @@ def test_exporter_metrics_env_take_priority(self): "os.environ", { OTEL_EXPORTER_OTLP_CERTIFICATE: OS_ENV_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE: OS_ENV_CLIENT_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_KEY: OS_ENV_CLIENT_KEY, OTEL_EXPORTER_OTLP_COMPRESSION: Compression.Gzip.value, OTEL_EXPORTER_OTLP_ENDPOINT: OS_ENV_ENDPOINT, OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: "https://metrics.endpoint.env", @@ -166,6 +184,8 @@ def test_exporter_constructor_take_priority(self): exporter = OTLPMetricExporter( endpoint="example.com/1234", certificate_file="path/to/service.crt", + client_key_file="path/to/client-key.pem", + client_certificate_file="path/to/client-cert.pem", headers={"testHeader1": "value1", "testHeader2": "value2"}, timeout=20, compression=Compression.NoCompression, @@ -174,6 +194,10 @@ def test_exporter_constructor_take_priority(self): self.assertEqual(exporter._endpoint, "example.com/1234") self.assertEqual(exporter._certificate_file, "path/to/service.crt") + self.assertEqual( + exporter._client_certificate_file, "path/to/client-cert.pem" + ) + self.assertEqual(exporter._client_key_file, "path/to/client-key.pem") self.assertEqual(exporter._timeout, 20) self.assertIs(exporter._compression, Compression.NoCompression) self.assertEqual( @@ -186,6 +210,8 @@ def test_exporter_constructor_take_priority(self): "os.environ", { OTEL_EXPORTER_OTLP_CERTIFICATE: OS_ENV_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE: OS_ENV_CLIENT_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_KEY: OS_ENV_CLIENT_KEY, OTEL_EXPORTER_OTLP_COMPRESSION: Compression.Gzip.value, OTEL_EXPORTER_OTLP_HEADERS: OS_ENV_HEADERS, OTEL_EXPORTER_OTLP_TIMEOUT: OS_ENV_TIMEOUT, @@ -196,6 +222,10 @@ def test_exporter_env(self): exporter = OTLPMetricExporter() self.assertEqual(exporter._certificate_file, OS_ENV_CERTIFICATE) + self.assertEqual( + exporter._client_certificate_file, OS_ENV_CLIENT_CERTIFICATE + ) + self.assertEqual(exporter._client_key_file, OS_ENV_CLIENT_KEY) self.assertEqual(exporter._timeout, int(OS_ENV_TIMEOUT)) self.assertIs(exporter._compression, Compression.Gzip) self.assertEqual( @@ -295,6 +325,7 @@ def test_serialization(self, mock_post): data=serialized_data.SerializeToString(), verify=exporter._certificate_file, timeout=exporter._timeout, + cert=exporter._client_cert, ) @activate diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py index e92ea389afb..f5606794620 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py @@ -40,10 +40,14 @@ from opentelemetry.sdk._logs.export import LogExportResult from opentelemetry.sdk.environment_variables import ( OTEL_EXPORTER_OTLP_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_KEY, OTEL_EXPORTER_OTLP_COMPRESSION, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE, + OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE, + OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY, OTEL_EXPORTER_OTLP_LOGS_COMPRESSION, OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, OTEL_EXPORTER_OTLP_LOGS_HEADERS, @@ -56,6 +60,8 @@ ENV_ENDPOINT = "http://localhost.env:8080/" ENV_CERTIFICATE = "/etc/base.crt" +ENV_CLIENT_CERTIFICATE = "/etc/client-cert.pem" +ENV_CLIENT_KEY = "/etc/client-key.pem" ENV_HEADERS = "envHeader1=val1,envHeader2=val2" ENV_TIMEOUT = "30" @@ -69,6 +75,8 @@ def test_constructor_default(self): exporter._endpoint, DEFAULT_ENDPOINT + DEFAULT_LOGS_EXPORT_PATH ) self.assertEqual(exporter._certificate_file, True) + self.assertEqual(exporter._client_certificate_file, None) + self.assertEqual(exporter._client_key_file, None) self.assertEqual(exporter._timeout, DEFAULT_TIMEOUT) self.assertIs(exporter._compression, DEFAULT_COMPRESSION) self.assertEqual(exporter._headers, {}) @@ -87,11 +95,15 @@ def test_constructor_default(self): "os.environ", { OTEL_EXPORTER_OTLP_CERTIFICATE: ENV_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE: ENV_CLIENT_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_KEY: ENV_CLIENT_KEY, OTEL_EXPORTER_OTLP_COMPRESSION: Compression.Gzip.value, OTEL_EXPORTER_OTLP_ENDPOINT: ENV_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS: ENV_HEADERS, OTEL_EXPORTER_OTLP_TIMEOUT: ENV_TIMEOUT, OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE: "logs/certificate.env", + OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE: "logs/client-cert.pem", + OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY: "logs/client-key.pem", OTEL_EXPORTER_OTLP_LOGS_COMPRESSION: Compression.Deflate.value, OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: "https://logs.endpoint.env", OTEL_EXPORTER_OTLP_LOGS_HEADERS: "logsEnv1=val1,logsEnv2=val2,logsEnv3===val3==", @@ -103,6 +115,10 @@ def test_exporter_metrics_env_take_priority(self): self.assertEqual(exporter._endpoint, "https://logs.endpoint.env") self.assertEqual(exporter._certificate_file, "logs/certificate.env") + self.assertEqual( + exporter._client_certificate_file, "logs/client-cert.pem" + ) + self.assertEqual(exporter._client_key_file, "logs/client-key.pem") self.assertEqual(exporter._timeout, 40) self.assertIs(exporter._compression, Compression.Deflate) self.assertEqual( @@ -119,6 +135,8 @@ def test_exporter_metrics_env_take_priority(self): "os.environ", { OTEL_EXPORTER_OTLP_CERTIFICATE: ENV_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE: ENV_CLIENT_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_KEY: ENV_CLIENT_KEY, OTEL_EXPORTER_OTLP_COMPRESSION: Compression.Gzip.value, OTEL_EXPORTER_OTLP_ENDPOINT: ENV_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS: ENV_HEADERS, @@ -130,6 +148,8 @@ def test_exporter_constructor_take_priority(self): exporter = OTLPLogExporter( endpoint="endpoint.local:69/logs", certificate_file="/hello.crt", + client_key_file="/client-key.pem", + client_certificate_file="/client-cert.pem", headers={"testHeader1": "value1", "testHeader2": "value2"}, timeout=70, compression=Compression.NoCompression, @@ -138,6 +158,8 @@ def test_exporter_constructor_take_priority(self): self.assertEqual(exporter._endpoint, "endpoint.local:69/logs") self.assertEqual(exporter._certificate_file, "/hello.crt") + self.assertEqual(exporter._client_certificate_file, "/client-cert.pem") + self.assertEqual(exporter._client_key_file, "/client-key.pem") self.assertEqual(exporter._timeout, 70) self.assertIs(exporter._compression, Compression.NoCompression) self.assertEqual( @@ -150,6 +172,8 @@ def test_exporter_constructor_take_priority(self): "os.environ", { OTEL_EXPORTER_OTLP_CERTIFICATE: ENV_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE: ENV_CLIENT_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_KEY: ENV_CLIENT_KEY, OTEL_EXPORTER_OTLP_COMPRESSION: Compression.Gzip.value, OTEL_EXPORTER_OTLP_ENDPOINT: ENV_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS: ENV_HEADERS, @@ -164,6 +188,10 @@ def test_exporter_env(self): exporter._endpoint, ENV_ENDPOINT + DEFAULT_LOGS_EXPORT_PATH ) self.assertEqual(exporter._certificate_file, ENV_CERTIFICATE) + self.assertEqual( + exporter._client_certificate_file, ENV_CLIENT_CERTIFICATE + ) + self.assertEqual(exporter._client_key_file, ENV_CLIENT_KEY) self.assertEqual(exporter._timeout, int(ENV_TIMEOUT)) self.assertIs(exporter._compression, Compression.Gzip) self.assertEqual( diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_span_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_span_exporter.py index 61b46dda481..9d57c62bae8 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_span_exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_span_exporter.py @@ -29,11 +29,15 @@ from opentelemetry.exporter.otlp.proto.http.version import __version__ from opentelemetry.sdk.environment_variables import ( OTEL_EXPORTER_OTLP_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_KEY, OTEL_EXPORTER_OTLP_COMPRESSION, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_TIMEOUT, OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE, + OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE, + OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY, OTEL_EXPORTER_OTLP_TRACES_COMPRESSION, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, OTEL_EXPORTER_OTLP_TRACES_HEADERS, @@ -44,6 +48,8 @@ OS_ENV_ENDPOINT = "os.env.base" OS_ENV_CERTIFICATE = "os/env/base.crt" +OS_ENV_CLIENT_CERTIFICATE = "os/env/client-cert.pem" +OS_ENV_CLIENT_KEY = "os/env/client-key.pem" OS_ENV_HEADERS = "envHeader1=val1,envHeader2=val2" OS_ENV_TIMEOUT = "30" @@ -58,6 +64,8 @@ def test_constructor_default(self): exporter._endpoint, DEFAULT_ENDPOINT + DEFAULT_TRACES_EXPORT_PATH ) self.assertEqual(exporter._certificate_file, True) + self.assertEqual(exporter._client_certificate_file, None) + self.assertEqual(exporter._client_key_file, None) self.assertEqual(exporter._timeout, DEFAULT_TIMEOUT) self.assertIs(exporter._compression, DEFAULT_COMPRESSION) self.assertEqual(exporter._headers, {}) @@ -76,11 +84,15 @@ def test_constructor_default(self): "os.environ", { OTEL_EXPORTER_OTLP_CERTIFICATE: OS_ENV_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE: OS_ENV_CLIENT_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_KEY: OS_ENV_CLIENT_KEY, OTEL_EXPORTER_OTLP_COMPRESSION: Compression.Gzip.value, OTEL_EXPORTER_OTLP_ENDPOINT: OS_ENV_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS: OS_ENV_HEADERS, OTEL_EXPORTER_OTLP_TIMEOUT: OS_ENV_TIMEOUT, OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE: "traces/certificate.env", + OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE: "traces/client-cert.pem", + OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY: "traces/client-key.pem", OTEL_EXPORTER_OTLP_TRACES_COMPRESSION: Compression.Deflate.value, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "https://traces.endpoint.env", OTEL_EXPORTER_OTLP_TRACES_HEADERS: "tracesEnv1=val1,tracesEnv2=val2,traceEnv3===val3==", @@ -92,6 +104,10 @@ def test_exporter_traces_env_take_priority(self): self.assertEqual(exporter._endpoint, "https://traces.endpoint.env") self.assertEqual(exporter._certificate_file, "traces/certificate.env") + self.assertEqual( + exporter._client_certificate_file, "traces/client-cert.pem" + ) + self.assertEqual(exporter._client_key_file, "traces/client-key.pem") self.assertEqual(exporter._timeout, 40) self.assertIs(exporter._compression, Compression.Deflate) self.assertEqual( @@ -108,6 +124,8 @@ def test_exporter_traces_env_take_priority(self): "os.environ", { OTEL_EXPORTER_OTLP_CERTIFICATE: OS_ENV_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE: OS_ENV_CLIENT_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_KEY: OS_ENV_CLIENT_KEY, OTEL_EXPORTER_OTLP_COMPRESSION: Compression.Gzip.value, OTEL_EXPORTER_OTLP_ENDPOINT: OS_ENV_ENDPOINT, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "https://traces.endpoint.env", @@ -119,6 +137,8 @@ def test_exporter_constructor_take_priority(self): exporter = OTLPSpanExporter( endpoint="example.com/1234", certificate_file="path/to/service.crt", + client_key_file="path/to/client-key.pem", + client_certificate_file="path/to/client-cert.pem", headers={"testHeader1": "value1", "testHeader2": "value2"}, timeout=20, compression=Compression.NoCompression, @@ -127,6 +147,10 @@ def test_exporter_constructor_take_priority(self): self.assertEqual(exporter._endpoint, "example.com/1234") self.assertEqual(exporter._certificate_file, "path/to/service.crt") + self.assertEqual( + exporter._client_certificate_file, "path/to/client-cert.pem" + ) + self.assertEqual(exporter._client_key_file, "path/to/client-key.pem") self.assertEqual(exporter._timeout, 20) self.assertIs(exporter._compression, Compression.NoCompression) self.assertEqual( @@ -139,6 +163,8 @@ def test_exporter_constructor_take_priority(self): "os.environ", { OTEL_EXPORTER_OTLP_CERTIFICATE: OS_ENV_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE: OS_ENV_CLIENT_CERTIFICATE, + OTEL_EXPORTER_OTLP_CLIENT_KEY: OS_ENV_CLIENT_KEY, OTEL_EXPORTER_OTLP_COMPRESSION: Compression.Gzip.value, OTEL_EXPORTER_OTLP_HEADERS: OS_ENV_HEADERS, OTEL_EXPORTER_OTLP_TIMEOUT: OS_ENV_TIMEOUT, @@ -149,6 +175,10 @@ def test_exporter_env(self): exporter = OTLPSpanExporter() self.assertEqual(exporter._certificate_file, OS_ENV_CERTIFICATE) + self.assertEqual( + exporter._client_certificate_file, OS_ENV_CLIENT_CERTIFICATE + ) + self.assertEqual(exporter._client_key_file, OS_ENV_CLIENT_KEY) self.assertEqual(exporter._timeout, int(OS_ENV_TIMEOUT)) self.assertIs(exporter._compression, Compression.Gzip) self.assertEqual( diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py index 99899539a1a..bda4cb60581 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py @@ -411,6 +411,76 @@ TLS credentials of gRPC client for metrics. Should only be used for a secure connection for exporting metrics. """ +OTEL_EXPORTER_OTLP_CLIENT_KEY = "OTEL_EXPORTER_OTLP_CLIENT_KEY" +""" +.. envvar:: OTEL_EXPORTER_OTLP_CLIENT_KEY + +The :envvar:`OTEL_EXPORTER_OTLP_CLIENT_KEY` stores the path to the client private key to use +in mTLS communication in PEM format. +""" + +OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY = "OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY" +""" +.. envvar:: OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY + +The :envvar:`OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY` stores the path to the client private key to use +in mTLS communication in PEM format for traces. +""" + +OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY = "OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY" +""" +.. envvar:: OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY + +The :envvar:`OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY` stores the path to the client private key to use +in mTLS communication in PEM format for metrics. +""" + +OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY = "OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY" +""" +.. envvar:: OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY + +The :envvar:`OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY` stores the path to the client private key to use +in mTLS communication in PEM format for logs. +""" + +OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE = "OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE" +""" +.. envvar:: OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE + +The :envvar:`OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE` stores the path to the client certificate/chain trust for +clients private key to use in mTLS communication in PEM format. +""" + +OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE = ( + "OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE" +) +""" +.. envvar:: OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE + +The :envvar:`OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE` stores the path to the client certificate/chain trust for +clients private key to use in mTLS communication in PEM format for traces. +""" + +OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE = ( + "OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE" +) +""" +.. envvar:: OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE + +The :envvar:`OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE` stores the path to the client certificate/chain trust for +clients private key to use in mTLS communication in PEM format for metrics. +""" + +OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE = ( + "OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE" +) +""" +.. envvar:: OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE + +The :envvar:`OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE` stores the path to the client certificate/chain trust for +clients private key to use in mTLS communication in PEM format for logs. +""" + OTEL_EXPORTER_OTLP_TRACES_HEADERS = "OTEL_EXPORTER_OTLP_TRACES_HEADERS" """ .. envvar:: OTEL_EXPORTER_OTLP_TRACES_HEADERS diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/point.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/point.py index 42420b9008e..473b45ed6b6 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/point.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/point.py @@ -183,8 +183,10 @@ def to_json(self, indent=4) -> str: # pylint: disable=invalid-name -DataT = Union[Sum, Gauge, Histogram] -DataPointT = Union[NumberDataPoint, HistogramDataPoint] +DataT = Union[Sum, Gauge, Histogram, ExponentialHistogram] +DataPointT = Union[ + NumberDataPoint, HistogramDataPoint, ExponentialHistogramDataPoint +] @dataclass(frozen=True) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/__init__.py index 97c31b97ec7..478237cd170 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/__init__.py @@ -43,6 +43,7 @@ __all__ = [ "AggregationTemporality", + "Buckets", "ConsoleMetricExporter", "InMemoryMetricReader", "MetricExporter", @@ -51,6 +52,8 @@ "PeriodicExportingMetricReader", "DataPointT", "DataT", + "ExponentialHistogram", + "ExponentialHistogramDataPoint", "Gauge", "Histogram", "HistogramDataPoint",