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 8 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Overwrite logging.config.fileConfig and logging.config.dictConfig to ensure
the OTLP `LogHandler` remains attached to the root logger. Fix a bug that
can cause a deadlock to occur over `logging._lock` in some cases ([#4636](https://github.com/open-telemetry/opentelemetry-python/pull/4636)).
- 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 exporters created during auto instrumentation.

## Version 1.35.0/0.56b0 (2025-07-11)

Expand Down
119 changes: 113 additions & 6 deletions opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@

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, Optional, Sequence, Type, Union

from grpc import ChannelCredentials # pylint: disable=import-error
Copy link
Contributor

Choose a reason for hiding this comment

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

Could this be an issue if the SDK is running in an environment without grpcio installed? I know the operator auto-instrumentation layer for Python pre-installs the HTTP exporters and not the grpc exporters to avoid extension issues.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah that's a good point ! I don't think we should require these modules (requests, grpc) for the SDK, so we have to use try/catch blocks on the imports.. I updated the PR.. Let me know what you think

from requests import Session
from typing_extensions import Literal

from opentelemetry._events import set_event_logger_provider
Expand All @@ -46,6 +49,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 Down Expand Up @@ -79,6 +86,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 @@ -103,6 +116,36 @@
]


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 isinstance(credentials, ChannelCredentials):
return ("credentials", credentials)
elif isinstance(credentials, Session):
return ("session", credentials)
else:
raise RuntimeError(
f"{credential_env} is neither a grpc.ChannelCredentials or requests.Session type."
)


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 +245,43 @@ def _get_exporter_names(
]


def _init_exporter(
signal_type: Literal["traces", "metrics", "logs"],
exporter_args_map: Mapping[str, Any],
exporter_class: Union[
Type[SpanExporter], Type[MetricExporter], Type[LogExporter]
],
otlp_credential_param_for_all_signal_types: Optional[
tuple[
Literal["credentials", "session"],
Union[ChannelCredentials, Session],
]
] = None,
) -> Union[SpanExporter, MetricExporter, LogExporter]:
# 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)):
exporter_args_map[credential_key] = credential
return exporter_class(**exporter_args_map)


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[str, Union[ChannelCredentials, Session]]
] = None,
):
provider = TracerProvider(
id_generator=id_generator,
Expand All @@ -220,16 +294,26 @@ 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,
)
)
)


def _init_metrics(
exporters_or_readers: dict[
str, Union[Type[MetricExporter], Type[MetricReader]]
],
resource: Resource | None = None,
resource: Resource = None,
exporter_args_map: ExporterArgsMap | None = None,
otlp_credential_param: Optional[
tuple[str, Union[ChannelCredentials, Session]]
] = None,
):
metric_readers = []

Expand All @@ -241,7 +325,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 +343,9 @@ def _init_logging(
resource: Resource | None = None,
setup_logging_handler: bool = True,
exporter_args_map: ExporterArgsMap | None = None,
otlp_credential_param: Optional[
tuple[str, Union[ChannelCredentials, Session]]
] = None,
):
provider = LoggerProvider(resource=resource)
set_logger_provider(provider)
Expand All @@ -262,7 +354,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 @@ -445,15 +544,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 +574,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_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_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_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