|
64 | 64 | ...
|
65 | 65 |
|
66 | 66 | The tracer sampler can also be configured via environment variables ``OTEL_TRACES_SAMPLER`` and ``OTEL_TRACES_SAMPLER_ARG`` (only if applicable).
|
67 |
| -The list of known values for ``OTEL_TRACES_SAMPLER`` are: |
| 67 | +The list of built-in values for ``OTEL_TRACES_SAMPLER`` are: |
68 | 68 |
|
69 | 69 | * always_on - Sampler that always samples spans, regardless of the parent span's sampling decision.
|
70 | 70 | * always_off - Sampler that never samples spans, regardless of the parent span's sampling decision.
|
|
73 | 73 | * parentbased_always_off - Sampler that respects its parent span's sampling decision, but otherwise never samples.
|
74 | 74 | * parentbased_traceidratio - Sampler that respects its parent span's sampling decision, but otherwise samples probabalistically based on rate.
|
75 | 75 |
|
76 |
| -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). |
77 |
| -
|
| 76 | +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). |
78 | 77 |
|
79 | 78 | Prev example but with environment variables. Please make sure to set the env ``OTEL_TRACES_SAMPLER=traceidratio`` and ``OTEL_TRACES_SAMPLER_ARG=0.001``.
|
80 | 79 |
|
|
97 | 96 | # created spans will now be sampled by the TraceIdRatioBased sampler with rate 1/1000.
|
98 | 97 | with trace.get_tracer(__name__).start_as_current_span("Test Span"):
|
99 | 98 | ...
|
| 99 | +
|
| 100 | +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 |
| 101 | +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 |
| 102 | +``OTEL_TRACES_SAMPLER_ARG`` environment variable. If ``OTEL_TRACES_SAMPLER_ARG`` is not configured, the input will be an empty string. For example: |
| 103 | +
|
| 104 | +.. code:: python |
| 105 | +
|
| 106 | + setup( |
| 107 | + ... |
| 108 | + entry_points={ |
| 109 | + ... |
| 110 | + "opentelemetry_traces_sampler": [ |
| 111 | + "custom_sampler_name = path.to.sampler.factory.method:CustomSamplerFactory.get_sampler" |
| 112 | + ] |
| 113 | + } |
| 114 | + ) |
| 115 | + # ... |
| 116 | + class CustomRatioSampler(Sampler): |
| 117 | + def __init__(rate): |
| 118 | + # ... |
| 119 | + # ... |
| 120 | + class CustomSamplerFactory: |
| 121 | + @staticmethod |
| 122 | + get_sampler(sampler_argument): |
| 123 | + try: |
| 124 | + rate = float(sampler_argument) |
| 125 | + return CustomSampler(rate) |
| 126 | + except ValueError: # In case argument is empty string. |
| 127 | + return CustomSampler(0.5) |
| 128 | +
|
| 129 | +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 |
| 130 | +above sampler, set ``OTEL_TRACES_SAMPLER=custom_sampler_name`` and ``OTEL_TRACES_SAMPLER_ARG=0.5``. |
100 | 131 | """
|
101 | 132 | import abc
|
102 | 133 | import enum
|
103 | 134 | import os
|
104 | 135 | from logging import getLogger
|
105 | 136 | from types import MappingProxyType
|
106 |
| -from typing import Optional, Sequence |
| 137 | +from typing import Callable, Optional, Sequence |
107 | 138 |
|
108 | 139 | # pylint: disable=unused-import
|
109 | 140 | from opentelemetry.context import Context
|
110 | 141 | from opentelemetry.sdk.environment_variables import (
|
111 | 142 | OTEL_TRACES_SAMPLER,
|
112 | 143 | OTEL_TRACES_SAMPLER_ARG,
|
113 | 144 | )
|
| 145 | +from opentelemetry.sdk.util import _import_config_components |
114 | 146 | from opentelemetry.trace import Link, SpanKind, get_current_span
|
115 | 147 | from opentelemetry.trace.span import TraceState
|
116 | 148 | from opentelemetry.util.types import Attributes
|
@@ -161,6 +193,9 @@ def __init__(
|
161 | 193 | self.trace_state = trace_state
|
162 | 194 |
|
163 | 195 |
|
| 196 | +_OTEL_SAMPLER_ENTRY_POINT_GROUP = "opentelemetry_traces_sampler" |
| 197 | + |
| 198 | + |
164 | 199 | class Sampler(abc.ABC):
|
165 | 200 | @abc.abstractmethod
|
166 | 201 | def should_sample(
|
@@ -372,26 +407,48 @@ def __init__(self, rate: float):
|
372 | 407 |
|
373 | 408 |
|
374 | 409 | def _get_from_env_or_default() -> Sampler:
|
375 |
| - trace_sampler = os.getenv( |
| 410 | + traces_sampler_name = os.getenv( |
376 | 411 | OTEL_TRACES_SAMPLER, "parentbased_always_on"
|
377 | 412 | ).lower()
|
378 |
| - if trace_sampler not in _KNOWN_SAMPLERS: |
379 |
| - _logger.warning("Couldn't recognize sampler %s.", trace_sampler) |
380 |
| - trace_sampler = "parentbased_always_on" |
381 |
| - |
382 |
| - if trace_sampler in ("traceidratio", "parentbased_traceidratio"): |
383 |
| - try: |
384 |
| - rate = float(os.getenv(OTEL_TRACES_SAMPLER_ARG)) |
385 |
| - except ValueError: |
386 |
| - _logger.warning("Could not convert TRACES_SAMPLER_ARG to float.") |
387 |
| - rate = 1.0 |
388 |
| - return _KNOWN_SAMPLERS[trace_sampler](rate) |
389 | 413 |
|
390 |
| - return _KNOWN_SAMPLERS[trace_sampler] |
| 414 | + if traces_sampler_name in _KNOWN_SAMPLERS: |
| 415 | + if traces_sampler_name in ("traceidratio", "parentbased_traceidratio"): |
| 416 | + try: |
| 417 | + rate = float(os.getenv(OTEL_TRACES_SAMPLER_ARG)) |
| 418 | + except ValueError: |
| 419 | + _logger.warning( |
| 420 | + "Could not convert TRACES_SAMPLER_ARG to float." |
| 421 | + ) |
| 422 | + rate = 1.0 |
| 423 | + return _KNOWN_SAMPLERS[traces_sampler_name](rate) |
| 424 | + return _KNOWN_SAMPLERS[traces_sampler_name] |
| 425 | + try: |
| 426 | + traces_sampler_factory = _import_sampler_factory(traces_sampler_name) |
| 427 | + sampler_arg = os.getenv(OTEL_TRACES_SAMPLER_ARG, "") |
| 428 | + traces_sampler = traces_sampler_factory(sampler_arg) |
| 429 | + if not isinstance(traces_sampler, Sampler): |
| 430 | + message = f"Traces sampler factory, {traces_sampler_factory}, produced output, {traces_sampler}, which is not a Sampler object." |
| 431 | + _logger.warning(message) |
| 432 | + raise ValueError(message) |
| 433 | + return traces_sampler |
| 434 | + except Exception as exc: # pylint: disable=broad-except |
| 435 | + _logger.warning( |
| 436 | + "Using default sampler. Failed to initialize custom sampler, %s: %s", |
| 437 | + traces_sampler_name, |
| 438 | + exc, |
| 439 | + ) |
| 440 | + return _KNOWN_SAMPLERS["parentbased_always_on"] |
391 | 441 |
|
392 | 442 |
|
393 | 443 | def _get_parent_trace_state(parent_context) -> Optional["TraceState"]:
|
394 | 444 | parent_span_context = get_current_span(parent_context).get_span_context()
|
395 | 445 | if parent_span_context is None or not parent_span_context.is_valid:
|
396 | 446 | return None
|
397 | 447 | return parent_span_context.trace_state
|
| 448 | + |
| 449 | + |
| 450 | +def _import_sampler_factory(sampler_name: str) -> Callable[[str], Sampler]: |
| 451 | + _, sampler_impl = _import_config_components( |
| 452 | + [sampler_name.strip()], _OTEL_SAMPLER_ENTRY_POINT_GROUP |
| 453 | + )[0] |
| 454 | + return sampler_impl |
0 commit comments