Skip to content

Commit 53e61c8

Browse files
authored
opentelemetry-sdk: pass exporter args from sdk configuration (#4659)
This permits to pass exporter specific arguments when initialized by the sdk passing a map using the exporter class as key and a map of arguments as value.
1 parent 0a2df97 commit 53e61c8

File tree

3 files changed

+98
-20
lines changed

3 files changed

+98
-20
lines changed

opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import os
2424
from abc import ABC, abstractmethod
2525
from os import environ
26-
from typing import Callable, Sequence, Type, Union
26+
from typing import Any, Callable, Mapping, Sequence, Type, Union
2727

2828
from typing_extensions import Literal
2929

@@ -91,10 +91,20 @@
9191

9292
_logger = logging.getLogger(__name__)
9393

94+
ExporterArgsMap = Mapping[
95+
Union[
96+
Type[SpanExporter],
97+
Type[MetricExporter],
98+
Type[MetricReader],
99+
Type[LogExporter],
100+
],
101+
Mapping[str, Any],
102+
]
103+
94104

95105
def _import_config_components(
96-
selected_components: list[str], entry_point_name: str
97-
) -> Sequence[tuple[str, object]]:
106+
selected_components: Sequence[str], entry_point_name: str
107+
) -> list[tuple[str, Type]]:
98108
component_implementations = []
99109

100110
for selected_component in selected_components:
@@ -179,7 +189,7 @@ def _get_exporter_entry_point(
179189

180190
def _get_exporter_names(
181191
signal_type: Literal["traces", "metrics", "logs"],
182-
) -> Sequence[str]:
192+
) -> list[str]:
183193
names = environ.get(_EXPORTER_ENV_BY_SIGNAL_TYPE.get(signal_type, ""))
184194

185195
if not names or names.lower().strip() == "none":
@@ -196,6 +206,7 @@ def _init_tracing(
196206
id_generator: IdGenerator | None = None,
197207
sampler: Sampler | None = None,
198208
resource: Resource | None = None,
209+
exporter_args_map: ExporterArgsMap | None = None,
199210
):
200211
provider = TracerProvider(
201212
id_generator=id_generator,
@@ -204,8 +215,9 @@ def _init_tracing(
204215
)
205216
set_tracer_provider(provider)
206217

218+
exporter_args_map = exporter_args_map or {}
207219
for _, exporter_class in exporters.items():
208-
exporter_args = {}
220+
exporter_args = exporter_args_map.get(exporter_class, {})
209221
provider.add_span_processor(
210222
BatchSpanProcessor(exporter_class(**exporter_args))
211223
)
@@ -216,12 +228,13 @@ def _init_metrics(
216228
str, Union[Type[MetricExporter], Type[MetricReader]]
217229
],
218230
resource: Resource | None = None,
231+
exporter_args_map: ExporterArgsMap | None = None,
219232
):
220233
metric_readers = []
221234

235+
exporter_args_map = exporter_args_map or {}
222236
for _, exporter_or_reader_class in exporters_or_readers.items():
223-
exporter_args = {}
224-
237+
exporter_args = exporter_args_map.get(exporter_or_reader_class, {})
225238
if issubclass(exporter_or_reader_class, MetricReader):
226239
metric_readers.append(exporter_or_reader_class(**exporter_args))
227240
else:
@@ -239,12 +252,14 @@ def _init_logging(
239252
exporters: dict[str, Type[LogExporter]],
240253
resource: Resource | None = None,
241254
setup_logging_handler: bool = True,
255+
exporter_args_map: ExporterArgsMap | None = None,
242256
):
243257
provider = LoggerProvider(resource=resource)
244258
set_logger_provider(provider)
245259

260+
exporter_args_map = exporter_args_map or {}
246261
for _, exporter_class in exporters.items():
247-
exporter_args = {}
262+
exporter_args = exporter_args_map.get(exporter_class, {})
248263
provider.add_log_record_processor(
249264
BatchLogRecordProcessor(exporter_class(**exporter_args))
250265
)
@@ -331,22 +346,24 @@ def _import_exporters(
331346
return trace_exporters, metric_exporters, log_exporters
332347

333348

334-
def _import_sampler_factory(sampler_name: str) -> Callable[[str], Sampler]:
349+
def _import_sampler_factory(
350+
sampler_name: str,
351+
) -> Callable[[float | str | None], Sampler]:
335352
_, sampler_impl = _import_config_components(
336353
[sampler_name.strip()], _OTEL_SAMPLER_ENTRY_POINT_GROUP
337354
)[0]
338355
return sampler_impl
339356

340357

341-
def _import_sampler(sampler_name: str) -> Sampler | None:
358+
def _import_sampler(sampler_name: str | None) -> Sampler | None:
342359
if not sampler_name:
343360
return None
344361
try:
345362
sampler_factory = _import_sampler_factory(sampler_name)
346363
arg = None
347364
if sampler_name in ("traceidratio", "parentbased_traceidratio"):
348365
try:
349-
rate = float(os.getenv(OTEL_TRACES_SAMPLER_ARG))
366+
rate = float(os.getenv(OTEL_TRACES_SAMPLER_ARG, ""))
350367
except (ValueError, TypeError):
351368
_logger.warning(
352369
"Could not convert TRACES_SAMPLER_ARG to float. Using default value 1.0."
@@ -391,6 +408,7 @@ def _initialize_components(
391408
resource_attributes: Attributes | None = None,
392409
id_generator: IdGenerator | None = None,
393410
setup_logging_handler: bool | None = None,
411+
exporter_args_map: ExporterArgsMap | None = None,
394412
):
395413
if trace_exporter_names is None:
396414
trace_exporter_names = []
@@ -413,7 +431,7 @@ def _initialize_components(
413431
resource_attributes = {}
414432
# populate version if using auto-instrumentation
415433
if auto_instrumentation_version:
416-
resource_attributes[ResourceAttributes.TELEMETRY_AUTO_VERSION] = (
434+
resource_attributes[ResourceAttributes.TELEMETRY_AUTO_VERSION] = ( # type: ignore[reportIndexIssue]
417435
auto_instrumentation_version
418436
)
419437
# if env var OTEL_RESOURCE_ATTRIBUTES is given, it will read the service_name
@@ -425,8 +443,11 @@ def _initialize_components(
425443
id_generator=id_generator,
426444
sampler=sampler,
427445
resource=resource,
446+
exporter_args_map=exporter_args_map,
447+
)
448+
_init_metrics(
449+
metric_exporters, resource, exporter_args_map=exporter_args_map
428450
)
429-
_init_metrics(metric_exporters, resource)
430451
if setup_logging_handler is None:
431452
setup_logging_handler = (
432453
os.getenv(
@@ -436,7 +457,12 @@ def _initialize_components(
436457
.lower()
437458
== "true"
438459
)
439-
_init_logging(log_exporters, resource, setup_logging_handler)
460+
_init_logging(
461+
log_exporters,
462+
resource,
463+
setup_logging_handler,
464+
exporter_args_map=exporter_args_map,
465+
)
440466

441467

442468
class _BaseConfigurator(ABC):

opentelemetry-sdk/tests/test_configurator.py

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,9 @@ def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None:
178178

179179

180180
class DummyOTLPMetricExporter:
181-
def __init__(self, *args, **kwargs):
181+
def __init__(self, compression: str | None = None, *args, **kwargs):
182182
self.export_called = False
183+
self.compression = compression
183184

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

203204

204205
class OTLPSpanExporter:
205-
pass
206+
def __init__(self, compression: str | None = None, *args, **kwargs):
207+
self.compression = compression
206208

207209

208210
class DummyOTLPLogExporter(LogExporter):
209-
def __init__(self, *args, **kwargs):
211+
def __init__(self, compression: str | None = None, *args, **kwargs):
210212
self.export_called = False
213+
self.compression = compression
211214

212215
def export(self, batch):
213216
self.export_called = True
@@ -374,6 +377,20 @@ def test_trace_init_otlp(self):
374377
"my-otlp-test-service",
375378
)
376379

380+
def test_trace_init_exporter_uses_exporter_args_map(self):
381+
_init_tracing(
382+
{"otlp": OTLPSpanExporter},
383+
id_generator=RandomIdGenerator(),
384+
exporter_args_map={
385+
OTLPSpanExporter: {"compression": "gzip"},
386+
DummyMetricReaderPullExporter: {"compression": "no"},
387+
},
388+
)
389+
390+
provider = self.set_provider_mock.call_args[0][0]
391+
exporter = provider.processor.exporter
392+
self.assertEqual(exporter.compression, "gzip")
393+
377394
@patch.dict(environ, {OTEL_PYTHON_ID_GENERATOR: "custom_id_generator"})
378395
@patch("opentelemetry.sdk._configuration.IdGenerator", new=IdGenerator)
379396
@patch("opentelemetry.sdk._configuration.entry_points")
@@ -667,6 +684,20 @@ def test_logging_init_exporter(self):
667684
getLogger(__name__).error("hello")
668685
self.assertTrue(provider.processor.exporter.export_called)
669686

687+
def test_logging_init_exporter_uses_exporter_args_map(self):
688+
resource = Resource.create({})
689+
_init_logging(
690+
{"otlp": DummyOTLPLogExporter},
691+
resource=resource,
692+
exporter_args_map={
693+
DummyOTLPLogExporter: {"compression": "gzip"},
694+
DummyOTLPMetricExporter: {"compression": "no"},
695+
},
696+
)
697+
self.assertEqual(self.set_provider_mock.call_count, 1)
698+
provider = self.set_provider_mock.call_args[0][0]
699+
self.assertEqual(provider.processor.exporter.compression, "gzip")
700+
670701
@patch.dict(
671702
environ,
672703
{"OTEL_RESOURCE_ATTRIBUTES": "service.name=otlp-service"},
@@ -702,7 +733,9 @@ def test_logging_init_exporter_without_handler_setup(self):
702733
def test_logging_init_disable_default(self, logging_mock, tracing_mock):
703734
_initialize_components(auto_instrumentation_version="auto-version")
704735
self.assertEqual(tracing_mock.call_count, 1)
705-
logging_mock.assert_called_once_with(mock.ANY, mock.ANY, False)
736+
logging_mock.assert_called_once_with(
737+
mock.ANY, mock.ANY, False, exporter_args_map=None
738+
)
706739

707740
@patch.dict(
708741
environ,
@@ -716,7 +749,9 @@ def test_logging_init_disable_default(self, logging_mock, tracing_mock):
716749
def test_logging_init_enable_env(self, logging_mock, tracing_mock):
717750
with self.assertLogs(level=WARNING):
718751
_initialize_components(auto_instrumentation_version="auto-version")
719-
logging_mock.assert_called_once_with(mock.ANY, mock.ANY, True)
752+
logging_mock.assert_called_once_with(
753+
mock.ANY, mock.ANY, True, exporter_args_map=None
754+
)
720755
self.assertEqual(tracing_mock.call_count, 1)
721756

722757
@patch.dict(
@@ -799,6 +834,7 @@ def test_initialize_components_kwargs(
799834
},
800835
"id_generator": "TEST_GENERATOR",
801836
"setup_logging_handler": True,
837+
"exporter_args_map": {1: {"compression": "gzip"}},
802838
}
803839
_initialize_components(**kwargs)
804840

@@ -832,15 +868,18 @@ def test_initialize_components_kwargs(
832868
id_generator="TEST_GENERATOR",
833869
sampler="TEST_SAMPLER",
834870
resource="TEST_RESOURCE",
871+
exporter_args_map={1: {"compression": "gzip"}},
835872
)
836873
metrics_mock.assert_called_once_with(
837874
"TEST_METRICS_EXPORTERS_DICT",
838875
"TEST_RESOURCE",
876+
exporter_args_map={1: {"compression": "gzip"}},
839877
)
840878
logging_mock.assert_called_once_with(
841879
"TEST_LOG_EXPORTERS_DICT",
842880
"TEST_RESOURCE",
843881
True,
882+
exporter_args_map={1: {"compression": "gzip"}},
844883
)
845884

846885
def test_basicConfig_works_with_otel_handler(self):
@@ -970,6 +1009,20 @@ def test_metrics_init_pull_exporter(self):
9701009
reader = provider._sdk_config.metric_readers[0]
9711010
self.assertIsInstance(reader, DummyMetricReaderPullExporter)
9721011

1012+
def test_metrics_init_exporter_uses_exporter_args_map(self):
1013+
resource = Resource.create({})
1014+
_init_metrics(
1015+
{"otlp": DummyOTLPMetricExporter},
1016+
resource=resource,
1017+
exporter_args_map={
1018+
DummyOTLPMetricExporter: {"compression": "gzip"},
1019+
DummyMetricReaderPullExporter: {"compression": "no"},
1020+
},
1021+
)
1022+
provider = self.set_provider_mock.call_args[0][0]
1023+
reader = provider._sdk_config.metric_readers[0]
1024+
self.assertEqual(reader.exporter.compression, "gzip")
1025+
9731026

9741027
class TestExporterNames(TestCase):
9751028
@patch.dict(

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,6 @@ include = [
109109

110110
exclude = [
111111
"opentelemetry-sdk/tests",
112-
"opentelemetry-sdk/src/opentelemetry/sdk/_configuration",
113112
"opentelemetry-sdk/src/opentelemetry/sdk/_events",
114113
"opentelemetry-sdk/src/opentelemetry/sdk/_logs",
115114
"opentelemetry-sdk/src/opentelemetry/sdk/error_handler",

0 commit comments

Comments
 (0)