Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configure auto instrumentation to support metrics #2705

Merged
merged 11 commits into from
Jun 8, 2022
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
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
srikanthccv marked this conversation as resolved.
Show resolved Hide resolved
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"
srikanthccv marked this conversation as resolved.
Show resolved Hide resolved
):
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__,
)