Skip to content

Commit

Permalink
Default configurators do more for distros (#1937)
Browse files Browse the repository at this point in the history
  • Loading branch information
NathanielRN authored Jul 23, 2021
1 parent 566df33 commit 191a55c
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 148 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.4.0-0.23b0...HEAD)
- `opentelemetry-distro` & `opentelemetry-sdk` Moved Auto Instrumentation Configurator code to SDK
to let distros use its default implementation
([#1937](https://github.com/open-telemetry/opentelemetry-python/pull/1937))

## [1.4.0-0.23b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.4.0-0.23b0) - 2021-07-21

Expand Down
2 changes: 1 addition & 1 deletion opentelemetry-distro/setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
138 changes: 4 additions & 134 deletions opentelemetry-distro/src/opentelemetry/distro/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,144 +13,14 @@
# limitations under the License.
#
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

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))
)
from opentelemetry.sdk._configuration import _OTelSDKConfigurator


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):
Expand Down
160 changes: 160 additions & 0 deletions opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# 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
"""

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

_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()
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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=[
Expand All @@ -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):
Expand Down

0 comments on commit 191a55c

Please sign in to comment.