From df33ede33d0dc0ff05351d9e88d35958ab56cb5b Mon Sep 17 00:00:00 2001 From: "(Eliseo) Nathaniel Ruiz Nowell" Date: Fri, 16 Jul 2021 13:45:05 -0700 Subject: [PATCH] Default configurators do more for distros --- CHANGELOG.md | 3 + opentelemetry-distro/setup.cfg | 4 +- .../src/opentelemetry/distro/__init__.py | 135 +------------- .../sdk/_configuration/__init__.py | 165 ++++++++++++++++++ .../tests/test_configurator.py | 26 +-- 5 files changed, 187 insertions(+), 146 deletions(-) create mode 100644 opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py rename {opentelemetry-distro => opentelemetry-sdk}/tests/test_configurator.py (91%) diff --git a/CHANGELOG.md b/CHANGELOG.md index b531ebb4d4e..77084f4a606 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased](https://github.com/open-telemetry/opentelemetry-python/compare/v1.3.0-0.22b0...HEAD) +- `opentelemtry-distro` & `opentelemetry-sdk` Moved Auto Instrumentation Configurator code to SDK + to let distros use its default implementation + ([#1934](https://github.com/open-telemetry/opentelemetry-python/pull/1934)) - `opentelemetry-semantic-conventions` Generate semconv constants update for OTel Spec 1.5.0 ([#1946](https://github.com/open-telemetry/opentelemetry-python/pull/1946)) diff --git a/opentelemetry-distro/setup.cfg b/opentelemetry-distro/setup.cfg index 74b3436f1d7..c7d1ecb040d 100644 --- a/opentelemetry-distro/setup.cfg +++ b/opentelemetry-distro/setup.cfg @@ -41,7 +41,7 @@ packages=find_namespace: zip_safe = False include_package_data = True install_requires = - opentelemetry-api ~= 1.3 + opentelemetry-api == 1.4.0.dev0 opentelemetry-instrumentation == 0.23.dev0 opentelemetry-sdk == 1.4.0.dev0 @@ -52,7 +52,7 @@ where = src opentelemetry_distro = distro = opentelemetry.distro:OpenTelemetryDistro opentelemetry_configurator = - configurator = opentelemetry.distro:Configurator + configurator = opentelemetry.distro:OpenTelemetryConfigurator [options.extras_require] test = diff --git a/opentelemetry-distro/src/opentelemetry/distro/__init__.py b/opentelemetry-distro/src/opentelemetry/distro/__init__.py index ba86e0e247d..0f65a7686c4 100644 --- a/opentelemetry-distro/src/opentelemetry/distro/__init__.py +++ b/opentelemetry-distro/src/opentelemetry/distro/__init__.py @@ -14,143 +14,16 @@ # import os from logging import getLogger -from os import environ -from typing import Sequence, Tuple -from pkg_resources import iter_entry_points - -from opentelemetry import trace -from opentelemetry.environment_variables import ( - OTEL_PYTHON_ID_GENERATOR, - OTEL_TRACES_EXPORTER, -) -from opentelemetry.instrumentation.configurator import BaseConfigurator +from opentelemetry.environment_variables import OTEL_TRACES_EXPORTER from opentelemetry.instrumentation.distro import BaseDistro -from opentelemetry.sdk.resources import SERVICE_NAME, 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.sdk._configuration import _OTelSDKConfigurator logger = getLogger(__file__) -EXPORTER_OTLP = "otlp" -EXPORTER_OTLP_SPAN = "otlp_proto_grpc_span" - -RANDOM_ID_GENERATOR = "random" -_DEFAULT_ID_GENERATOR = RANDOM_ID_GENERATOR - - -def _get_id_generator() -> str: - return environ.get(OTEL_PYTHON_ID_GENERATOR, _DEFAULT_ID_GENERATOR) - - -def _get_exporter_names() -> Sequence[str]: - trace_exporters = environ.get(OTEL_TRACES_EXPORTER) - - exporters = set() - - if trace_exporters and trace_exporters.lower().strip() != "none": - exporters.update( - { - trace_exporter.strip() - for trace_exporter in trace_exporters.split(",") - } - ) - - if EXPORTER_OTLP in exporters: - exporters.remove(EXPORTER_OTLP) - exporters.add(EXPORTER_OTLP_SPAN) - - return list(exporters) - - -def _init_tracing( - exporters: Sequence[SpanExporter], id_generator: IdGenerator -): - # if env var OTEL_RESOURCE_ATTRIBUTES is given, it will read the service_name - # from the env variable else defaults to "unknown_service" - provider = TracerProvider( - id_generator=id_generator(), - ) - trace.set_tracer_provider(provider) - - for _, exporter_class in exporters.items(): - exporter_args = {} - provider.add_span_processor( - BatchSpanProcessor(exporter_class(**exporter_args)) - ) - - -def _import_tracer_provider_config_components( - selected_components, entry_point_name -) -> Sequence[Tuple[str, object]]: - component_entry_points = { - ep.name: ep for ep in iter_entry_points(entry_point_name) - } - component_impls = [] - for selected_component in selected_components: - entry_point = component_entry_points.get(selected_component, None) - if not entry_point: - raise RuntimeError( - "Requested component '{}' not found in entry points for '{}'".format( - selected_component, entry_point_name - ) - ) - - component_impl = entry_point.load() - component_impls.append((selected_component, component_impl)) - - return component_impls - - -def _import_exporters( - exporter_names: Sequence[str], -) -> Sequence[SpanExporter]: - trace_exporters = {} - - for ( - exporter_name, - exporter_impl, - ) in _import_tracer_provider_config_components( - exporter_names, "opentelemetry_exporter" - ): - if issubclass(exporter_impl, SpanExporter): - trace_exporters[exporter_name] = exporter_impl - else: - raise RuntimeError( - "{0} is not a trace exporter".format(exporter_name) - ) - return trace_exporters - - -def _import_id_generator(id_generator_name: str) -> IdGenerator: - # pylint: disable=unbalanced-tuple-unpacking - [ - (id_generator_name, id_generator_impl) - ] = _import_tracer_provider_config_components( - [id_generator_name.strip()], "opentelemetry_id_generator" - ) - - if issubclass(id_generator_impl, IdGenerator): - return id_generator_impl - - raise RuntimeError("{0} is not an IdGenerator".format(id_generator_name)) - - -def _initialize_components(): - exporter_names = _get_exporter_names() - trace_exporters = _import_exporters(exporter_names) - id_generator_name = _get_id_generator() - id_generator = _import_id_generator(id_generator_name) - _init_tracing(trace_exporters, id_generator) - - -class Configurator(BaseConfigurator): - - # pylint: disable=no-self-use - def _configure(self, **kwargs): - _initialize_components() +class OpenTelemetryConfigurator(_OTelSDKConfigurator): + pass class OpenTelemetryDistro(BaseDistro): diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py new file mode 100644 index 00000000000..18f9bbbc8ff --- /dev/null +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py @@ -0,0 +1,165 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +""" +OpenTelemetry SDK Configurator for Easy Instrumentation with Distros +""" + +import os +from logging import getLogger +from os import environ +from typing import Sequence, Tuple + +from pkg_resources import iter_entry_points + +from opentelemetry import trace +from opentelemetry.environment_variables import ( + OTEL_PYTHON_ID_GENERATOR, + OTEL_TRACES_EXPORTER, +) +from opentelemetry.instrumentation.configurator import BaseConfigurator +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace.export import BatchSpanProcessor, SpanExporter +from opentelemetry.sdk.trace.id_generator import IdGenerator + +logger = getLogger(__file__) + + +_EXPORTER_OTLP = "otlp" +_EXPORTER_OTLP_SPAN = "otlp_proto_grpc_span" + +_RANDOM_ID_GENERATOR = "random" +_DEFAULT_ID_GENERATOR = _RANDOM_ID_GENERATOR + + +def _get_id_generator() -> str: + return environ.get(OTEL_PYTHON_ID_GENERATOR, _DEFAULT_ID_GENERATOR) + + +def _get_exporter_names() -> Sequence[str]: + trace_exporters = environ.get(OTEL_TRACES_EXPORTER) + + exporters = set() + + if trace_exporters and trace_exporters.lower().strip() != "none": + exporters.update( + { + trace_exporter.strip() + for trace_exporter in trace_exporters.split(",") + } + ) + + if _EXPORTER_OTLP in exporters: + exporters.remove(_EXPORTER_OTLP) + exporters.add(_EXPORTER_OTLP_SPAN) + + return list(exporters) + + +def _init_tracing( + exporters: Sequence[SpanExporter], id_generator: IdGenerator +): + # if env var OTEL_RESOURCE_ATTRIBUTES is given, it will read the service_name + # from the env variable else defaults to "unknown_service" + provider = TracerProvider( + id_generator=id_generator(), + ) + trace.set_tracer_provider(provider) + + for _, exporter_class in exporters.items(): + exporter_args = {} + provider.add_span_processor( + BatchSpanProcessor(exporter_class(**exporter_args)) + ) + + +def _import_tracer_provider_config_components( + selected_components, entry_point_name +) -> Sequence[Tuple[str, object]]: + component_entry_points = { + ep.name: ep for ep in iter_entry_points(entry_point_name) + } + component_impls = [] + for selected_component in selected_components: + entry_point = component_entry_points.get(selected_component, None) + if not entry_point: + raise RuntimeError( + "Requested component '{}' not found in entry points for '{}'".format( + selected_component, entry_point_name + ) + ) + + component_impl = entry_point.load() + component_impls.append((selected_component, component_impl)) + + return component_impls + + +def _import_exporters( + exporter_names: Sequence[str], +) -> Sequence[SpanExporter]: + trace_exporters = {} + + for ( + exporter_name, + exporter_impl, + ) in _import_tracer_provider_config_components( + exporter_names, "opentelemetry_exporter" + ): + if issubclass(exporter_impl, SpanExporter): + trace_exporters[exporter_name] = exporter_impl + else: + raise RuntimeError( + "{0} is not a trace exporter".format(exporter_name) + ) + return trace_exporters + + +def _import_id_generator(id_generator_name: str) -> IdGenerator: + # pylint: disable=unbalanced-tuple-unpacking + [ + (id_generator_name, id_generator_impl) + ] = _import_tracer_provider_config_components( + [id_generator_name.strip()], "opentelemetry_id_generator" + ) + + if issubclass(id_generator_impl, IdGenerator): + return id_generator_impl + + raise RuntimeError("{0} is not an IdGenerator".format(id_generator_name)) + + +def _initialize_components(): + exporter_names = _get_exporter_names() + trace_exporters = _import_exporters(exporter_names) + id_generator_name = _get_id_generator() + id_generator = _import_id_generator(id_generator_name) + _init_tracing(trace_exporters, id_generator) + + +class _OTelSDKConfigurator(BaseConfigurator): + """A basic Configurator by OTel Python for initalizing OTel SDK components + + Initializes several crucial OTel SDK components (i.e. TracerProvider, + MeterProvider, Processors...) according to a default implementation. Other + Configurators can subclass and slightly alter this initialization. + + NOTE: This class should not be instantiated nor should it become an entry + point on the `opentelemetry-sdk` package. Instead, distros should subclass + this Configurator and enchance it as needed. + """ + + def _configure(self, **kwargs): + _initialize_components() diff --git a/opentelemetry-distro/tests/test_configurator.py b/opentelemetry-sdk/tests/test_configurator.py similarity index 91% rename from opentelemetry-distro/tests/test_configurator.py rename to opentelemetry-sdk/tests/test_configurator.py index b2d2cb46d46..e7619d64dcc 100644 --- a/opentelemetry-distro/tests/test_configurator.py +++ b/opentelemetry-sdk/tests/test_configurator.py @@ -18,18 +18,18 @@ from unittest.mock import patch from opentelemetry import trace -from opentelemetry.distro import ( - EXPORTER_OTLP, - EXPORTER_OTLP_SPAN, +from opentelemetry.environment_variables import ( + OTEL_PYTHON_ID_GENERATOR, + OTEL_TRACES_EXPORTER, +) +from opentelemetry.sdk._configuration import ( + _EXPORTER_OTLP, + _EXPORTER_OTLP_SPAN, _get_exporter_names, _get_id_generator, _import_id_generator, _init_tracing, ) -from opentelemetry.environment_variables import ( - OTEL_PYTHON_ID_GENERATOR, - OTEL_TRACES_EXPORTER, -) from opentelemetry.sdk.resources import SERVICE_NAME, Resource from opentelemetry.sdk.trace.id_generator import IdGenerator, RandomIdGenerator @@ -87,10 +87,10 @@ class TestTraceInit(TestCase): def setUp(self): super() self.get_provider_patcher = patch( - "opentelemetry.distro.TracerProvider", Provider + "opentelemetry.sdk._configuration.TracerProvider", Provider ) self.get_processor_patcher = patch( - "opentelemetry.distro.BatchSpanProcessor", Processor + "opentelemetry.sdk._configuration.BatchSpanProcessor", Processor ) self.set_provider_patcher = patch( "opentelemetry.trace.set_tracer_provider" @@ -143,8 +143,8 @@ def test_trace_init_otlp(self): ) @patch.dict(environ, {OTEL_PYTHON_ID_GENERATOR: "custom_id_generator"}) - @patch("opentelemetry.distro.IdGenerator", new=IdGenerator) - @patch("opentelemetry.distro.iter_entry_points") + @patch("opentelemetry.sdk._configuration.IdGenerator", new=IdGenerator) + @patch("opentelemetry.sdk._configuration.iter_entry_points") def test_trace_init_custom_id_generator(self, mock_iter_entry_points): mock_iter_entry_points.configure_mock( return_value=[ @@ -160,9 +160,9 @@ def test_trace_init_custom_id_generator(self, mock_iter_entry_points): class TestExporterNames(TestCase): def test_otlp_exporter_overwrite(self): - for exporter in [EXPORTER_OTLP, EXPORTER_OTLP_SPAN]: + for exporter in [_EXPORTER_OTLP, _EXPORTER_OTLP_SPAN]: with patch.dict(environ, {OTEL_TRACES_EXPORTER: exporter}): - self.assertEqual(_get_exporter_names(), [EXPORTER_OTLP_SPAN]) + self.assertEqual(_get_exporter_names(), [_EXPORTER_OTLP_SPAN]) @patch.dict(environ, {OTEL_TRACES_EXPORTER: "jaeger,zipkin"}) def test_multiple_exporters(self):