Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
1e68c3c
Initial Commit
DylanRussell Jul 16, 2025
06dee51
Fix broken tests
DylanRussell Jul 17, 2025
c500b15
Start writing test
DylanRussell Jul 17, 2025
a6dcd1c
Fix tests
DylanRussell Jul 17, 2025
5ef26f2
Merge branch 'main' of github.com:DylanRussell/opentelemetry-python i…
DylanRussell Jul 18, 2025
2e76e59
Add changelog
DylanRussell Jul 18, 2025
9344ebd
Improne and fix _init_exporter func
DylanRussell Jul 18, 2025
cc89293
Fix desc
DylanRussell Jul 18, 2025
f27856f
Run precommit and update variable
DylanRussell Jul 21, 2025
82d72bb
Update changelog
DylanRussell Jul 21, 2025
2244d5a
Merge branch 'main' into add_cred_envvar
DylanRussell Jul 21, 2025
afaf88d
Merge remote-tracking branch 'origin' into add_cred_envvar
DylanRussell Aug 12, 2025
4881780
Fix_type
DylanRussell Aug 12, 2025
66fb14d
Commit changes
DylanRussell Aug 12, 2025
fcadee2
Add test
DylanRussell Aug 13, 2025
6c2740a
fix envvar and typecheck
DylanRussell Aug 13, 2025
0dc2861
Precommit and constraints
DylanRussell Aug 13, 2025
99a55d4
Move change to exporter
DylanRussell Aug 25, 2025
9bf07a7
Move common code to util.
DylanRussell Aug 25, 2025
ae576c1
Merge branch 'main' into add_cred_envvar
DylanRussell Aug 25, 2025
65f177b
Fix lint err and print statment
DylanRussell Sep 2, 2025
ffeb2d2
Merge branch 'main' into add_cred_envvar
DylanRussell Sep 2, 2025
efe5a94
Update opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/…
DylanRussell Sep 8, 2025
0b21293
Respond to comments
DylanRussell Sep 8, 2025
205efa3
Merge branch 'add_cred_envvar' of github.com:DylanRussell/opentelemet…
DylanRussell Sep 8, 2025
810ec49
Ruff
DylanRussell Sep 8, 2025
9b95dfa
Document the entry points better
DylanRussell Sep 8, 2025
9934efe
Going with factory function approach w/ 2 entry points
DylanRussell Sep 8, 2025
24119fb
Add tests.. Fix tox.ini ?
DylanRussell Sep 8, 2025
813c11c
Add http/grpc env var variants
DylanRussell Sep 8, 2025
97fa836
Respond to comments
DylanRussell Sep 8, 2025
e7027a0
Rename python env var with _
DylanRussell Sep 8, 2025
f06d8db
Precommit
DylanRussell Sep 8, 2025
3ab6126
Commit changes
DylanRussell Sep 8, 2025
3e409cc
add new line
DylanRussell Sep 8, 2025
5632fe0
Fix docs error..
DylanRussell Sep 8, 2025
f0fc0f6
get rid of envvar in docstring
DylanRussell Sep 8, 2025
0e0fb9d
Revert tox.ini changes
DylanRussell Sep 9, 2025
5336c95
Revert tox
DylanRussell Sep 9, 2025
0fcdd7b
Merge branch 'main' into add_cred_envvar
DylanRussell Sep 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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(
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,58 @@
# 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:
return True
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
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -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,
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading