Skip to content

Commit

Permalink
Merge branch 'main' into issue_2967
Browse files Browse the repository at this point in the history
  • Loading branch information
srikanthccv authored Oct 29, 2022
2 parents 3039c4b + 35ba257 commit 57c0977
Show file tree
Hide file tree
Showing 6 changed files with 358 additions and 60 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Add and use missing metrics environment variables
([#2968](https://github.com/open-telemetry/opentelemetry-python/pull/2968))
- Enabled custom samplers via entry points
([#2972](https://github.com/open-telemetry/opentelemetry-python/pull/2972))
- Update log symbol names
([#2943](https://github.com/open-telemetry/opentelemetry-python/pull/2943))
- Update explicit histogram bucket boundaries
Expand Down
27 changes: 3 additions & 24 deletions opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
from os import environ
from typing import Dict, Optional, Sequence, Tuple, Type

from pkg_resources import iter_entry_points
from typing_extensions import Literal

from opentelemetry.environment_variables import (
Expand Down Expand Up @@ -55,6 +54,7 @@
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.util import _import_config_components
from opentelemetry.semconv.resource import ResourceAttributes
from opentelemetry.trace import set_tracer_provider

Expand Down Expand Up @@ -226,26 +226,6 @@ def _init_logging(
logging.getLogger().addHandler(handler)


def _import_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(
f"Requested component '{selected_component}' not found in entry points for '{entry_point_name}'"
)

component_impl = entry_point.load()
component_impls.append((selected_component, component_impl))

return component_impls


def _import_exporters(
trace_exporter_names: Sequence[str],
metric_exporter_names: Sequence[str],
Expand Down Expand Up @@ -287,10 +267,9 @@ def _import_exporters(


def _import_id_generator(id_generator_name: str) -> IdGenerator:
# pylint: disable=unbalanced-tuple-unpacking
[(id_generator_name, id_generator_impl)] = _import_config_components(
id_generator_name, id_generator_impl = _import_config_components(
[id_generator_name.strip()], "opentelemetry_id_generator"
)
)[0]

if issubclass(id_generator_impl, IdGenerator):
return id_generator_impl
Expand Down
91 changes: 74 additions & 17 deletions opentelemetry-sdk/src/opentelemetry/sdk/trace/sampling.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
...
The tracer sampler can also be configured via environment variables ``OTEL_TRACES_SAMPLER`` and ``OTEL_TRACES_SAMPLER_ARG`` (only if applicable).
The list of known values for ``OTEL_TRACES_SAMPLER`` are:
The list of built-in values for ``OTEL_TRACES_SAMPLER`` are:
* always_on - Sampler that always samples spans, regardless of the parent span's sampling decision.
* always_off - Sampler that never samples spans, regardless of the parent span's sampling decision.
Expand All @@ -73,8 +73,7 @@
* parentbased_always_off - Sampler that respects its parent span's sampling decision, but otherwise never samples.
* parentbased_traceidratio - Sampler that respects its parent span's sampling decision, but otherwise samples probabalistically based on rate.
Sampling probability can be set with ``OTEL_TRACES_SAMPLER_ARG`` if the sampler is traceidratio or parentbased_traceidratio, when not provided rate will be set to 1.0 (maximum rate possible).
Sampling probability can be set with ``OTEL_TRACES_SAMPLER_ARG`` if the sampler is traceidratio or parentbased_traceidratio. Rate must be in the range [0.0,1.0]. When not provided rate will be set to 1.0 (maximum rate possible).
Prev example but with environment variables. Please make sure to set the env ``OTEL_TRACES_SAMPLER=traceidratio`` and ``OTEL_TRACES_SAMPLER_ARG=0.001``.
Expand All @@ -97,20 +96,53 @@
# created spans will now be sampled by the TraceIdRatioBased sampler with rate 1/1000.
with trace.get_tracer(__name__).start_as_current_span("Test Span"):
...
In order to create a configurable custom sampler, create an entry point for the custom sampler factory method under the entry point group, ``opentelemetry_traces_sampler``. The custom sampler factory
method must be of type ``Callable[[str], Sampler]``, taking a single string argument and returning a Sampler object. The single input will come from the string value of the
``OTEL_TRACES_SAMPLER_ARG`` environment variable. If ``OTEL_TRACES_SAMPLER_ARG`` is not configured, the input will be an empty string. For example:
.. code:: python
setup(
...
entry_points={
...
"opentelemetry_traces_sampler": [
"custom_sampler_name = path.to.sampler.factory.method:CustomSamplerFactory.get_sampler"
]
}
)
# ...
class CustomRatioSampler(Sampler):
def __init__(rate):
# ...
# ...
class CustomSamplerFactory:
@staticmethod
get_sampler(sampler_argument):
try:
rate = float(sampler_argument)
return CustomSampler(rate)
except ValueError: # In case argument is empty string.
return CustomSampler(0.5)
In order to configure you application with a custom sampler's entry point, set the ``OTEL_TRACES_SAMPLER`` environment variable to the key name of the entry point. For example, to configured the
above sampler, set ``OTEL_TRACES_SAMPLER=custom_sampler_name`` and ``OTEL_TRACES_SAMPLER_ARG=0.5``.
"""
import abc
import enum
import os
from logging import getLogger
from types import MappingProxyType
from typing import Optional, Sequence
from typing import Callable, Optional, Sequence

# pylint: disable=unused-import
from opentelemetry.context import Context
from opentelemetry.sdk.environment_variables import (
OTEL_TRACES_SAMPLER,
OTEL_TRACES_SAMPLER_ARG,
)
from opentelemetry.sdk.util import _import_config_components
from opentelemetry.trace import Link, SpanKind, get_current_span
from opentelemetry.trace.span import TraceState
from opentelemetry.util.types import Attributes
Expand Down Expand Up @@ -161,6 +193,9 @@ def __init__(
self.trace_state = trace_state


_OTEL_SAMPLER_ENTRY_POINT_GROUP = "opentelemetry_traces_sampler"


class Sampler(abc.ABC):
@abc.abstractmethod
def should_sample(
Expand Down Expand Up @@ -372,26 +407,48 @@ def __init__(self, rate: float):


def _get_from_env_or_default() -> Sampler:
trace_sampler = os.getenv(
traces_sampler_name = os.getenv(
OTEL_TRACES_SAMPLER, "parentbased_always_on"
).lower()
if trace_sampler not in _KNOWN_SAMPLERS:
_logger.warning("Couldn't recognize sampler %s.", trace_sampler)
trace_sampler = "parentbased_always_on"

if trace_sampler in ("traceidratio", "parentbased_traceidratio"):
try:
rate = float(os.getenv(OTEL_TRACES_SAMPLER_ARG))
except ValueError:
_logger.warning("Could not convert TRACES_SAMPLER_ARG to float.")
rate = 1.0
return _KNOWN_SAMPLERS[trace_sampler](rate)

return _KNOWN_SAMPLERS[trace_sampler]
if traces_sampler_name in _KNOWN_SAMPLERS:
if traces_sampler_name in ("traceidratio", "parentbased_traceidratio"):
try:
rate = float(os.getenv(OTEL_TRACES_SAMPLER_ARG))
except ValueError:
_logger.warning(
"Could not convert TRACES_SAMPLER_ARG to float."
)
rate = 1.0
return _KNOWN_SAMPLERS[traces_sampler_name](rate)
return _KNOWN_SAMPLERS[traces_sampler_name]
try:
traces_sampler_factory = _import_sampler_factory(traces_sampler_name)
sampler_arg = os.getenv(OTEL_TRACES_SAMPLER_ARG, "")
traces_sampler = traces_sampler_factory(sampler_arg)
if not isinstance(traces_sampler, Sampler):
message = f"Traces sampler factory, {traces_sampler_factory}, produced output, {traces_sampler}, which is not a Sampler object."
_logger.warning(message)
raise ValueError(message)
return traces_sampler
except Exception as exc: # pylint: disable=broad-except
_logger.warning(
"Using default sampler. Failed to initialize custom sampler, %s: %s",
traces_sampler_name,
exc,
)
return _KNOWN_SAMPLERS["parentbased_always_on"]


def _get_parent_trace_state(parent_context) -> Optional["TraceState"]:
parent_span_context = get_current_span(parent_context).get_span_context()
if parent_span_context is None or not parent_span_context.is_valid:
return None
return parent_span_context.trace_state


def _import_sampler_factory(sampler_name: str) -> Callable[[str], Sampler]:
_, sampler_impl = _import_config_components(
[sampler_name.strip()], _OTEL_SAMPLER_ENTRY_POINT_GROUP
)[0]
return sampler_impl
30 changes: 25 additions & 5 deletions opentelemetry-sdk/src/opentelemetry/sdk/util/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@

import datetime
import threading
from collections import OrderedDict, deque
from collections.abc import MutableMapping, Sequence
from typing import Optional
from collections import OrderedDict, abc, deque
from typing import List, Optional, Sequence, Tuple

from deprecated import deprecated
from pkg_resources import iter_entry_points


def ns_to_iso_str(nanoseconds):
Expand All @@ -41,7 +41,27 @@ def get_dict_as_key(labels):
)


class BoundedList(Sequence):
def _import_config_components(
selected_components: List[str], entry_point_name: str
) -> 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(
f"Requested component '{selected_component}' not found in entry points for '{entry_point_name}'"
)

component_impl = entry_point.load()
component_impls.append((selected_component, component_impl))

return component_impls


class BoundedList(abc.Sequence):
"""An append only list with a fixed max size.
Calls to `append` and `extend` will drop the oldest elements if there is
Expand Down Expand Up @@ -92,7 +112,7 @@ def from_seq(cls, maxlen, seq):


@deprecated(version="1.4.0") # type: ignore
class BoundedDict(MutableMapping):
class BoundedDict(abc.MutableMapping):
"""An ordered dict with a fixed max capacity.
Oldest elements are dropped when the dict is full and a new element is
Expand Down
2 changes: 1 addition & 1 deletion opentelemetry-sdk/tests/test_configurator.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ def test_trace_init_otlp(self):

@patch.dict(environ, {OTEL_PYTHON_ID_GENERATOR: "custom_id_generator"})
@patch("opentelemetry.sdk._configuration.IdGenerator", new=IdGenerator)
@patch("opentelemetry.sdk._configuration.iter_entry_points")
@patch("opentelemetry.sdk.util.iter_entry_points")
def test_trace_init_custom_id_generator(self, mock_iter_entry_points):
mock_iter_entry_points.configure_mock(
return_value=[
Expand Down
Loading

0 comments on commit 57c0977

Please sign in to comment.