Skip to content

Commit

Permalink
Configure auto instrumentation to support metrics (#2705)
Browse files Browse the repository at this point in the history
* Configure auto instrumentation to support metrics

* Add tests

* Update test_configurator.py

* Add CHANGELOG entry

* lint
  • Loading branch information
srikanthccv authored Jun 8, 2022
1 parent 928d333 commit b83c2ae
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 10 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#2728](https://github.com/open-telemetry/opentelemetry-python/pull/2728))
- fix: update entry point object references for metrics
([#2731](https://github.com/open-telemetry/opentelemetry-python/pull/2731))
- Configure auto instrumentation to support metrics
([#2705](https://github.com/open-telemetry/opentelemetry-python/pull/2705))

## [1.12.0rc1-0.31b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.12.0rc1-0.31b0) - 2022-05-17

Expand Down
60 changes: 55 additions & 5 deletions opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@

from pkg_resources import iter_entry_points

from opentelemetry import trace
from opentelemetry.environment_variables import (
OTEL_LOGS_EXPORTER,
OTEL_METRICS_EXPORTER,
OTEL_PYTHON_ID_GENERATOR,
OTEL_TRACES_EXPORTER,
)
from opentelemetry.metrics import set_meter_provider
from opentelemetry.sdk._logs import (
LogEmitterProvider,
LoggingHandler,
Expand All @@ -40,11 +41,17 @@
from opentelemetry.sdk.environment_variables import (
_OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED,
)
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import (
MetricExporter,
PeriodicExportingMetricReader,
)
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor, SpanExporter
from opentelemetry.sdk.trace.id_generator import IdGenerator
from opentelemetry.semconv.resource import ResourceAttributes
from opentelemetry.trace import set_tracer_provider

_EXPORTER_OTLP = "otlp"
_EXPORTER_OTLP_PROTO_GRPC = "otlp_proto_grpc"
Expand Down Expand Up @@ -87,7 +94,7 @@ def _init_tracing(
id_generator=id_generator(),
resource=Resource.create(auto_resource),
)
trace.set_tracer_provider(provider)
set_tracer_provider(provider)

for _, exporter_class in exporters.items():
exporter_args = {}
Expand All @@ -96,6 +103,33 @@ def _init_tracing(
)


def _init_metrics(
exporters: Dict[str, Type[MetricExporter]],
auto_instrumentation_version: Optional[str] = None,
):
# if env var OTEL_RESOURCE_ATTRIBUTES is given, it will read the service_name
# from the env variable else defaults to "unknown_service"
auto_resource = {}
# populate version if using auto-instrumentation
if auto_instrumentation_version:
auto_resource[
ResourceAttributes.TELEMETRY_AUTO_VERSION
] = auto_instrumentation_version

metric_readers = []

for _, exporter_class in exporters.items():
exporter_args = {}
metric_readers.append(
PeriodicExportingMetricReader(exporter_class(**exporter_args))
)

provider = MeterProvider(
resource=Resource.create(auto_resource), metric_readers=metric_readers
)
set_meter_provider(provider)


def _init_logging(
exporters: Dict[str, Type[LogExporter]],
auto_instrumentation_version: Optional[str] = None,
Expand Down Expand Up @@ -145,9 +179,15 @@ def _import_config_components(

def _import_exporters(
trace_exporter_names: Sequence[str],
metric_exporter_names: Sequence[str],
log_exporter_names: Sequence[str],
) -> Tuple[Dict[str, Type[SpanExporter]], Dict[str, Type[LogExporter]]]:
) -> Tuple[
Dict[str, Type[SpanExporter]],
Dict[str, Type[MetricExporter]],
Dict[str, Type[LogExporter]],
]:
trace_exporters = {}
metric_exporters = {}
log_exporters = {}

for (exporter_name, exporter_impl,) in _import_config_components(
Expand All @@ -158,6 +198,14 @@ def _import_exporters(
else:
raise RuntimeError(f"{exporter_name} is not a trace exporter")

for (exporter_name, exporter_impl,) in _import_config_components(
metric_exporter_names, "opentelemetry_metrics_exporter"
):
if issubclass(exporter_impl, MetricExporter):
metric_exporters[exporter_name] = exporter_impl
else:
raise RuntimeError(f"{exporter_name} is not a metric exporter")

for (exporter_name, exporter_impl,) in _import_config_components(
log_exporter_names, "opentelemetry_logs_exporter"
):
Expand All @@ -166,7 +214,7 @@ def _import_exporters(
else:
raise RuntimeError(f"{exporter_name} is not a log exporter")

return trace_exporters, log_exporters
return trace_exporters, metric_exporters, log_exporters


def _import_id_generator(id_generator_name: str) -> IdGenerator:
Expand All @@ -182,13 +230,15 @@ def _import_id_generator(id_generator_name: str) -> IdGenerator:


def _initialize_components(auto_instrumentation_version):
trace_exporters, log_exporters = _import_exporters(
trace_exporters, metric_exporters, log_exporters = _import_exporters(
_get_exporter_names(environ.get(OTEL_TRACES_EXPORTER)),
_get_exporter_names(environ.get(OTEL_METRICS_EXPORTER)),
_get_exporter_names(environ.get(OTEL_LOGS_EXPORTER)),
)
id_generator_name = _get_id_generator()
id_generator = _import_id_generator(id_generator_name)
_init_tracing(trace_exporters, id_generator, auto_instrumentation_version)
_init_metrics(metric_exporters, auto_instrumentation_version)
logging_enabled = os.getenv(
_OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED, "false"
)
Expand Down
118 changes: 113 additions & 5 deletions opentelemetry-sdk/tests/test_configurator.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# type: ignore
# pylint: skip-file

import logging
from os import environ
from typing import Dict, Iterable, Optional
from unittest import TestCase
from unittest.mock import patch

Expand All @@ -28,12 +30,21 @@
_import_exporters,
_import_id_generator,
_init_logging,
_init_metrics,
_init_tracing,
_initialize_components,
)
from opentelemetry.sdk._logs import LoggingHandler
from opentelemetry.sdk._logs.export import ConsoleLogExporter
from opentelemetry.sdk.metrics.export import ConsoleMetricExporter
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import (
AggregationTemporality,
ConsoleMetricExporter,
Metric,
MetricExporter,
MetricReader,
)
from opentelemetry.sdk.metrics.view import Aggregation
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
from opentelemetry.sdk.trace.export import ConsoleSpanExporter
from opentelemetry.sdk.trace.id_generator import IdGenerator, RandomIdGenerator
Expand Down Expand Up @@ -61,6 +72,10 @@ def get_log_emitter(self, name):
return DummyLogEmitter(name, self.resource, self.processor)


class DummyMeterProvider(MeterProvider):
pass


class DummyLogEmitter:
def __init__(self, name, resource, processor):
self.name = name
Expand Down Expand Up @@ -93,6 +108,44 @@ def __init__(self, exporter):
self.exporter = exporter


class DummyMetricReader(MetricReader):
def __init__(
self,
exporter: MetricExporter,
preferred_temporality: Dict[type, AggregationTemporality] = None,
preferred_aggregation: Dict[type, Aggregation] = None,
export_interval_millis: Optional[float] = None,
export_timeout_millis: Optional[float] = None,
) -> None:
super().__init__(
preferred_temporality=preferred_temporality,
preferred_aggregation=preferred_aggregation,
)
self.exporter = exporter

def _receive_metrics(
self,
metrics: Iterable[Metric],
timeout_millis: float = 10_000,
**kwargs,
) -> None:
self.exporter.export(None)

def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None:
return True


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

def export(self, batch):
self.export_called = True

def shutdown(self):
pass


class Exporter:
def __init__(self):
tracer_provider = trace.get_tracer_provider()
Expand Down Expand Up @@ -148,7 +201,7 @@ def setUp(self):
"opentelemetry.sdk._configuration.BatchSpanProcessor", Processor
)
self.set_provider_patcher = patch(
"opentelemetry.trace.set_tracer_provider"
"opentelemetry.sdk._configuration.set_tracer_provider"
)

self.get_provider_mock = self.get_provider_patcher.start()
Expand Down Expand Up @@ -304,6 +357,61 @@ def test_logging_init_enable_env(self, logging_mock, tracing_mock):
self.assertEqual(tracing_mock.call_count, 1)


class TestMetricsInit(TestCase):
def setUp(self):
self.metric_reader_patch = patch(
"opentelemetry.sdk._configuration.PeriodicExportingMetricReader",
DummyMetricReader,
)
self.provider_patch = patch(
"opentelemetry.sdk._configuration.MeterProvider",
DummyMeterProvider,
)
self.set_provider_patch = patch(
"opentelemetry.sdk._configuration.set_meter_provider"
)

self.metric_reader_mock = self.metric_reader_patch.start()
self.provider_mock = self.provider_patch.start()
self.set_provider_mock = self.set_provider_patch.start()

def tearDown(self):
self.metric_reader_patch.stop()
self.set_provider_patch.stop()
self.provider_patch.stop()

def test_metrics_init_empty(self):
_init_metrics({}, "auto-version")
self.assertEqual(self.set_provider_mock.call_count, 1)
provider = self.set_provider_mock.call_args[0][0]
self.assertIsInstance(provider, DummyMeterProvider)
self.assertIsInstance(provider._sdk_config.resource, Resource)
self.assertEqual(
provider._sdk_config.resource.attributes.get(
"telemetry.auto.version"
),
"auto-version",
)

@patch.dict(
environ,
{"OTEL_RESOURCE_ATTRIBUTES": "service.name=otlp-service"},
)
def test_metrics_init_exporter(self):
_init_metrics({"otlp": DummyOTLPMetricExporter})
self.assertEqual(self.set_provider_mock.call_count, 1)
provider = self.set_provider_mock.call_args[0][0]
self.assertIsInstance(provider, DummyMeterProvider)
self.assertIsInstance(provider._sdk_config.resource, Resource)
self.assertEqual(
provider._sdk_config.resource.attributes.get("service.name"),
"otlp-service",
)
reader = provider._sdk_config.metric_readers[0]
self.assertIsInstance(reader, DummyMetricReader)
self.assertIsInstance(reader.exporter, DummyOTLPMetricExporter)


class TestExporterNames(TestCase):
def test_otlp_exporter_overwrite(self):
for exporter in [_EXPORTER_OTLP, _EXPORTER_OTLP_PROTO_GRPC]:
Expand All @@ -328,8 +436,8 @@ def test_empty_exporters(self):

class TestImportExporters(TestCase):
def test_console_exporters(self):
trace_exporters, logs_exporters = _import_exporters(
["console"], ["console"]
trace_exporters, metric_exporterts, logs_exporters = _import_exporters(
["console"], ["console"], ["console"]
)
self.assertEqual(
trace_exporters["console"].__class__, ConsoleSpanExporter.__class__
Expand All @@ -338,6 +446,6 @@ def test_console_exporters(self):
logs_exporters["console"].__class__, ConsoleLogExporter.__class__
)
self.assertEqual(
logs_exporters["console"].__class__,
metric_exporterts["console"].__class__,
ConsoleMetricExporter.__class__,
)

0 comments on commit b83c2ae

Please sign in to comment.