Skip to content

Commit af43f96

Browse files
xrmxJWinermaSplunk
authored andcommitted
opentelemetry-sdk: pass exporter args from sdk configuration (open-telemetry#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 6a3444a commit af43f96

3 files changed

Lines changed: 98 additions & 20 deletions

File tree

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

@@ -102,10 +102,20 @@
102102

103103
_logger = logging.getLogger(__name__)
104104

105+
ExporterArgsMap = Mapping[
106+
Union[
107+
Type[SpanExporter],
108+
Type[MetricExporter],
109+
Type[MetricReader],
110+
Type[LogExporter],
111+
],
112+
Mapping[str, Any],
113+
]
114+
105115

106116
def _import_config_components(
107-
selected_components: list[str], entry_point_name: str
108-
) -> Sequence[tuple[str, object]]:
117+
selected_components: Sequence[str], entry_point_name: str
118+
) -> list[tuple[str, Type]]:
109119
component_implementations = []
110120

111121
for selected_component in selected_components:
@@ -197,7 +207,7 @@ def _get_exporter_entry_point(
197207

198208
def _get_exporter_names(
199209
signal_type: Literal["traces", "metrics", "logs"],
200-
) -> Sequence[str]:
210+
) -> list[str]:
201211
names = environ.get(_EXPORTER_ENV_BY_SIGNAL_TYPE.get(signal_type, ""))
202212

203213
if not names or names.lower().strip() == "none":
@@ -214,6 +224,7 @@ def _init_tracing(
214224
id_generator: IdGenerator | None = None,
215225
sampler: Sampler | None = None,
216226
resource: Resource | None = None,
227+
exporter_args_map: ExporterArgsMap | None = None,
217228
):
218229
provider = TracerProvider(
219230
id_generator=id_generator,
@@ -222,8 +233,9 @@ def _init_tracing(
222233
)
223234
set_tracer_provider(provider)
224235

236+
exporter_args_map = exporter_args_map or {}
225237
for _, exporter_class in exporters.items():
226-
exporter_args = {}
238+
exporter_args = exporter_args_map.get(exporter_class, {})
227239
provider.add_span_processor(
228240
BatchSpanProcessor(exporter_class(**exporter_args))
229241
)
@@ -234,12 +246,13 @@ def _init_metrics(
234246
str, Union[Type[MetricExporter], Type[MetricReader]]
235247
],
236248
resource: Resource | None = None,
249+
exporter_args_map: ExporterArgsMap | None = None,
237250
):
238251
metric_readers = []
239252

253+
exporter_args_map = exporter_args_map or {}
240254
for _, exporter_or_reader_class in exporters_or_readers.items():
241-
exporter_args = {}
242-
255+
exporter_args = exporter_args_map.get(exporter_or_reader_class, {})
243256
if issubclass(exporter_or_reader_class, MetricReader):
244257
metric_readers.append(exporter_or_reader_class(**exporter_args))
245258
else:
@@ -257,12 +270,14 @@ def _init_logging(
257270
exporters: dict[str, Type[LogExporter]],
258271
resource: Resource | None = None,
259272
setup_logging_handler: bool = True,
273+
exporter_args_map: ExporterArgsMap | None = None,
260274
):
261275
provider = LoggerProvider(resource=resource)
262276
set_logger_provider(provider)
263277

278+
exporter_args_map = exporter_args_map or {}
264279
for _, exporter_class in exporters.items():
265-
exporter_args = {}
280+
exporter_args = exporter_args_map.get(exporter_class, {})
266281
provider.add_log_record_processor(
267282
BatchLogRecordProcessor(exporter_class(**exporter_args))
268283
)
@@ -357,22 +372,24 @@ def _import_exporters(
357372
return trace_exporters, metric_exporters, log_exporters
358373

359374

360-
def _import_sampler_factory(sampler_name: str) -> Callable[[str], Sampler]:
375+
def _import_sampler_factory(
376+
sampler_name: str,
377+
) -> Callable[[float | str | None], Sampler]:
361378
_, sampler_impl = _import_config_components(
362379
[sampler_name.strip()], _OTEL_SAMPLER_ENTRY_POINT_GROUP
363380
)[0]
364381
return sampler_impl
365382

366383

367-
def _import_sampler(sampler_name: str) -> Sampler | None:
384+
def _import_sampler(sampler_name: str | None) -> Sampler | None:
368385
if not sampler_name:
369386
return None
370387
try:
371388
sampler_factory = _import_sampler_factory(sampler_name)
372389
arg = None
373390
if sampler_name in ("traceidratio", "parentbased_traceidratio"):
374391
try:
375-
rate = float(os.getenv(OTEL_TRACES_SAMPLER_ARG))
392+
rate = float(os.getenv(OTEL_TRACES_SAMPLER_ARG, ""))
376393
except (ValueError, TypeError):
377394
_logger.warning(
378395
"Could not convert TRACES_SAMPLER_ARG to float. Using default value 1.0."
@@ -417,6 +434,7 @@ def _initialize_components(
417434
resource_attributes: Attributes | None = None,
418435
id_generator: IdGenerator | None = None,
419436
setup_logging_handler: bool | None = None,
437+
exporter_args_map: ExporterArgsMap | None = None,
420438
):
421439
if trace_exporter_names is None:
422440
trace_exporter_names = []
@@ -439,7 +457,7 @@ def _initialize_components(
439457
resource_attributes = {}
440458
# populate version if using auto-instrumentation
441459
if auto_instrumentation_version:
442-
resource_attributes[ResourceAttributes.TELEMETRY_AUTO_VERSION] = (
460+
resource_attributes[ResourceAttributes.TELEMETRY_AUTO_VERSION] = ( # type: ignore[reportIndexIssue]
443461
auto_instrumentation_version
444462
)
445463
# if env var OTEL_RESOURCE_ATTRIBUTES is given, it will read the service_name
@@ -451,8 +469,11 @@ def _initialize_components(
451469
id_generator=id_generator,
452470
sampler=sampler,
453471
resource=resource,
472+
exporter_args_map=exporter_args_map,
473+
)
474+
_init_metrics(
475+
metric_exporters, resource, exporter_args_map=exporter_args_map
454476
)
455-
_init_metrics(metric_exporters, resource)
456477
if setup_logging_handler is None:
457478
setup_logging_handler = (
458479
os.getenv(
@@ -462,7 +483,12 @@ def _initialize_components(
462483
.lower()
463484
== "true"
464485
)
465-
_init_logging(log_exporters, resource, setup_logging_handler)
486+
_init_logging(
487+
log_exporters,
488+
resource,
489+
setup_logging_handler,
490+
exporter_args_map=exporter_args_map,
491+
)
466492

467493

468494
class _BaseConfigurator(ABC):

opentelemetry-sdk/tests/test_configurator.py

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

189189

190190
class DummyOTLPMetricExporter:
191-
def __init__(self, *args, **kwargs):
191+
def __init__(self, compression: str | None = None, *args, **kwargs):
192192
self.export_called = False
193+
self.compression = compression
193194

194195
def export(self, batch):
195196
self.export_called = True
@@ -212,12 +213,14 @@ def shutdown(self):
212213

213214

214215
class OTLPSpanExporter:
215-
pass
216+
def __init__(self, compression: str | None = None, *args, **kwargs):
217+
self.compression = compression
216218

217219

218220
class DummyOTLPLogExporter(LogExporter):
219-
def __init__(self, *args, **kwargs):
221+
def __init__(self, compression: str | None = None, *args, **kwargs):
220222
self.export_called = False
223+
self.compression = compression
221224

222225
def export(self, batch):
223226
self.export_called = True
@@ -384,6 +387,20 @@ def test_trace_init_otlp(self):
384387
"my-otlp-test-service",
385388
)
386389

390+
def test_trace_init_exporter_uses_exporter_args_map(self):
391+
_init_tracing(
392+
{"otlp": OTLPSpanExporter},
393+
id_generator=RandomIdGenerator(),
394+
exporter_args_map={
395+
OTLPSpanExporter: {"compression": "gzip"},
396+
DummyMetricReaderPullExporter: {"compression": "no"},
397+
},
398+
)
399+
400+
provider = self.set_provider_mock.call_args[0][0]
401+
exporter = provider.processor.exporter
402+
self.assertEqual(exporter.compression, "gzip")
403+
387404
@patch.dict(environ, {OTEL_PYTHON_ID_GENERATOR: "custom_id_generator"})
388405
@patch("opentelemetry.sdk._configuration.IdGenerator", new=IdGenerator)
389406
@patch("opentelemetry.sdk._configuration.entry_points")
@@ -679,6 +696,20 @@ def test_logging_init_exporter(self):
679696
getLogger(__name__).error("hello")
680697
self.assertTrue(provider.processor.exporter.export_called)
681698

699+
def test_logging_init_exporter_uses_exporter_args_map(self):
700+
resource = Resource.create({})
701+
_init_logging(
702+
{"otlp": DummyOTLPLogExporter},
703+
resource=resource,
704+
exporter_args_map={
705+
DummyOTLPLogExporter: {"compression": "gzip"},
706+
DummyOTLPMetricExporter: {"compression": "no"},
707+
},
708+
)
709+
self.assertEqual(self.set_provider_mock.call_count, 1)
710+
provider = self.set_provider_mock.call_args[0][0]
711+
self.assertEqual(provider.processor.exporter.compression, "gzip")
712+
682713
@patch.dict(
683714
environ,
684715
{"OTEL_RESOURCE_ATTRIBUTES": "service.name=otlp-service"},
@@ -859,7 +890,9 @@ def test_otel_log_level_by_name_invalid(self):
859890
def test_logging_init_disable_default(self, logging_mock, tracing_mock):
860891
_initialize_components(auto_instrumentation_version="auto-version")
861892
self.assertEqual(tracing_mock.call_count, 1)
862-
logging_mock.assert_called_once_with(mock.ANY, mock.ANY, False)
893+
logging_mock.assert_called_once_with(
894+
mock.ANY, mock.ANY, False, exporter_args_map=None
895+
)
863896

864897
@patch.dict(
865898
environ,
@@ -873,7 +906,9 @@ def test_logging_init_disable_default(self, logging_mock, tracing_mock):
873906
def test_logging_init_enable_env(self, logging_mock, tracing_mock):
874907
with self.assertLogs(level=WARNING):
875908
_initialize_components(auto_instrumentation_version="auto-version")
876-
logging_mock.assert_called_once_with(mock.ANY, mock.ANY, True)
909+
logging_mock.assert_called_once_with(
910+
mock.ANY, mock.ANY, True, exporter_args_map=None
911+
)
877912
self.assertEqual(tracing_mock.call_count, 1)
878913

879914
@patch.dict(
@@ -956,6 +991,7 @@ def test_initialize_components_kwargs(
956991
},
957992
"id_generator": "TEST_GENERATOR",
958993
"setup_logging_handler": True,
994+
"exporter_args_map": {1: {"compression": "gzip"}},
959995
}
960996
_initialize_components(**kwargs)
961997

@@ -989,15 +1025,18 @@ def test_initialize_components_kwargs(
9891025
id_generator="TEST_GENERATOR",
9901026
sampler="TEST_SAMPLER",
9911027
resource="TEST_RESOURCE",
1028+
exporter_args_map={1: {"compression": "gzip"}},
9921029
)
9931030
metrics_mock.assert_called_once_with(
9941031
"TEST_METRICS_EXPORTERS_DICT",
9951032
"TEST_RESOURCE",
1033+
exporter_args_map={1: {"compression": "gzip"}},
9961034
)
9971035
logging_mock.assert_called_once_with(
9981036
"TEST_LOG_EXPORTERS_DICT",
9991037
"TEST_RESOURCE",
10001038
True,
1039+
exporter_args_map={1: {"compression": "gzip"}},
10011040
)
10021041

10031042
@patch.dict(
@@ -1222,6 +1261,20 @@ def test_metrics_init_pull_exporter(self):
12221261
reader = provider._sdk_config.metric_readers[0]
12231262
self.assertIsInstance(reader, DummyMetricReaderPullExporter)
12241263

1264+
def test_metrics_init_exporter_uses_exporter_args_map(self):
1265+
resource = Resource.create({})
1266+
_init_metrics(
1267+
{"otlp": DummyOTLPMetricExporter},
1268+
resource=resource,
1269+
exporter_args_map={
1270+
DummyOTLPMetricExporter: {"compression": "gzip"},
1271+
DummyMetricReaderPullExporter: {"compression": "no"},
1272+
},
1273+
)
1274+
provider = self.set_provider_mock.call_args[0][0]
1275+
reader = provider._sdk_config.metric_readers[0]
1276+
self.assertEqual(reader.exporter.compression, "gzip")
1277+
12251278

12261279
class TestExporterNames(TestCase):
12271280
@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)