Skip to content

opentelemetry-sdk: pass exporter args from sdk configuration #4659

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

Merged
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import os
from abc import ABC, abstractmethod
from os import environ
from typing import Callable, Sequence, Type, Union
from typing import Any, Callable, Mapping, Sequence, Type, Union

from typing_extensions import Literal

Expand Down Expand Up @@ -91,10 +91,20 @@

_logger = logging.getLogger(__name__)

ExporterArgsMap = Mapping[
Union[
Type[SpanExporter],
Type[MetricExporter],
Type[MetricReader],
Type[LogExporter],
],
Mapping[str, Any],
]


def _import_config_components(
selected_components: list[str], entry_point_name: str
) -> Sequence[tuple[str, object]]:
selected_components: Sequence[str], entry_point_name: str
) -> list[tuple[str, Type]]:
component_implementations = []

for selected_component in selected_components:
Expand Down Expand Up @@ -179,7 +189,7 @@ def _get_exporter_entry_point(

def _get_exporter_names(
signal_type: Literal["traces", "metrics", "logs"],
) -> Sequence[str]:
) -> list[str]:
names = environ.get(_EXPORTER_ENV_BY_SIGNAL_TYPE.get(signal_type, ""))

if not names or names.lower().strip() == "none":
Expand All @@ -196,6 +206,7 @@ def _init_tracing(
id_generator: IdGenerator | None = None,
sampler: Sampler | None = None,
resource: Resource | None = None,
exporter_args_map: ExporterArgsMap | None = None,
):
provider = TracerProvider(
id_generator=id_generator,
Expand All @@ -204,8 +215,9 @@ def _init_tracing(
)
set_tracer_provider(provider)

exporter_args_map = exporter_args_map or {}
for _, exporter_class in exporters.items():
exporter_args = {}
exporter_args = exporter_args_map.get(exporter_class, {})
provider.add_span_processor(
BatchSpanProcessor(exporter_class(**exporter_args))
)
Expand All @@ -216,12 +228,13 @@ def _init_metrics(
str, Union[Type[MetricExporter], Type[MetricReader]]
],
resource: Resource | None = None,
exporter_args_map: ExporterArgsMap | None = None,
):
metric_readers = []

exporter_args_map = exporter_args_map or {}
for _, exporter_or_reader_class in exporters_or_readers.items():
exporter_args = {}

exporter_args = exporter_args_map.get(exporter_or_reader_class, {})
if issubclass(exporter_or_reader_class, MetricReader):
metric_readers.append(exporter_or_reader_class(**exporter_args))
else:
Expand All @@ -239,12 +252,14 @@ def _init_logging(
exporters: dict[str, Type[LogExporter]],
resource: Resource | None = None,
setup_logging_handler: bool = True,
exporter_args_map: ExporterArgsMap | None = None,
):
provider = LoggerProvider(resource=resource)
set_logger_provider(provider)

exporter_args_map = exporter_args_map or {}
for _, exporter_class in exporters.items():
exporter_args = {}
exporter_args = exporter_args_map.get(exporter_class, {})
provider.add_log_record_processor(
BatchLogRecordProcessor(exporter_class(**exporter_args))
)
Expand Down Expand Up @@ -331,22 +346,24 @@ def _import_exporters(
return trace_exporters, metric_exporters, log_exporters


def _import_sampler_factory(sampler_name: str) -> Callable[[str], Sampler]:
def _import_sampler_factory(
sampler_name: str,
) -> Callable[[float | str | None], Sampler]:
_, sampler_impl = _import_config_components(
[sampler_name.strip()], _OTEL_SAMPLER_ENTRY_POINT_GROUP
)[0]
return sampler_impl


def _import_sampler(sampler_name: str) -> Sampler | None:
def _import_sampler(sampler_name: str | None) -> Sampler | None:
if not sampler_name:
return None
try:
sampler_factory = _import_sampler_factory(sampler_name)
arg = None
if sampler_name in ("traceidratio", "parentbased_traceidratio"):
try:
rate = float(os.getenv(OTEL_TRACES_SAMPLER_ARG))
rate = float(os.getenv(OTEL_TRACES_SAMPLER_ARG, ""))
except (ValueError, TypeError):
_logger.warning(
"Could not convert TRACES_SAMPLER_ARG to float. Using default value 1.0."
Expand Down Expand Up @@ -391,6 +408,7 @@ def _initialize_components(
resource_attributes: Attributes | None = None,
id_generator: IdGenerator | None = None,
setup_logging_handler: bool | None = None,
exporter_args_map: ExporterArgsMap | None = None,
):
if trace_exporter_names is None:
trace_exporter_names = []
Expand All @@ -413,7 +431,7 @@ def _initialize_components(
resource_attributes = {}
# populate version if using auto-instrumentation
if auto_instrumentation_version:
resource_attributes[ResourceAttributes.TELEMETRY_AUTO_VERSION] = (
resource_attributes[ResourceAttributes.TELEMETRY_AUTO_VERSION] = ( # type: ignore[reportIndexIssue]
auto_instrumentation_version
)
# if env var OTEL_RESOURCE_ATTRIBUTES is given, it will read the service_name
Expand All @@ -425,8 +443,11 @@ def _initialize_components(
id_generator=id_generator,
sampler=sampler,
resource=resource,
exporter_args_map=exporter_args_map,
)
_init_metrics(
metric_exporters, resource, exporter_args_map=exporter_args_map
)
_init_metrics(metric_exporters, resource)
if setup_logging_handler is None:
setup_logging_handler = (
os.getenv(
Expand All @@ -436,7 +457,12 @@ def _initialize_components(
.lower()
== "true"
)
_init_logging(log_exporters, resource, setup_logging_handler)
_init_logging(
log_exporters,
resource,
setup_logging_handler,
exporter_args_map=exporter_args_map,
)


class _BaseConfigurator(ABC):
Expand Down
63 changes: 58 additions & 5 deletions opentelemetry-sdk/tests/test_configurator.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,9 @@ def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None:


class DummyOTLPMetricExporter:
def __init__(self, *args, **kwargs):
def __init__(self, compression: str | None = None, *args, **kwargs):
self.export_called = False
self.compression = compression

def export(self, batch):
self.export_called = True
Expand All @@ -202,12 +203,14 @@ def shutdown(self):


class OTLPSpanExporter:
pass
def __init__(self, compression: str | None = None, *args, **kwargs):
self.compression = compression


class DummyOTLPLogExporter(LogExporter):
def __init__(self, *args, **kwargs):
def __init__(self, compression: str | None = None, *args, **kwargs):
self.export_called = False
self.compression = compression

def export(self, batch):
self.export_called = True
Expand Down Expand Up @@ -374,6 +377,20 @@ def test_trace_init_otlp(self):
"my-otlp-test-service",
)

def test_trace_init_exporter_uses_exporter_args_map(self):
_init_tracing(
{"otlp": OTLPSpanExporter},
id_generator=RandomIdGenerator(),
exporter_args_map={
OTLPSpanExporter: {"compression": "gzip"},
DummyMetricReaderPullExporter: {"compression": "no"},
},
)

provider = self.set_provider_mock.call_args[0][0]
exporter = provider.processor.exporter
self.assertEqual(exporter.compression, "gzip")

@patch.dict(environ, {OTEL_PYTHON_ID_GENERATOR: "custom_id_generator"})
@patch("opentelemetry.sdk._configuration.IdGenerator", new=IdGenerator)
@patch("opentelemetry.sdk._configuration.entry_points")
Expand Down Expand Up @@ -667,6 +684,20 @@ def test_logging_init_exporter(self):
getLogger(__name__).error("hello")
self.assertTrue(provider.processor.exporter.export_called)

def test_logging_init_exporter_uses_exporter_args_map(self):
resource = Resource.create({})
_init_logging(
{"otlp": DummyOTLPLogExporter},
resource=resource,
exporter_args_map={
DummyOTLPLogExporter: {"compression": "gzip"},
DummyOTLPMetricExporter: {"compression": "no"},
},
)
self.assertEqual(self.set_provider_mock.call_count, 1)
provider = self.set_provider_mock.call_args[0][0]
self.assertEqual(provider.processor.exporter.compression, "gzip")

@patch.dict(
environ,
{"OTEL_RESOURCE_ATTRIBUTES": "service.name=otlp-service"},
Expand Down Expand Up @@ -702,7 +733,9 @@ def test_logging_init_exporter_without_handler_setup(self):
def test_logging_init_disable_default(self, logging_mock, tracing_mock):
_initialize_components(auto_instrumentation_version="auto-version")
self.assertEqual(tracing_mock.call_count, 1)
logging_mock.assert_called_once_with(mock.ANY, mock.ANY, False)
logging_mock.assert_called_once_with(
mock.ANY, mock.ANY, False, exporter_args_map=None
)

@patch.dict(
environ,
Expand All @@ -716,7 +749,9 @@ def test_logging_init_disable_default(self, logging_mock, tracing_mock):
def test_logging_init_enable_env(self, logging_mock, tracing_mock):
with self.assertLogs(level=WARNING):
_initialize_components(auto_instrumentation_version="auto-version")
logging_mock.assert_called_once_with(mock.ANY, mock.ANY, True)
logging_mock.assert_called_once_with(
mock.ANY, mock.ANY, True, exporter_args_map=None
)
self.assertEqual(tracing_mock.call_count, 1)

@patch.dict(
Expand Down Expand Up @@ -799,6 +834,7 @@ def test_initialize_components_kwargs(
},
"id_generator": "TEST_GENERATOR",
"setup_logging_handler": True,
"exporter_args_map": {1: {"compression": "gzip"}},
}
_initialize_components(**kwargs)

Expand Down Expand Up @@ -832,15 +868,18 @@ def test_initialize_components_kwargs(
id_generator="TEST_GENERATOR",
sampler="TEST_SAMPLER",
resource="TEST_RESOURCE",
exporter_args_map={1: {"compression": "gzip"}},
)
metrics_mock.assert_called_once_with(
"TEST_METRICS_EXPORTERS_DICT",
"TEST_RESOURCE",
exporter_args_map={1: {"compression": "gzip"}},
)
logging_mock.assert_called_once_with(
"TEST_LOG_EXPORTERS_DICT",
"TEST_RESOURCE",
True,
exporter_args_map={1: {"compression": "gzip"}},
)

def test_basicConfig_works_with_otel_handler(self):
Expand Down Expand Up @@ -970,6 +1009,20 @@ def test_metrics_init_pull_exporter(self):
reader = provider._sdk_config.metric_readers[0]
self.assertIsInstance(reader, DummyMetricReaderPullExporter)

def test_metrics_init_exporter_uses_exporter_args_map(self):
resource = Resource.create({})
_init_metrics(
{"otlp": DummyOTLPMetricExporter},
resource=resource,
exporter_args_map={
DummyOTLPMetricExporter: {"compression": "gzip"},
DummyMetricReaderPullExporter: {"compression": "no"},
},
)
provider = self.set_provider_mock.call_args[0][0]
reader = provider._sdk_config.metric_readers[0]
self.assertEqual(reader.exporter.compression, "gzip")


class TestExporterNames(TestCase):
@patch.dict(
Expand Down
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ include = [

exclude = [
"opentelemetry-sdk/tests",
"opentelemetry-sdk/src/opentelemetry/sdk/_configuration",
"opentelemetry-sdk/src/opentelemetry/sdk/_events",
"opentelemetry-sdk/src/opentelemetry/sdk/_logs",
"opentelemetry-sdk/src/opentelemetry/sdk/error_handler",
Expand Down