Skip to content

Commit b5f3f50

Browse files
committed
Make opentelemetry_metrics_exporter entrypoint support pull exporters
1 parent 6070a0d commit b5f3f50

File tree

3 files changed

+70
-14
lines changed

3 files changed

+70
-14
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
- Fix error when no LoggerProvider configured for LoggingHandler
1111
([#3423](https://github.com/open-telemetry/opentelemetry-python/pull/3423))
12+
- Make `opentelemetry_metrics_exporter` entrypoint support pull exporters
13+
([#3428](https://github.com/open-telemetry/opentelemetry-python/pull/3428))
1214

1315
## Version 1.20.0/0.41b0 (2023-09-04)
1416

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

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import os
2222
from abc import ABC, abstractmethod
2323
from os import environ
24-
from typing import Callable, Dict, List, Optional, Sequence, Tuple, Type
24+
from typing import Callable, Dict, List, Optional, Sequence, Tuple, Type, Union
2525

2626
from typing_extensions import Literal
2727

@@ -47,6 +47,7 @@
4747
from opentelemetry.sdk.metrics import MeterProvider
4848
from opentelemetry.sdk.metrics.export import (
4949
MetricExporter,
50+
MetricReader,
5051
PeriodicExportingMetricReader,
5152
)
5253
from opentelemetry.sdk.resources import Resource
@@ -90,7 +91,6 @@
9091
def _import_config_components(
9192
selected_components: List[str], entry_point_name: str
9293
) -> Sequence[Tuple[str, object]]:
93-
9494
component_implementations = []
9595

9696
for selected_component in selected_components:
@@ -108,13 +108,11 @@ def _import_config_components(
108108
)
109109
)
110110
except KeyError:
111-
112111
raise RuntimeError(
113112
f"Requested entry point '{entry_point_name}' not found"
114113
)
115114

116115
except StopIteration:
117-
118116
raise RuntimeError(
119117
f"Requested component '{selected_component}' not found in "
120118
f"entry point '{entry_point_name}'"
@@ -210,16 +208,24 @@ def _init_tracing(
210208

211209

212210
def _init_metrics(
213-
exporters: Dict[str, Type[MetricExporter]],
211+
exporters_or_readers: Dict[
212+
str, Union[Type[MetricExporter], Type[MetricReader]]
213+
],
214214
resource: Resource = None,
215215
):
216216
metric_readers = []
217217

218-
for _, exporter_class in exporters.items():
218+
for _, exporter_or_reader_class in exporters_or_readers.items():
219219
exporter_args = {}
220-
metric_readers.append(
221-
PeriodicExportingMetricReader(exporter_class(**exporter_args))
222-
)
220+
221+
if issubclass(exporter_or_reader_class, MetricReader):
222+
metric_readers.append(exporter_or_reader_class(**exporter_args))
223+
else:
224+
metric_readers.append(
225+
PeriodicExportingMetricReader(
226+
exporter_or_reader_class(**exporter_args)
227+
)
228+
)
223229

224230
provider = MeterProvider(resource=resource, metric_readers=metric_readers)
225231
set_meter_provider(provider)
@@ -249,7 +255,7 @@ def _import_exporters(
249255
log_exporter_names: Sequence[str],
250256
) -> Tuple[
251257
Dict[str, Type[SpanExporter]],
252-
Dict[str, Type[MetricExporter]],
258+
Dict[str, Union[Type[MetricExporter], Type[MetricReader]]],
253259
Dict[str, Type[LogExporter]],
254260
]:
255261
trace_exporters = {}
@@ -267,7 +273,9 @@ def _import_exporters(
267273
for (exporter_name, exporter_impl,) in _import_config_components(
268274
metric_exporter_names, "opentelemetry_metrics_exporter"
269275
):
270-
if issubclass(exporter_impl, MetricExporter):
276+
# The metric exporter components may be push MetricExporter or pull exporters which
277+
# subclass MetricReader directly
278+
if issubclass(exporter_impl, (MetricExporter, MetricReader)):
271279
metric_exporters[exporter_name] = exporter_impl
272280
else:
273281
raise RuntimeError(f"{exporter_name} is not a metric exporter")
@@ -380,7 +388,6 @@ class _BaseConfigurator(ABC):
380388
_is_instrumented = False
381389

382390
def __new__(cls, *args, **kwargs):
383-
384391
if cls._instance is None:
385392
cls._instance = object.__new__(cls, *args, **kwargs)
386393

opentelemetry-sdk/tests/test_configurator.py

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from os import environ
1919
from typing import Dict, Iterable, Optional, Sequence
2020
from unittest import TestCase
21-
from unittest.mock import patch
21+
from unittest.mock import Mock, patch
2222

2323
from pytest import raises
2424

@@ -158,6 +158,20 @@ def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None:
158158
return True
159159

160160

161+
# MetricReader that can be configured as a pull exporter
162+
class DummyMetricReaderPullExporter(MetricReader):
163+
def _receive_metrics(
164+
self,
165+
metrics: Iterable[Metric],
166+
timeout_millis: float = 10_000,
167+
**kwargs,
168+
) -> None:
169+
pass
170+
171+
def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None:
172+
return True
173+
174+
161175
class DummyOTLPMetricExporter:
162176
def __init__(self, *args, **kwargs):
163177
self.export_called = False
@@ -309,7 +323,6 @@ def tearDown(self):
309323
environ, {"OTEL_RESOURCE_ATTRIBUTES": "service.name=my-test-service"}
310324
)
311325
def test_trace_init_default(self):
312-
313326
auto_resource = Resource.create(
314327
{
315328
"telemetry.auto.version": "test-version",
@@ -740,6 +753,18 @@ def test_metrics_init_exporter(self):
740753
self.assertIsInstance(reader, DummyMetricReader)
741754
self.assertIsInstance(reader.exporter, DummyOTLPMetricExporter)
742755

756+
def test_metrics_init_pull_exporter(self):
757+
resource = Resource.create({})
758+
_init_metrics(
759+
{"dummy_metric_reader": DummyMetricReaderPullExporter},
760+
resource=resource,
761+
)
762+
self.assertEqual(self.set_provider_mock.call_count, 1)
763+
provider = self.set_provider_mock.call_args[0][0]
764+
self.assertIsInstance(provider, DummyMeterProvider)
765+
reader = provider._sdk_config.metric_readers[0]
766+
self.assertIsInstance(reader, DummyMetricReaderPullExporter)
767+
743768

744769
class TestExporterNames(TestCase):
745770
@patch.dict(
@@ -835,6 +860,28 @@ def test_console_exporters(self):
835860
ConsoleMetricExporter.__class__,
836861
)
837862

863+
@patch(
864+
"opentelemetry.sdk._configuration.entry_points",
865+
)
866+
def test_metric_pull_exporter(self, mock_entry_points: Mock):
867+
def mock_entry_points_impl(group, name):
868+
if name == "dummy_pull_exporter":
869+
return [
870+
IterEntryPoint(
871+
name=name, class_type=DummyMetricReaderPullExporter
872+
)
873+
]
874+
return []
875+
876+
mock_entry_points.side_effect = mock_entry_points_impl
877+
_, metric_exporters, _ = _import_exporters(
878+
[], ["dummy_pull_exporter"], []
879+
)
880+
self.assertIs(
881+
metric_exporters["dummy_pull_exporter"],
882+
DummyMetricReaderPullExporter,
883+
)
884+
838885

839886
class TestImportConfigComponents(TestCase):
840887
@patch(

0 commit comments

Comments
 (0)