Skip to content
Open
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

- `opentelemetry-sdk`: add exporter plugin loading to declarative file configuration for all three signals (traces, metrics, logs) via the `opentelemetry_*_exporter` entry point groups
([#5128](https://github.com/open-telemetry/opentelemetry-python/pull/5128))
- `opentelemetry-sdk`: add `additional_properties` support to generated config models via custom `datamodel-codegen` template, enabling plugin/custom component names to flow through typed dataclasses
([#5131](https://github.com/open-telemetry/opentelemetry-python/pull/5131))
- Fix incorrect code example in `create_tracer()` docstring
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,5 +109,8 @@ def _parse_headers(
)
if headers:
for pair in headers:
result[pair.name] = pair.value or ""
if isinstance(pair, dict):
result[pair["name"]] = pair.get("value") or ""
else:
result[pair.name] = pair.value or ""
return result
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
from typing import Optional

from opentelemetry._logs import set_logger_provider
from opentelemetry.sdk._configuration._common import _parse_headers
from opentelemetry.sdk._configuration._common import (
_parse_headers,
load_entry_point,
)
from opentelemetry.sdk._configuration._exceptions import ConfigurationError
from opentelemetry.sdk._configuration.models import (
BatchLogRecordProcessor as BatchLogRecordProcessorConfig,
Expand Down Expand Up @@ -136,20 +139,30 @@ def _create_otlp_grpc_log_exporter(
)


_LOG_EXPORTER_REGISTRY: dict = {
"otlp_http": _create_otlp_http_log_exporter,
"otlp_grpc": _create_otlp_grpc_log_exporter,
"console": lambda _: _create_console_log_exporter(),
}


def _create_log_record_exporter(
config: LogRecordExporterConfig,
) -> LogRecordExporter:
"""Create a log record exporter from config."""
if config.console is not None:
return _create_console_log_exporter()
if config.otlp_http is not None:
return _create_otlp_http_log_exporter(config.otlp_http)
if config.otlp_grpc is not None:
return _create_otlp_grpc_log_exporter(config.otlp_grpc)
if config.otlp_file_development is not None:
raise ConfigurationError(
"otlp_file_development log exporter is experimental and not yet supported."
)
"""Create a log record exporter from config.

Known exporter types are checked via typed fields on the LogRecordExporter
dataclass. Unknown exporter names captured in additional_properties
by the @_additional_properties decorator are loaded via the
``opentelemetry_logs_exporter`` entry point group.
"""
for name, factory in _LOG_EXPORTER_REGISTRY.items():
value = getattr(config, name, None)
if value is not None:
return factory(value)
if config.additional_properties:
name = next(iter(config.additional_properties))
return load_entry_point("opentelemetry_logs_exporter", name)()
raise ConfigurationError(
"No exporter type specified in log record exporter config. "
"Supported types: console, otlp_http, otlp_grpc."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
from typing import Optional, Set, Type

from opentelemetry import metrics
from opentelemetry.sdk._configuration._common import _parse_headers
from opentelemetry.sdk._configuration._common import (
_parse_headers,
load_entry_point,
)
from opentelemetry.sdk._configuration._exceptions import ConfigurationError
from opentelemetry.sdk._configuration.models import (
Aggregation as AggregationConfig,
Expand Down Expand Up @@ -349,20 +352,30 @@ def _create_otlp_grpc_metric_exporter(
)


_METRIC_EXPORTER_REGISTRY: dict = {
"otlp_http": _create_otlp_http_metric_exporter,
"otlp_grpc": _create_otlp_grpc_metric_exporter,
"console": _create_console_metric_exporter,
}


def _create_push_metric_exporter(
config: PushMetricExporterConfig,
) -> MetricExporter:
"""Create a push metric exporter from config."""
if config.console is not None:
return _create_console_metric_exporter(config.console)
if config.otlp_http is not None:
return _create_otlp_http_metric_exporter(config.otlp_http)
if config.otlp_grpc is not None:
return _create_otlp_grpc_metric_exporter(config.otlp_grpc)
if config.otlp_file_development is not None:
raise ConfigurationError(
"otlp_file_development metric exporter is experimental and not yet supported."
)
"""Create a push metric exporter from config.

Known exporter types are checked via typed fields on the PushMetricExporter
dataclass. Unknown exporter names captured in additional_properties
by the @_additional_properties decorator are loaded via the
``opentelemetry_metrics_exporter`` entry point group.
"""
for name, factory in _METRIC_EXPORTER_REGISTRY.items():
value = getattr(config, name, None)
if value is not None:
return factory(value)
if config.additional_properties:
name = next(iter(config.additional_properties))
return load_entry_point("opentelemetry_metrics_exporter", name)()
raise ConfigurationError(
"No exporter type specified in push metric exporter config. "
"Supported types: console, otlp_http, otlp_grpc."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
from typing import Optional

from opentelemetry import trace
from opentelemetry.sdk._configuration._common import _parse_headers
from opentelemetry.sdk._configuration._common import (
_parse_headers,
load_entry_point,
)
from opentelemetry.sdk._configuration._exceptions import ConfigurationError
from opentelemetry.sdk._configuration.models import (
OtlpGrpcExporter as OtlpGrpcExporterConfig,
Expand Down Expand Up @@ -146,14 +149,28 @@ def _create_otlp_grpc_span_exporter(
)


_SPAN_EXPORTER_REGISTRY: dict = {
"otlp_http": _create_otlp_http_span_exporter,
"otlp_grpc": _create_otlp_grpc_span_exporter,
"console": lambda _: ConsoleSpanExporter(),
}


def _create_span_exporter(config: SpanExporterConfig) -> SpanExporter:
"""Create a span exporter from config."""
if config.otlp_http is not None:
return _create_otlp_http_span_exporter(config.otlp_http)
if config.otlp_grpc is not None:
return _create_otlp_grpc_span_exporter(config.otlp_grpc)
if config.console is not None:
return ConsoleSpanExporter()
"""Create a span exporter from config.

Known exporter types are checked via typed fields on the SpanExporter
dataclass. Unknown exporter names captured in additional_properties
by the @_additional_properties decorator are loaded via the
``opentelemetry_traces_exporter`` entry point group.
"""
for name, factory in _SPAN_EXPORTER_REGISTRY.items():
value = getattr(config, name, None)
if value is not None:
return factory(value)
if config.additional_properties:
name = next(iter(config.additional_properties))
return load_entry_point("opentelemetry_traces_exporter", name)()
raise ConfigurationError(
"No exporter type specified in span exporter config. "
"Supported types: otlp_http, otlp_grpc, console."
Expand Down
29 changes: 24 additions & 5 deletions opentelemetry-sdk/tests/_configuration/test_logger_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,16 +228,35 @@ def test_console_exporter(self):
exporter = _create_log_record_exporter(config)
self.assertIsInstance(exporter, ConsoleLogRecordExporter)

def test_otlp_file_development_raises(self):
config = LogRecordExporterConfig(otlp_file_development={})
with self.assertRaises(ConfigurationError):
_create_log_record_exporter(config)

def test_no_exporter_type_raises(self):
config = LogRecordExporterConfig()
with self.assertRaises(ConfigurationError):
_create_log_record_exporter(config)

def test_plugin_log_exporter_loaded_via_entry_point(self):
mock_exporter = MagicMock()
mock_class = MagicMock(return_value=mock_exporter)
with patch(
"opentelemetry.sdk._configuration._common.entry_points",
return_value=[MagicMock(**{"load.return_value": mock_class})],
):
# pylint: disable=unexpected-keyword-arg
result = _create_log_record_exporter(
LogRecordExporterConfig(my_custom_exporter={})
)
self.assertIs(result, mock_exporter)

def test_unknown_log_exporter_raises_configuration_error(self):
with patch(
"opentelemetry.sdk._configuration._common.entry_points",
return_value=[],
):
with self.assertRaises(ConfigurationError):
# pylint: disable=unexpected-keyword-arg
_create_log_record_exporter(
LogRecordExporterConfig(no_such_exporter={})
)

def test_otlp_http_missing_package_raises(self):
config = LogRecordExporterConfig(
otlp_http=OtlpHttpExporterConfig(endpoint="http://localhost:4318")
Expand Down
26 changes: 26 additions & 0 deletions opentelemetry-sdk/tests/_configuration/test_meter_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,32 @@ def test_no_exporter_type_raises(self):
with self.assertRaises(ConfigurationError):
create_meter_provider(config)

def test_plugin_metric_exporter_loaded_via_entry_point(self):
mock_exporter = MagicMock()
mock_class = MagicMock(return_value=mock_exporter)
with patch(
"opentelemetry.sdk._configuration._common.entry_points",
return_value=[MagicMock(**{"load.return_value": mock_class})],
):
# pylint: disable=unexpected-keyword-arg
config = self._make_periodic_config(
PushMetricExporterConfig(my_custom_exporter={})
)
provider = create_meter_provider(config)
self.assertEqual(len(provider._sdk_config.metric_readers), 1)

def test_unknown_metric_exporter_raises_configuration_error(self):
with patch(
"opentelemetry.sdk._configuration._common.entry_points",
return_value=[],
):
# pylint: disable=unexpected-keyword-arg
config = self._make_periodic_config(
PushMetricExporterConfig(no_such_exporter={})
)
with self.assertRaises(ConfigurationError):
create_meter_provider(config)

def test_multiple_readers(self):
config = MeterProviderConfig(
readers=[
Expand Down
28 changes: 28 additions & 0 deletions opentelemetry-sdk/tests/_configuration/test_tracer_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,34 @@ def test_no_exporter_type_raises(self):
with self.assertRaises(ConfigurationError):
create_tracer_provider(config)

def test_plugin_span_exporter_loaded_via_entry_point(self):
mock_exporter = MagicMock()
mock_class = MagicMock(return_value=mock_exporter)
with patch(
"opentelemetry.sdk._configuration._common.entry_points",
return_value=[MagicMock(**{"load.return_value": mock_class})],
):
config = self._make_batch_config(
# pylint: disable=unexpected-keyword-arg
SpanExporterConfig(my_custom_exporter={})
)
provider = create_tracer_provider(config)
self.assertEqual(
len(provider._active_span_processor._span_processors), 1
)

def test_unknown_span_exporter_raises_configuration_error(self):
with patch(
"opentelemetry.sdk._configuration._common.entry_points",
return_value=[],
):
config = self._make_batch_config(
# pylint: disable=unexpected-keyword-arg
SpanExporterConfig(no_such_exporter={})
)
with self.assertRaises(ConfigurationError):
create_tracer_provider(config)


class TestCreateSpanLimits(unittest.TestCase):
# pylint: disable=no-self-use
Expand Down
Loading