Skip to content

Add credentials environment variables to let ChannelCredentials and Session be injected #4689

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

- Add new environment variables to the SDK `OTEL_PYTHON_EXPORTER_OTLP_{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).

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

- Add missing Prometheus exporter documentation
Expand Down
2 changes: 1 addition & 1 deletion dev-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ psutil==5.9.6
GitPython==3.1.41
pre-commit==3.7.0; python_version >= '3.9'
pre-commit==3.5.0; python_version < '3.9'
ruff==0.6.9
ruff==0.6.9
160 changes: 153 additions & 7 deletions opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,23 @@

from __future__ import annotations

import inspect
import logging
import logging.config
import os
from abc import ABC, abstractmethod
from os import environ
from typing import Any, Callable, Mapping, Sequence, Type, Union
from typing import (
Any,
Callable,
Mapping,
MutableMapping,
Optional,
Sequence,
Type,
TypeVar,
Union,
)

from typing_extensions import Literal

Expand All @@ -46,6 +57,10 @@
OTEL_EXPORTER_OTLP_METRICS_PROTOCOL,
OTEL_EXPORTER_OTLP_PROTOCOL,
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL,
OTEL_PYTHON_EXPORTER_OTLP_CREDENTIAL_PROVIDER,
OTEL_PYTHON_EXPORTER_OTLP_LOGS_CREDENTIAL_PROVIDER,
OTEL_PYTHON_EXPORTER_OTLP_METRICS_CREDENTIAL_PROVIDER,
OTEL_PYTHON_EXPORTER_OTLP_TRACES_CREDENTIAL_PROVIDER,
OTEL_TRACES_SAMPLER,
OTEL_TRACES_SAMPLER_ARG,
)
Expand All @@ -64,6 +79,22 @@
from opentelemetry.trace import set_tracer_provider
from opentelemetry.util._importlib_metadata import entry_points

try:
from grpc import ChannelCredentials

_GRPC_IMPORTED = True
except ImportError:
_GRPC_IMPORTED = False

try:
from requests import Session

_REQUESTS_IMPORTED = True
except ImportError:
_REQUESTS_IMPORTED = False

T = TypeVar("T")

_EXPORTER_OTLP = "otlp"
_EXPORTER_OTLP_PROTO_GRPC = "otlp_proto_grpc"
_EXPORTER_OTLP_PROTO_HTTP = "otlp_proto_http"
Expand All @@ -79,6 +110,12 @@
"logs": OTEL_LOGS_EXPORTER,
}

_EXPORTER_CREDENTIAL_BY_SIGNAL_TYPE = {
"traces": OTEL_PYTHON_EXPORTER_OTLP_TRACES_CREDENTIAL_PROVIDER,
"metrics": OTEL_PYTHON_EXPORTER_OTLP_METRICS_CREDENTIAL_PROVIDER,
"logs": OTEL_PYTHON_EXPORTER_OTLP_LOGS_CREDENTIAL_PROVIDER,
}

_PROTOCOL_ENV_BY_SIGNAL_TYPE = {
"traces": OTEL_EXPORTER_OTLP_TRACES_PROTOCOL,
"metrics": OTEL_EXPORTER_OTLP_METRICS_PROTOCOL,
Expand All @@ -99,10 +136,42 @@
Type[MetricReader],
Type[LogExporter],
],
Mapping[str, Any],
MutableMapping[str, Any],
]


def _load_credential_from_envvar(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any thoughts @lzchen @xrmx @emdneto on moving the entry point loading into the actual respective exporters to avoid the defensive import checks? I don't think we've done it anywhere else, but IMO would clean up the conditional checks here and keep the SDK and exporter impls decoupled

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better altough we are doing this kind of defensive import for psutil in SDK for resources

environment_variable: str,
) -> Optional[
tuple[
Literal["credentials", "session"],
Union["ChannelCredentials", "Session"],
]
]:
credential_env = os.getenv(environment_variable)
if credential_env:
credentials = _import_config_component(
credential_env, "opentelemetry_otlp_credential_provider"
)()
if _GRPC_IMPORTED and isinstance(credentials, ChannelCredentials): # type: ignore[reportPossiblyUnboundVariable]
return ("credentials", credentials)

if _REQUESTS_IMPORTED and isinstance(credentials, Session): # type: ignore[reportPossiblyUnboundVariable]
return ("session", credentials)
raise RuntimeError(
f"{credential_env} is neither a grpc.ChannelCredentials or requests.Session type."
)
return None


def _import_config_component(
selected_component: str, entry_point_name: str
) -> Type:
return _import_config_components([selected_component], entry_point_name)[
0
][1]


def _import_config_components(
selected_components: Sequence[str], entry_point_name: str
) -> list[tuple[str, Type]]:
Expand Down Expand Up @@ -202,12 +271,50 @@ def _get_exporter_names(
]


def _init_exporter(
signal_type: Literal["traces", "metrics", "logs"],
exporter_args: MutableMapping[str, Any],
exporter_class: Type[T],
otlp_credential_param_for_all_signal_types: Optional[
tuple[
Literal["credentials", "session"],
Union["ChannelCredentials", "Session"],
]
] = None,
) -> T:
# Per signal type envvar should take precedence over all signal type env var.
otlp_credential_param = (
_load_credential_from_envvar(
_EXPORTER_CREDENTIAL_BY_SIGNAL_TYPE[signal_type]
)
or otlp_credential_param_for_all_signal_types
)
if otlp_credential_param:
credential_key, credential = otlp_credential_param
# We only want to inject credentials into the appropriate OTLP HTTP // GRPC exporters.
if credential_key in inspect.signature(
exporter_class.__init__
).parameters and (
"opentelemetry.exporter.otlp.proto.http" in str(exporter_class)
or "opentelemetry.exporter.otlp.proto.grpc" in str(exporter_class)
or "tests.test_configurator" in str(exporter_class)
):
exporter_args[credential_key] = credential
return exporter_class(**exporter_args)


def _init_tracing(
exporters: dict[str, Type[SpanExporter]],
id_generator: IdGenerator | None = None,
sampler: Sampler | None = None,
resource: Resource | None = None,
exporter_args_map: ExporterArgsMap | None = None,
otlp_credential_param: Optional[
tuple[
Literal["credentials", "session"],
Union["ChannelCredentials", "Session"],
]
] = None,
):
provider = TracerProvider(
id_generator=id_generator,
Expand All @@ -220,7 +327,14 @@ def _init_tracing(
for _, exporter_class in exporters.items():
exporter_args = exporter_args_map.get(exporter_class, {})
provider.add_span_processor(
BatchSpanProcessor(exporter_class(**exporter_args))
BatchSpanProcessor(
_init_exporter(
"traces",
exporter_args,
exporter_class,
otlp_credential_param,
)
)
)


Expand All @@ -230,6 +344,12 @@ def _init_metrics(
],
resource: Resource | None = None,
exporter_args_map: ExporterArgsMap | None = None,
otlp_credential_param: Optional[
tuple[
Literal["credentials", "session"],
Union["ChannelCredentials", "Session"],
]
] = None,
):
metric_readers = []

Expand All @@ -241,7 +361,12 @@ def _init_metrics(
else:
metric_readers.append(
PeriodicExportingMetricReader(
exporter_or_reader_class(**exporter_args)
_init_exporter(
"metrics",
exporter_args,
exporter_or_reader_class,
otlp_credential_param,
)
)
)

Expand All @@ -254,6 +379,12 @@ def _init_logging(
resource: Resource | None = None,
setup_logging_handler: bool = True,
exporter_args_map: ExporterArgsMap | None = None,
otlp_credential_param: Optional[
tuple[
Literal["credentials", "session"],
Union["ChannelCredentials", "Session"],
]
] = None,
):
provider = LoggerProvider(resource=resource)
set_logger_provider(provider)
Expand All @@ -262,7 +393,14 @@ def _init_logging(
for _, exporter_class in exporters.items():
exporter_args = exporter_args_map.get(exporter_class, {})
provider.add_log_record_processor(
BatchLogRecordProcessor(exporter_class(**exporter_args))
BatchLogRecordProcessor(
_init_exporter(
"logs",
exporter_args,
exporter_class,
otlp_credential_param,
)
)
)

event_logger_provider = EventLoggerProvider(logger_provider=provider)
Expand Down Expand Up @@ -406,7 +544,7 @@ def _import_id_generator(id_generator_name: str) -> IdGenerator:
raise RuntimeError(f"{id_generator_name} is not an IdGenerator")


def _initialize_components(
def _initialize_components( # pylint: disable=too-many-locals
auto_instrumentation_version: str | None = None,
trace_exporter_names: list[str] | None = None,
metric_exporter_names: list[str] | None = None,
Expand Down Expand Up @@ -445,15 +583,22 @@ def _initialize_components(
# from the env variable else defaults to "unknown_service"
resource = Resource.create(resource_attributes)

otlp_credential_param = _load_credential_from_envvar(
OTEL_PYTHON_EXPORTER_OTLP_CREDENTIAL_PROVIDER
)
_init_tracing(
exporters=span_exporters,
id_generator=id_generator,
sampler=sampler,
resource=resource,
otlp_credential_param=otlp_credential_param,
exporter_args_map=exporter_args_map,
)
_init_metrics(
metric_exporters, resource, exporter_args_map=exporter_args_map
metric_exporters,
resource,
otlp_credential_param=otlp_credential_param,
exporter_args_map=exporter_args_map,
)
if setup_logging_handler is None:
setup_logging_handler = (
Expand All @@ -468,6 +613,7 @@ def _initialize_components(
log_exporters,
resource,
setup_logging_handler,
otlp_credential_param=otlp_credential_param,
exporter_args_map=exporter_args_map,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,43 @@
A scheme of https indicates a secure connection and takes precedence over this configuration setting.
"""

OTEL_PYTHON_EXPORTER_OTLP_LOGS_CREDENTIAL_PROVIDER = (
"OTEL_PYTHON_EXPORTER_OTLP_LOGS_CREDENTIAL_PROVIDER"
)
"""
.. envvar:: OTEL_PYTHON_EXPORTER_OTLP_LOGS_CREDENTIAL_PROVIDER

The :envvar:`OTEL_PYTHON_EXPORTER_OTLP_LOGS_CREDENTIAL_PROVIDER` provides either ChannelCredentials for grpc OTLP Log exporters,
or request.Session for HTTP Log exporters.
"""
OTEL_PYTHON_EXPORTER_OTLP_CREDENTIAL_PROVIDER = (
"OTEL_PYTHON_EXPORTER_OTLP_CREDENTIAL_PROVIDER"
)
"""
.. envvar:: OTEL_PYTHON_EXPORTER_OTLP_CREDENTIAL_PROVIDER

The :envvar:`OTEL_PYTHON_EXPORTER_OTLP_CREDENTIAL_PROVIDER` provides either ChannelCredentials for all grpc OTLP exporters,
or request.Session for HTTP exporters.
"""
OTEL_PYTHON_EXPORTER_OTLP_TRACES_CREDENTIAL_PROVIDER = (
"OTEL_PYTHON_EXPORTER_OTLP_TRACES_CREDENTIAL_PROVIDER"
)
"""
.. envvar:: OTEL_PYTHON_EXPORTER_OTLP_TRACES_CREDENTIAL_PROVIDER

The :envvar:`OTEL_PYTHON_EXPORTER_OTLP_TRACES_CREDENTIAL_PROVIDER` provides either ChannelCredentials for grpc OTLP Span exporters,
or request.Session for HTTP Span exporters.
"""
OTEL_PYTHON_EXPORTER_OTLP_METRICS_CREDENTIAL_PROVIDER = (
"OTEL_PYTHON_EXPORTER_OTLP_METRICS_CREDENTIAL_PROVIDER"
)
"""
.. envvar:: OTEL_PYTHON_EXPORTER_OTLP_METRICS_CREDENTIAL_PROVIDER

The :envvar:`OTEL_PYTHON_EXPORTER_OTLP_METRICS_CREDENTIAL_PROVIDER` provides either ChannelCredentials for grpc OTLP Metric exporters,
or request.Session for HTTP Metric exporters.
"""

OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE = "OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE"
"""
.. envvar:: OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE
Expand Down
2 changes: 2 additions & 0 deletions opentelemetry-sdk/test-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ py-cpuinfo==9.0.0
pytest==7.4.4
tomli==2.0.1
typing_extensions==4.10.0
grpcio==1.66.2
requests==2.32.3
wrapt==1.16.0
zipp==3.19.2
-e tests/opentelemetry-test-utils
Expand Down
Loading
Loading