diff --git a/CHANGELOG.md b/CHANGELOG.md index dd0b0d470c2..06469336811 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add experimental composite samplers ([#4714](https://github.com/open-telemetry/opentelemetry-python/pull/4714)) +- Add new environment variables to the SDK `OTEL_PYTHON_EXPORTER_OTLP_{HTTP/GRPC}_{METRICS/TRACES/LOGS}_CREDENTIAL_PROVIDER` that can be used to +inject a `requests.Session` or `grpc.ChannelCredentials` object into OTLP exporters created during auto instrumentation [#4689](https://github.com/open-telemetry/opentelemetry-python/pull/4689). - Filter duplicate logs out of some internal `logger`'s logs on the export logs path that might otherwise endlessly log or cause a recursion depth exceeded issue in cases where logging itself results in an exception. ([#4695](https://github.com/open-telemetry/opentelemetry-python/pull/4695)). - docs: linked the examples with their github source code location and added Prometheus example 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 70f3df444a4..95bb5ded2b7 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 @@ -32,6 +32,7 @@ from opentelemetry.sdk._logs import LogRecord as SDKLogRecord from opentelemetry.sdk._logs.export import LogExporter, LogExportResult from opentelemetry.sdk.environment_variables import ( + _OTEL_PYTHON_EXPORTER_OTLP_GRPC_LOGS_CREDENTIAL_PROVIDER, OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE, OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE, OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY, @@ -73,6 +74,7 @@ def __init__( ): credentials = _get_credentials( credentials, + _OTEL_PYTHON_EXPORTER_OTLP_GRPC_LOGS_CREDENTIAL_PROVIDER, OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE, OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY, OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE, 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 e61ed8baeed..477cc1a6417 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,7 @@ from opentelemetry.proto.resource.v1.resource_pb2 import Resource # noqa: F401 from opentelemetry.sdk._shared_internal import DuplicateFilter from opentelemetry.sdk.environment_variables import ( + _OTEL_PYTHON_EXPORTER_OTLP_GRPC_CREDENTIAL_PROVIDER, OTEL_EXPORTER_OTLP_CERTIFICATE, OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, OTEL_EXPORTER_OTLP_CLIENT_KEY, @@ -73,6 +74,7 @@ from opentelemetry.sdk.metrics.export import MetricsData from opentelemetry.sdk.resources import Resource as SDKResource from opentelemetry.sdk.trace import ReadableSpan +from opentelemetry.util._importlib_metadata import entry_points from opentelemetry.util.re import parse_env_headers _RETRYABLE_ERROR_CODES = frozenset( @@ -169,12 +171,36 @@ def _load_credentials( def _get_credentials( creds: Optional[ChannelCredentials], + credential_entry_point_env_key: str, 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 + _credential_env = environ.get(credential_entry_point_env_key) + if _credential_env: + try: + maybe_channel_creds = next( + iter( + entry_points( + group="opentelemetry_otlp_credential_provider", + name=_credential_env, + ) + ) + ).load()() + except StopIteration: + raise RuntimeError( + f"Requested component '{_credential_env}' not found in " + f"entry point 'opentelemetry_otlp_credential_provider'" + ) + if isinstance(maybe_channel_creds, ChannelCredentials): + return maybe_channel_creds + else: + raise RuntimeError( + f"Requested component '{_credential_env}' is of type {type(maybe_channel_creds)}" + f" must be of type `grpc.ChannelCredentials`." + ) certificate_file = environ.get(certificate_file_env_key) if certificate_file: @@ -278,15 +304,16 @@ def __init__( options=self._channel_options, ) else: - credentials = _get_credentials( + self._credentials = _get_credentials( credentials, + _OTEL_PYTHON_EXPORTER_OTLP_GRPC_CREDENTIAL_PROVIDER, OTEL_EXPORTER_OTLP_CERTIFICATE, OTEL_EXPORTER_OTLP_CLIENT_KEY, OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, ) self._channel = secure_channel( self._endpoint, - credentials, + self._credentials, compression=compression, options=self._channel_options, ) 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 d1bfa4de94b..be9e246a4c1 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 @@ -43,6 +43,7 @@ ) from opentelemetry.proto.metrics.v1 import metrics_pb2 as pb2 # noqa: F401 from opentelemetry.sdk.environment_variables import ( + _OTEL_PYTHON_EXPORTER_OTLP_GRPC_METRICS_CREDENTIAL_PROVIDER, OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE, OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE, OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY, @@ -118,6 +119,7 @@ def __init__( ): credentials = _get_credentials( credentials, + _OTEL_PYTHON_EXPORTER_OTLP_GRPC_METRICS_CREDENTIAL_PROVIDER, OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE, OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY, OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE, 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 0dbdb22bc50..e8f9db3f584 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 @@ -46,6 +46,7 @@ Span as CollectorSpan, ) from opentelemetry.sdk.environment_variables import ( + _OTEL_PYTHON_EXPORTER_OTLP_GRPC_TRACES_CREDENTIAL_PROVIDER, OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE, OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE, OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY, @@ -106,6 +107,7 @@ def __init__( ): credentials = _get_credentials( credentials, + _OTEL_PYTHON_EXPORTER_OTLP_GRPC_TRACES_CREDENTIAL_PROVIDER, OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE, OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY, OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE, diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_exporter_mixin.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_exporter_mixin.py index aef52fbc4a7..4358506ff5f 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_exporter_mixin.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_exporter_mixin.py @@ -28,7 +28,7 @@ from google.rpc.error_details_pb2 import ( # pylint: disable=no-name-in-module RetryInfo, ) -from grpc import Compression, StatusCode, server +from grpc import ChannelCredentials, Compression, StatusCode, server from opentelemetry.exporter.otlp.proto.common.trace_encoder import ( encode_spans, @@ -49,6 +49,7 @@ add_TraceServiceServicer_to_server, ) from opentelemetry.sdk.environment_variables import ( + _OTEL_PYTHON_EXPORTER_OTLP_GRPC_CREDENTIAL_PROVIDER, OTEL_EXPORTER_OTLP_COMPRESSION, ) from opentelemetry.sdk.trace import ReadableSpan, _Span @@ -60,6 +61,15 @@ logger = getLogger(__name__) +class IterEntryPoint: + def __init__(self, name, class_type): + self.name = name + self.class_type = class_type + + def load(self): + return self.class_type + + # The below tests use this test SpanExporter and Spans, but are testing the # underlying behavior in the mixin. A MetricExporter or LogExporter could # just as easily be used. @@ -276,6 +286,55 @@ def test_otlp_exporter_otlp_compression_unspecified( ), ) + @patch.dict( + "os.environ", + { + _OTEL_PYTHON_EXPORTER_OTLP_GRPC_CREDENTIAL_PROVIDER: "credential_provider" + }, + ) + @patch("opentelemetry.exporter.otlp.proto.grpc.exporter.entry_points") + def test_that_credential_gets_passed_to_exporter(self, mock_entry_points): + credential = ChannelCredentials(None) + + def f(): + return credential + + mock_entry_points.configure_mock( + return_value=[IterEntryPoint("custom_credential", f)] + ) + exporter = OTLPSpanExporterForTesting(insecure=False) + # pylint: disable=protected-access + assert exporter._credentials is credential + + @patch.dict( + "os.environ", + { + _OTEL_PYTHON_EXPORTER_OTLP_GRPC_CREDENTIAL_PROVIDER: "credential_provider" + }, + ) + def test_that_missing_entry_point_raises_exception(self): + with self.assertRaises(RuntimeError): + OTLPSpanExporterForTesting(insecure=False) + + @patch.dict( + "os.environ", + { + _OTEL_PYTHON_EXPORTER_OTLP_GRPC_CREDENTIAL_PROVIDER: "credential_provider" + }, + ) + @patch("opentelemetry.exporter.otlp.proto.grpc.exporter.entry_points") + def test_that_entry_point_returning_bad_type_raises_exception( + self, mock_entry_points + ): + def f(): + return 1 + + mock_entry_points.configure_mock( + return_value=[IterEntryPoint("custom_credential", f)] + ) + with self.assertRaises(RuntimeError): + OTLPSpanExporterForTesting(insecure=False) + # pylint: disable=no-self-use, disable=unused-argument @patch( "opentelemetry.exporter.otlp.proto.grpc.exporter.ssl_channel_credentials" diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_common/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_common/__init__.py index b1ed46d28b7..0658d0968e6 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_common/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_common/__init__.py @@ -12,8 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. +from os import environ +from typing import Literal, Optional + import requests +from opentelemetry.sdk.environment_variables import ( + _OTEL_PYTHON_EXPORTER_OTLP_HTTP_CREDENTIAL_PROVIDER, + _OTEL_PYTHON_EXPORTER_OTLP_HTTP_LOGS_CREDENTIAL_PROVIDER, + _OTEL_PYTHON_EXPORTER_OTLP_HTTP_METRICS_CREDENTIAL_PROVIDER, + _OTEL_PYTHON_EXPORTER_OTLP_HTTP_TRACES_CREDENTIAL_PROVIDER, +) +from opentelemetry.util._importlib_metadata import entry_points + def _is_retryable(resp: requests.Response) -> bool: if resp.status_code == 408: @@ -21,3 +32,38 @@ def _is_retryable(resp: requests.Response) -> bool: if resp.status_code >= 500 and resp.status_code <= 599: return True return False + + +def _load_session_from_envvar( + cred_envvar: Literal[ + _OTEL_PYTHON_EXPORTER_OTLP_HTTP_LOGS_CREDENTIAL_PROVIDER, + _OTEL_PYTHON_EXPORTER_OTLP_HTTP_TRACES_CREDENTIAL_PROVIDER, + _OTEL_PYTHON_EXPORTER_OTLP_HTTP_METRICS_CREDENTIAL_PROVIDER, + ], +) -> Optional[requests.Session]: + _credential_env = environ.get( + _OTEL_PYTHON_EXPORTER_OTLP_HTTP_CREDENTIAL_PROVIDER + ) or environ.get(cred_envvar) + if _credential_env: + try: + maybe_session = next( + iter( + entry_points( + group="opentelemetry_otlp_credential_provider", + name=_credential_env, + ) + ) + ).load()() + except StopIteration: + raise RuntimeError( + f"Requested component '{_credential_env}' not found in " + f"entry point 'opentelemetry_otlp_credential_provider'" + ) + if isinstance(maybe_session, requests.Session): + return maybe_session + else: + raise RuntimeError( + f"Requested component '{_credential_env}' is of type {type(maybe_session)}" + f" must be of type `requests.Session`." + ) + return None 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 3b7e25f6571..2afdf660025 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 @@ -32,6 +32,7 @@ ) from opentelemetry.exporter.otlp.proto.http._common import ( _is_retryable, + _load_session_from_envvar, ) from opentelemetry.sdk._logs import LogData from opentelemetry.sdk._logs.export import ( @@ -40,6 +41,7 @@ ) from opentelemetry.sdk._shared_internal import DuplicateFilter from opentelemetry.sdk.environment_variables import ( + _OTEL_PYTHON_EXPORTER_OTLP_HTTP_LOGS_CREDENTIAL_PROVIDER, OTEL_EXPORTER_OTLP_CERTIFICATE, OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, OTEL_EXPORTER_OTLP_CLIENT_KEY, @@ -120,7 +122,14 @@ def __init__( ) ) self._compression = compression or _compression_from_env() - self._session = session or requests.Session() + self._session = ( + session + or _load_session_from_envvar( + _OTEL_PYTHON_EXPORTER_OTLP_HTTP_LOGS_CREDENTIAL_PROVIDER + ) + or requests.Session() + ) + self._session.headers.update(self._headers) self._session.headers.update(_OTLP_HTTP_HEADERS) # let users override our defaults self._session.headers.update(self._headers) 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 bcf4c297395..c6d657e7ae0 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 @@ -49,6 +49,7 @@ ) from opentelemetry.exporter.otlp.proto.http._common import ( _is_retryable, + _load_session_from_envvar, ) from opentelemetry.proto.collector.metrics.v1.metrics_service_pb2 import ( # noqa: F401 ExportMetricsServiceRequest, @@ -66,6 +67,7 @@ Resource as PB2Resource, ) from opentelemetry.sdk.environment_variables import ( + _OTEL_PYTHON_EXPORTER_OTLP_HTTP_METRICS_CREDENTIAL_PROVIDER, OTEL_EXPORTER_OTLP_CERTIFICATE, OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, OTEL_EXPORTER_OTLP_CLIENT_KEY, @@ -159,7 +161,14 @@ def __init__( ) ) self._compression = compression or _compression_from_env() - self._session = session or requests.Session() + self._session = ( + session + or _load_session_from_envvar( + _OTEL_PYTHON_EXPORTER_OTLP_HTTP_METRICS_CREDENTIAL_PROVIDER + ) + or requests.Session() + ) + self._session.headers.update(self._headers) self._session.headers.update(_OTLP_HTTP_HEADERS) # let users override our defaults self._session.headers.update(self._headers) 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 1ed0400048d..055e829daba 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 @@ -34,8 +34,10 @@ ) from opentelemetry.exporter.otlp.proto.http._common import ( _is_retryable, + _load_session_from_envvar, ) from opentelemetry.sdk.environment_variables import ( + _OTEL_PYTHON_EXPORTER_OTLP_HTTP_TRACES_CREDENTIAL_PROVIDER, OTEL_EXPORTER_OTLP_CERTIFICATE, OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, OTEL_EXPORTER_OTLP_CLIENT_KEY, @@ -115,7 +117,14 @@ def __init__( ) ) self._compression = compression or _compression_from_env() - self._session = session or requests.Session() + self._session = ( + session + or _load_session_from_envvar( + _OTEL_PYTHON_EXPORTER_OTLP_HTTP_TRACES_CREDENTIAL_PROVIDER + ) + or requests.Session() + ) + self._session.headers.update(self._headers) self._session.headers.update(_OTLP_HTTP_HEADERS) # let users override our defaults self._session.headers.update(self._headers) diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/tests/_common/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-http/tests/_common/__init__.py new file mode 100644 index 00000000000..08801b68afd --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-http/tests/_common/__init__.py @@ -0,0 +1,24 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# pylint: disable=protected-access + + +class IterEntryPoint: + def __init__(self, name, class_type): + self.name = name + self.class_type = class_type + + def load(self): + return self.class_type 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 2453dc2dab1..d7a5bed2d4d 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 @@ -35,6 +35,7 @@ ) from opentelemetry.exporter.otlp.proto.http.version import __version__ from opentelemetry.sdk.environment_variables import ( + _OTEL_PYTHON_EXPORTER_OTLP_HTTP_METRICS_CREDENTIAL_PROVIDER, OTEL_EXPORTER_OTLP_CERTIFICATE, OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, OTEL_EXPORTER_OTLP_CLIENT_KEY, @@ -77,6 +78,8 @@ ) from opentelemetry.test.metrictestutil import _generate_sum +from .._common import IterEntryPoint + OS_ENV_ENDPOINT = "os.env.base" OS_ENV_CERTIFICATE = "os/env/base.crt" OS_ENV_CLIENT_CERTIFICATE = "os/env/client-cert.pem" @@ -153,9 +156,19 @@ def test_constructor_default(self): OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: "https://metrics.endpoint.env", OTEL_EXPORTER_OTLP_METRICS_HEADERS: "metricsEnv1=val1,metricsEnv2=val2,metricEnv3===val3==,User-agent=metrics-user-agent", OTEL_EXPORTER_OTLP_METRICS_TIMEOUT: "40", + _OTEL_PYTHON_EXPORTER_OTLP_HTTP_METRICS_CREDENTIAL_PROVIDER: "credential_provider", }, ) - def test_exporter_metrics_env_take_priority(self): + @patch("opentelemetry.exporter.otlp.proto.http._common.entry_points") + def test_exporter_metrics_env_take_priority(self, mock_entry_points): + credential = Session() + + def f(): + return credential + + mock_entry_points.configure_mock( + return_value=[IterEntryPoint("custom_credential", f)] + ) exporter = OTLPMetricExporter() self.assertEqual(exporter._endpoint, "https://metrics.endpoint.env") 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 269b1143a81..d136e09ffdc 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 @@ -43,6 +43,7 @@ from opentelemetry.sdk._logs import LogRecord as SDKLogRecord from opentelemetry.sdk._logs.export import LogExportResult from opentelemetry.sdk.environment_variables import ( + _OTEL_PYTHON_EXPORTER_OTLP_HTTP_LOGS_CREDENTIAL_PROVIDER, OTEL_EXPORTER_OTLP_CERTIFICATE, OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, OTEL_EXPORTER_OTLP_CLIENT_KEY, @@ -67,6 +68,8 @@ set_span_in_context, ) +from ._common import IterEntryPoint + ENV_ENDPOINT = "http://localhost.env:8080/" ENV_CERTIFICATE = "/etc/base.crt" ENV_CLIENT_CERTIFICATE = "/etc/client-cert.pem" @@ -116,9 +119,19 @@ def test_constructor_default(self): OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: "https://logs.endpoint.env", OTEL_EXPORTER_OTLP_LOGS_HEADERS: "logsEnv1=val1,logsEnv2=val2,logsEnv3===val3==,User-agent=LogsUserAgent", OTEL_EXPORTER_OTLP_LOGS_TIMEOUT: "40", + _OTEL_PYTHON_EXPORTER_OTLP_HTTP_LOGS_CREDENTIAL_PROVIDER: "credential_provider", }, ) - def test_exporter_metrics_env_take_priority(self): + @patch("opentelemetry.exporter.otlp.proto.http._common.entry_points") + def test_exporter_logs_env_take_priority(self, mock_entry_points): + credential = Session() + + def f(): + return credential + + mock_entry_points.configure_mock( + return_value=[IterEntryPoint("custom_credential", f)] + ) exporter = OTLPLogExporter() self.assertEqual(exporter._endpoint, "https://logs.endpoint.env") @@ -138,6 +151,7 @@ def test_exporter_metrics_env_take_priority(self): "user-agent": "LogsUserAgent", }, ) + self.assertIs(exporter._session, credential) self.assertIsInstance(exporter._session, requests.Session) self.assertEqual( exporter._session.headers.get("User-Agent"), @@ -148,6 +162,35 @@ def test_exporter_metrics_env_take_priority(self): "application/x-protobuf", ) + @patch.dict( + "os.environ", + { + _OTEL_PYTHON_EXPORTER_OTLP_HTTP_LOGS_CREDENTIAL_PROVIDER: "provider_without_entry_point", + }, + ) + @patch("opentelemetry.exporter.otlp.proto.http._common.entry_points") + def test_exception_raised_when_entrypoint_returns_wrong_type( + self, mock_entry_points + ): + def f(): + return 1 + + mock_entry_points.configure_mock( + return_value=[IterEntryPoint("custom_credential", f)] + ) + with self.assertRaises(RuntimeError): + OTLPLogExporter() + + @patch.dict( + "os.environ", + { + _OTEL_PYTHON_EXPORTER_OTLP_HTTP_LOGS_CREDENTIAL_PROVIDER: "provider_without_entry_point", + }, + ) + def test_exception_raised_when_entrypoint_does_not_exist(self): + with self.assertRaises(RuntimeError): + OTLPLogExporter() + @patch.dict( "os.environ", { 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 f01a9efc91a..2d6dea71de5 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 @@ -32,6 +32,7 @@ ) from opentelemetry.exporter.otlp.proto.http.version import __version__ from opentelemetry.sdk.environment_variables import ( + _OTEL_PYTHON_EXPORTER_OTLP_HTTP_TRACES_CREDENTIAL_PROVIDER, OTEL_EXPORTER_OTLP_CERTIFICATE, OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, OTEL_EXPORTER_OTLP_CLIENT_KEY, @@ -50,6 +51,8 @@ from opentelemetry.sdk.trace import _Span from opentelemetry.sdk.trace.export import SpanExportResult +from ._common import IterEntryPoint + OS_ENV_ENDPOINT = "os.env.base" OS_ENV_CERTIFICATE = "os/env/base.crt" OS_ENV_CLIENT_CERTIFICATE = "os/env/client-cert.pem" @@ -110,9 +113,19 @@ def test_constructor_default(self): OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "https://traces.endpoint.env", OTEL_EXPORTER_OTLP_TRACES_HEADERS: "tracesEnv1=val1,tracesEnv2=val2,traceEnv3===val3==,User-agent=TraceUserAgent", OTEL_EXPORTER_OTLP_TRACES_TIMEOUT: "40", + _OTEL_PYTHON_EXPORTER_OTLP_HTTP_TRACES_CREDENTIAL_PROVIDER: "credential_provider", }, ) - def test_exporter_traces_env_take_priority(self): + @patch("opentelemetry.exporter.otlp.proto.http._common.entry_points") + def test_exporter_traces_env_take_priority(self, mock_entry_point): + credential = Session() + + def f(): + return credential + + mock_entry_point.configure_mock( + return_value=[IterEntryPoint("custom_credential", f)] + ) exporter = OTLPSpanExporter() self.assertEqual(exporter._endpoint, "https://traces.endpoint.env") @@ -132,6 +145,7 @@ def test_exporter_traces_env_take_priority(self): "user-agent": "TraceUserAgent", }, ) + self.assertIs(exporter._session, credential) self.assertIsInstance(exporter._session, requests.Session) self.assertEqual( exporter._session.headers.get("Content-Type"), diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py index 23b634fcd85..5baf5fcd55d 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py @@ -394,6 +394,152 @@ A scheme of https indicates a secure connection and takes precedence over this configuration setting. """ +_OTEL_PYTHON_EXPORTER_OTLP_GRPC_LOGS_CREDENTIAL_PROVIDER = ( + "OTEL_PYTHON_EXPORTER_OTLP_GRPC_LOGS_CREDENTIAL_PROVIDER" +) +""" +.. envvar:: OTEL_PYTHON_EXPORTER_OTLP_GRPC_LOGS_CREDENTIAL_PROVIDER + +The :envvar:`OTEL_PYTHON_EXPORTER_OTLP_GRPC_LOGS_CREDENTIAL_PROVIDER` provides `grpc.ChannelCredentials` to the grpc OTLP Log exporter, +Entry point providers should implement the following: + +.. code-block:: python + + import grpc + + # Add a reference to this function under the `opentelemetry_otlp_credential_provider` entry point. + def channel_credential_provider() -> grpc.ChannelCredentials: + +Note: This environment variable is experimental and subject to change. +""" + +_OTEL_PYTHON_EXPORTER_OTLP_HTTP_LOGS_CREDENTIAL_PROVIDER = ( + "OTEL_PYTHON_EXPORTER_OTLP_HTTP_LOGS_CREDENTIAL_PROVIDER" +) +""" +.. envvar:: OTEL_PYTHON_EXPORTER_OTLP_HTTP_LOGS_CREDENTIAL_PROVIDER + +The :envvar:`OTEL_PYTHON_EXPORTER_OTLP_HTTP_LOGS_CREDENTIAL_PROVIDER` provides `requests.Session` for the HTTP OTLP Log exporter. +Entry point providers should implement the following: + +.. code-block:: python + + import requests + + # Add a reference to this function under the `opentelemetry_otlp_credential_provider` entry point. + def request_session_provder() -> requests.Session: + +Note: This environment variable is experimental and subject to change. +""" +_OTEL_PYTHON_EXPORTER_OTLP_HTTP_CREDENTIAL_PROVIDER = ( + "OTEL_PYTHON_EXPORTER_OTLP_HTTP_CREDENTIAL_PROVIDER" +) +""" +.. envvar:: OTEL_PYTHON_EXPORTER_OTLP_HTTP_CREDENTIAL_PROVIDER + +The :envvar:`OTEL_PYTHON_EXPORTER_OTLP_HTTP_CREDENTIAL_PROVIDER` provides `requests.Session` for all HTTP OTLP exporters. +Entry point providers should implement the following: + +.. code-block:: python + + import requests + + # Add a reference to this function under the `opentelemetry_otlp_credential_provider` entry point. + def request_session_provder() -> requests.Session: + +Note: This environment variable is experimental and subject to change. +""" +_OTEL_PYTHON_EXPORTER_OTLP_GRPC_CREDENTIAL_PROVIDER = ( + "OTEL_PYTHON_EXPORTER_OTLP_GRPC_CREDENTIAL_PROVIDER" +) +""" +.. envvar:: OTEL_PYTHON_EXPORTER_OTLP_GRPC_CREDENTIAL_PROVIDER + +The :envvar:`OTEL_PYTHON_EXPORTER_OTLP_GRPC_CREDENTIAL_PROVIDER` provides `grpc.ChannelCredentials` for all GRPC OTLP exporters. +Entry point providers should implement the following: + +.. code-block:: python + + import grpc + + # Add a reference to this function under the `opentelemetry_otlp_credential_provider` entry point. + def channel_credential_provider() -> grpc.ChannelCredentials: + +Note: This environment variable is experimental and subject to change. +""" +_OTEL_PYTHON_EXPORTER_OTLP_HTTP_TRACES_CREDENTIAL_PROVIDER = ( + "OTEL_PYTHON_EXPORTER_OTLP_HTTP_TRACES_CREDENTIAL_PROVIDER" +) +""" +.. envvar:: OTEL_PYTHON_EXPORTER_OTLP_HTTP_TRACES_CREDENTIAL_PROVIDER + +The :envvar:`OTEL_PYTHON_EXPORTER_OTLP_HTTP_TRACES_CREDENTIAL_PROVIDER` provides `requests.Session` to the HTTP OTLP Span exporter. +Entry point providers should implement the following: + +.. code-block:: python + + import requests + + # Add a reference to this function under the `opentelemetry_otlp_credential_provider` entry point. + def request_session_provder() -> requests.Session: + +Note: This environment variable is experimental and subject to change. +""" +_OTEL_PYTHON_EXPORTER_OTLP_GRPC_TRACES_CREDENTIAL_PROVIDER = ( + "OTEL_PYTHON_EXPORTER_OTLP_GRPC_TRACES_CREDENTIAL_PROVIDER" +) +""" +.. envvar:: OTEL_PYTHON_EXPORTER_OTLP_GRPC_TRACES_CREDENTIAL_PROVIDER + +The :envvar:`OTEL_PYTHON_EXPORTER_OTLP_GRPC_TRACES_CREDENTIAL_PROVIDER` provides `grpc.ChannelCredentials` to the GRPC OTLP Span exporter. +Entry point providers should implement the following: + +.. code-block:: python + + import grpc + + # Add a reference to this function under the `opentelemetry_otlp_credential_provider` entry point. + def channel_credential_provider() -> grpc.ChannelCredentials: + +Note: This environment variable is experimental and subject to change. +""" +_OTEL_PYTHON_EXPORTER_OTLP_HTTP_METRICS_CREDENTIAL_PROVIDER = ( + "OTEL_PYTHON_EXPORTER_OTLP_HTTP_METRICS_CREDENTIAL_PROVIDER" +) +""" +.. envvar:: OTEL_PYTHON_EXPORTER_OTLP_HTTP_METRICS_CREDENTIAL_PROVIDER + +The :envvar:`OTEL_PYTHON_EXPORTER_OTLP_HTTP_METRICS_CREDENTIAL_PROVIDER` provides `requests.Session` to the HTTP OTLP Metric exporter. +Entry point providers should implement the following: + +.. code-block:: python + + import requests + + # Add a reference to this function under the `opentelemetry_otlp_credential_provider` entry point. + def request_session_provder() -> requests.Session: + +Note: This environment variable is experimental and subject to change. +""" +_OTEL_PYTHON_EXPORTER_OTLP_GRPC_METRICS_CREDENTIAL_PROVIDER = ( + "OTEL_PYTHON_EXPORTER_OTLP_GRPC_METRICS_CREDENTIAL_PROVIDER" +) +""" +.. envvar:: OTEL_PYTHON_EXPORTER_OTLP_GRPC_METRICS_CREDENTIAL_PROVIDER + +The :envvar:`OTEL_PYTHON_EXPORTER_OTLP_GRPC_METRICS_CREDENTIAL_PROVIDER` provides `grpc.ChannelCredentials` to the GRPC OTLP Metric exporter. +Entry point providers should implement the following: + +.. code-block:: python + + import grpc + + # Add a reference to this function under the `opentelemetry_otlp_credential_provider` entry point. + def channel_credential_provider() -> grpc.ChannelCredentials: + +Note: This environment variable is experimental and subject to change. +""" + OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE = "OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE" """ .. envvar:: OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE diff --git a/tox.ini b/tox.ini index b2b1dae85e2..5355cb8dd4a 100644 --- a/tox.ini +++ b/tox.ini @@ -357,3 +357,4 @@ deps = pre-commit commands = pre-commit run --color=always --all-files {posargs} + \ No newline at end of file