Skip to content

Commit 9fac00d

Browse files
authored
Merge branch 'main' into fix-apilogrecord-serialization
2 parents e0a92b0 + d6c0441 commit 9fac00d

File tree

17 files changed

+439
-11
lines changed

17 files changed

+439
-11
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414

1515
- Add experimental composite samplers
1616
([#4714](https://github.com/open-telemetry/opentelemetry-python/pull/4714))
17+
- Add new environment variables to the SDK `OTEL_PYTHON_EXPORTER_OTLP_{HTTP/GRPC}_{METRICS/TRACES/LOGS}_CREDENTIAL_PROVIDER` that can be used to
18+
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).
1719
- 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.
1820
([#4695](https://github.com/open-telemetry/opentelemetry-python/pull/4695)).
1921
- docs: linked the examples with their github source code location and added Prometheus example
@@ -24,6 +26,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2426
([#4731](https://github.com/open-telemetry/opentelemetry-python/pull/4731))
2527
- opentelemetry-sdk: fix calling Logger.emit with an API LogRecord instance
2628
([#4741](https://github.com/open-telemetry/opentelemetry-python/pull/4741))
29+
- Performance: Cache `importlib_metadata.entry_points`
30+
([#4735](https://github.com/open-telemetry/opentelemetry-python/pull/4735))
2731

2832
## Version 1.36.0/0.57b0 (2025-07-29)
2933

exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/_log_exporter/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from opentelemetry.sdk._logs import LogRecord as SDKLogRecord
3333
from opentelemetry.sdk._logs.export import LogExporter, LogExportResult
3434
from opentelemetry.sdk.environment_variables import (
35+
_OTEL_PYTHON_EXPORTER_OTLP_GRPC_LOGS_CREDENTIAL_PROVIDER,
3536
OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE,
3637
OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE,
3738
OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY,
@@ -73,6 +74,7 @@ def __init__(
7374
):
7475
credentials = _get_credentials(
7576
credentials,
77+
_OTEL_PYTHON_EXPORTER_OTLP_GRPC_LOGS_CREDENTIAL_PROVIDER,
7678
OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE,
7779
OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY,
7880
OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE,

exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
from opentelemetry.proto.resource.v1.resource_pb2 import Resource # noqa: F401
6262
from opentelemetry.sdk._shared_internal import DuplicateFilter
6363
from opentelemetry.sdk.environment_variables import (
64+
_OTEL_PYTHON_EXPORTER_OTLP_GRPC_CREDENTIAL_PROVIDER,
6465
OTEL_EXPORTER_OTLP_CERTIFICATE,
6566
OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE,
6667
OTEL_EXPORTER_OTLP_CLIENT_KEY,
@@ -73,6 +74,7 @@
7374
from opentelemetry.sdk.metrics.export import MetricsData
7475
from opentelemetry.sdk.resources import Resource as SDKResource
7576
from opentelemetry.sdk.trace import ReadableSpan
77+
from opentelemetry.util._importlib_metadata import entry_points
7678
from opentelemetry.util.re import parse_env_headers
7779

7880
_RETRYABLE_ERROR_CODES = frozenset(
@@ -169,12 +171,36 @@ def _load_credentials(
169171

170172
def _get_credentials(
171173
creds: Optional[ChannelCredentials],
174+
credential_entry_point_env_key: str,
172175
certificate_file_env_key: str,
173176
client_key_file_env_key: str,
174177
client_certificate_file_env_key: str,
175178
) -> ChannelCredentials:
176179
if creds is not None:
177180
return creds
181+
_credential_env = environ.get(credential_entry_point_env_key)
182+
if _credential_env:
183+
try:
184+
maybe_channel_creds = next(
185+
iter(
186+
entry_points(
187+
group="opentelemetry_otlp_credential_provider",
188+
name=_credential_env,
189+
)
190+
)
191+
).load()()
192+
except StopIteration:
193+
raise RuntimeError(
194+
f"Requested component '{_credential_env}' not found in "
195+
f"entry point 'opentelemetry_otlp_credential_provider'"
196+
)
197+
if isinstance(maybe_channel_creds, ChannelCredentials):
198+
return maybe_channel_creds
199+
else:
200+
raise RuntimeError(
201+
f"Requested component '{_credential_env}' is of type {type(maybe_channel_creds)}"
202+
f" must be of type `grpc.ChannelCredentials`."
203+
)
178204

179205
certificate_file = environ.get(certificate_file_env_key)
180206
if certificate_file:
@@ -278,15 +304,16 @@ def __init__(
278304
options=self._channel_options,
279305
)
280306
else:
281-
credentials = _get_credentials(
307+
self._credentials = _get_credentials(
282308
credentials,
309+
_OTEL_PYTHON_EXPORTER_OTLP_GRPC_CREDENTIAL_PROVIDER,
283310
OTEL_EXPORTER_OTLP_CERTIFICATE,
284311
OTEL_EXPORTER_OTLP_CLIENT_KEY,
285312
OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE,
286313
)
287314
self._channel = secure_channel(
288315
self._endpoint,
289-
credentials,
316+
self._credentials,
290317
compression=compression,
291318
options=self._channel_options,
292319
)

exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/metric_exporter/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
)
4444
from opentelemetry.proto.metrics.v1 import metrics_pb2 as pb2 # noqa: F401
4545
from opentelemetry.sdk.environment_variables import (
46+
_OTEL_PYTHON_EXPORTER_OTLP_GRPC_METRICS_CREDENTIAL_PROVIDER,
4647
OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE,
4748
OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE,
4849
OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY,
@@ -118,6 +119,7 @@ def __init__(
118119
):
119120
credentials = _get_credentials(
120121
credentials,
122+
_OTEL_PYTHON_EXPORTER_OTLP_GRPC_METRICS_CREDENTIAL_PROVIDER,
121123
OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE,
122124
OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY,
123125
OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE,

exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/trace_exporter/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
Span as CollectorSpan,
4747
)
4848
from opentelemetry.sdk.environment_variables import (
49+
_OTEL_PYTHON_EXPORTER_OTLP_GRPC_TRACES_CREDENTIAL_PROVIDER,
4950
OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE,
5051
OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE,
5152
OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY,
@@ -106,6 +107,7 @@ def __init__(
106107
):
107108
credentials = _get_credentials(
108109
credentials,
110+
_OTEL_PYTHON_EXPORTER_OTLP_GRPC_TRACES_CREDENTIAL_PROVIDER,
109111
OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE,
110112
OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY,
111113
OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE,

exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_exporter_mixin.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from google.rpc.error_details_pb2 import ( # pylint: disable=no-name-in-module
2929
RetryInfo,
3030
)
31-
from grpc import Compression, StatusCode, server
31+
from grpc import ChannelCredentials, Compression, StatusCode, server
3232

3333
from opentelemetry.exporter.otlp.proto.common.trace_encoder import (
3434
encode_spans,
@@ -49,6 +49,7 @@
4949
add_TraceServiceServicer_to_server,
5050
)
5151
from opentelemetry.sdk.environment_variables import (
52+
_OTEL_PYTHON_EXPORTER_OTLP_GRPC_CREDENTIAL_PROVIDER,
5253
OTEL_EXPORTER_OTLP_COMPRESSION,
5354
)
5455
from opentelemetry.sdk.trace import ReadableSpan, _Span
@@ -60,6 +61,15 @@
6061
logger = getLogger(__name__)
6162

6263

64+
class IterEntryPoint:
65+
def __init__(self, name, class_type):
66+
self.name = name
67+
self.class_type = class_type
68+
69+
def load(self):
70+
return self.class_type
71+
72+
6373
# The below tests use this test SpanExporter and Spans, but are testing the
6474
# underlying behavior in the mixin. A MetricExporter or LogExporter could
6575
# just as easily be used.
@@ -276,6 +286,55 @@ def test_otlp_exporter_otlp_compression_unspecified(
276286
),
277287
)
278288

289+
@patch.dict(
290+
"os.environ",
291+
{
292+
_OTEL_PYTHON_EXPORTER_OTLP_GRPC_CREDENTIAL_PROVIDER: "credential_provider"
293+
},
294+
)
295+
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.entry_points")
296+
def test_that_credential_gets_passed_to_exporter(self, mock_entry_points):
297+
credential = ChannelCredentials(None)
298+
299+
def f():
300+
return credential
301+
302+
mock_entry_points.configure_mock(
303+
return_value=[IterEntryPoint("custom_credential", f)]
304+
)
305+
exporter = OTLPSpanExporterForTesting(insecure=False)
306+
# pylint: disable=protected-access
307+
assert exporter._credentials is credential
308+
309+
@patch.dict(
310+
"os.environ",
311+
{
312+
_OTEL_PYTHON_EXPORTER_OTLP_GRPC_CREDENTIAL_PROVIDER: "credential_provider"
313+
},
314+
)
315+
def test_that_missing_entry_point_raises_exception(self):
316+
with self.assertRaises(RuntimeError):
317+
OTLPSpanExporterForTesting(insecure=False)
318+
319+
@patch.dict(
320+
"os.environ",
321+
{
322+
_OTEL_PYTHON_EXPORTER_OTLP_GRPC_CREDENTIAL_PROVIDER: "credential_provider"
323+
},
324+
)
325+
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.entry_points")
326+
def test_that_entry_point_returning_bad_type_raises_exception(
327+
self, mock_entry_points
328+
):
329+
def f():
330+
return 1
331+
332+
mock_entry_points.configure_mock(
333+
return_value=[IterEntryPoint("custom_credential", f)]
334+
)
335+
with self.assertRaises(RuntimeError):
336+
OTLPSpanExporterForTesting(insecure=False)
337+
279338
# pylint: disable=no-self-use, disable=unused-argument
280339
@patch(
281340
"opentelemetry.exporter.otlp.proto.grpc.exporter.ssl_channel_credentials"

exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_common/__init__.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,58 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
from os import environ
16+
from typing import Literal, Optional
17+
1518
import requests
1619

20+
from opentelemetry.sdk.environment_variables import (
21+
_OTEL_PYTHON_EXPORTER_OTLP_HTTP_CREDENTIAL_PROVIDER,
22+
_OTEL_PYTHON_EXPORTER_OTLP_HTTP_LOGS_CREDENTIAL_PROVIDER,
23+
_OTEL_PYTHON_EXPORTER_OTLP_HTTP_METRICS_CREDENTIAL_PROVIDER,
24+
_OTEL_PYTHON_EXPORTER_OTLP_HTTP_TRACES_CREDENTIAL_PROVIDER,
25+
)
26+
from opentelemetry.util._importlib_metadata import entry_points
27+
1728

1829
def _is_retryable(resp: requests.Response) -> bool:
1930
if resp.status_code == 408:
2031
return True
2132
if resp.status_code >= 500 and resp.status_code <= 599:
2233
return True
2334
return False
35+
36+
37+
def _load_session_from_envvar(
38+
cred_envvar: Literal[
39+
_OTEL_PYTHON_EXPORTER_OTLP_HTTP_LOGS_CREDENTIAL_PROVIDER,
40+
_OTEL_PYTHON_EXPORTER_OTLP_HTTP_TRACES_CREDENTIAL_PROVIDER,
41+
_OTEL_PYTHON_EXPORTER_OTLP_HTTP_METRICS_CREDENTIAL_PROVIDER,
42+
],
43+
) -> Optional[requests.Session]:
44+
_credential_env = environ.get(
45+
_OTEL_PYTHON_EXPORTER_OTLP_HTTP_CREDENTIAL_PROVIDER
46+
) or environ.get(cred_envvar)
47+
if _credential_env:
48+
try:
49+
maybe_session = next(
50+
iter(
51+
entry_points(
52+
group="opentelemetry_otlp_credential_provider",
53+
name=_credential_env,
54+
)
55+
)
56+
).load()()
57+
except StopIteration:
58+
raise RuntimeError(
59+
f"Requested component '{_credential_env}' not found in "
60+
f"entry point 'opentelemetry_otlp_credential_provider'"
61+
)
62+
if isinstance(maybe_session, requests.Session):
63+
return maybe_session
64+
else:
65+
raise RuntimeError(
66+
f"Requested component '{_credential_env}' is of type {type(maybe_session)}"
67+
f" must be of type `requests.Session`."
68+
)
69+
return None

exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
)
3333
from opentelemetry.exporter.otlp.proto.http._common import (
3434
_is_retryable,
35+
_load_session_from_envvar,
3536
)
3637
from opentelemetry.sdk._logs import LogData
3738
from opentelemetry.sdk._logs.export import (
@@ -40,6 +41,7 @@
4041
)
4142
from opentelemetry.sdk._shared_internal import DuplicateFilter
4243
from opentelemetry.sdk.environment_variables import (
44+
_OTEL_PYTHON_EXPORTER_OTLP_HTTP_LOGS_CREDENTIAL_PROVIDER,
4345
OTEL_EXPORTER_OTLP_CERTIFICATE,
4446
OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE,
4547
OTEL_EXPORTER_OTLP_CLIENT_KEY,
@@ -120,7 +122,14 @@ def __init__(
120122
)
121123
)
122124
self._compression = compression or _compression_from_env()
123-
self._session = session or requests.Session()
125+
self._session = (
126+
session
127+
or _load_session_from_envvar(
128+
_OTEL_PYTHON_EXPORTER_OTLP_HTTP_LOGS_CREDENTIAL_PROVIDER
129+
)
130+
or requests.Session()
131+
)
132+
self._session.headers.update(self._headers)
124133
self._session.headers.update(_OTLP_HTTP_HEADERS)
125134
# let users override our defaults
126135
self._session.headers.update(self._headers)

exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/metric_exporter/__init__.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
)
5050
from opentelemetry.exporter.otlp.proto.http._common import (
5151
_is_retryable,
52+
_load_session_from_envvar,
5253
)
5354
from opentelemetry.proto.collector.metrics.v1.metrics_service_pb2 import ( # noqa: F401
5455
ExportMetricsServiceRequest,
@@ -66,6 +67,7 @@
6667
Resource as PB2Resource,
6768
)
6869
from opentelemetry.sdk.environment_variables import (
70+
_OTEL_PYTHON_EXPORTER_OTLP_HTTP_METRICS_CREDENTIAL_PROVIDER,
6971
OTEL_EXPORTER_OTLP_CERTIFICATE,
7072
OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE,
7173
OTEL_EXPORTER_OTLP_CLIENT_KEY,
@@ -159,7 +161,14 @@ def __init__(
159161
)
160162
)
161163
self._compression = compression or _compression_from_env()
162-
self._session = session or requests.Session()
164+
self._session = (
165+
session
166+
or _load_session_from_envvar(
167+
_OTEL_PYTHON_EXPORTER_OTLP_HTTP_METRICS_CREDENTIAL_PROVIDER
168+
)
169+
or requests.Session()
170+
)
171+
self._session.headers.update(self._headers)
163172
self._session.headers.update(_OTLP_HTTP_HEADERS)
164173
# let users override our defaults
165174
self._session.headers.update(self._headers)

exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/trace_exporter/__init__.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@
3434
)
3535
from opentelemetry.exporter.otlp.proto.http._common import (
3636
_is_retryable,
37+
_load_session_from_envvar,
3738
)
3839
from opentelemetry.sdk.environment_variables import (
40+
_OTEL_PYTHON_EXPORTER_OTLP_HTTP_TRACES_CREDENTIAL_PROVIDER,
3941
OTEL_EXPORTER_OTLP_CERTIFICATE,
4042
OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE,
4143
OTEL_EXPORTER_OTLP_CLIENT_KEY,
@@ -115,7 +117,14 @@ def __init__(
115117
)
116118
)
117119
self._compression = compression or _compression_from_env()
118-
self._session = session or requests.Session()
120+
self._session = (
121+
session
122+
or _load_session_from_envvar(
123+
_OTEL_PYTHON_EXPORTER_OTLP_HTTP_TRACES_CREDENTIAL_PROVIDER
124+
)
125+
or requests.Session()
126+
)
127+
self._session.headers.update(self._headers)
119128
self._session.headers.update(_OTLP_HTTP_HEADERS)
120129
# let users override our defaults
121130
self._session.headers.update(self._headers)

0 commit comments

Comments
 (0)