-
Notifications
You must be signed in to change notification settings - Fork 882
Add support for composite samplers in declarative config #5201
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| `opentelemetry-sdk`: Add `composite/development` samplers support to declarative file configuration |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,11 +3,18 @@ | |
|
|
||
| from __future__ import annotations | ||
|
|
||
| import logging | ||
| import re | ||
| from collections.abc import Sequence | ||
| from typing import Protocol | ||
|
|
||
| from opentelemetry.context import Context | ||
| from opentelemetry.trace import Link, SpanKind, TraceState | ||
| from opentelemetry.trace import ( | ||
| Link, | ||
| SpanKind, | ||
| TraceState, | ||
| get_current_span, | ||
| ) | ||
| from opentelemetry.util.types import AnyValue, Attributes | ||
|
|
||
| from ._composable import ComposableSampler, SamplingIntent | ||
|
|
@@ -32,6 +39,9 @@ class AttributePredicate: | |
| """An exact match of an attribute value""" | ||
|
|
||
| def __init__(self, key: str, value: AnyValue): | ||
| logging.warning( | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a predicate I had around when implemented the rule based sampler, does not make much sense to keep around since we have one that implements a superset of its features |
||
| "This is deprecated, use AttributeValuesPredicate instead" | ||
| ) | ||
| self.key = key | ||
| self.value = value | ||
|
|
||
|
|
@@ -52,6 +62,184 @@ def __str__(self): | |
| return f"{self.key}={self.value}" | ||
|
|
||
|
|
||
| class AlwaysMatchPredicate: | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Even if these are not spec'ed in the SDK specification I've added these predicates near the rule based sampler. It looked more natural to have them here instead of in sdk configuration side for discoverability if you are not using declarative config. I think Java does not do that though. |
||
| def __call__( | ||
| self, | ||
| parent_ctx: Context | None, | ||
| name: str, | ||
| span_kind: SpanKind | None, | ||
| attributes: Attributes, | ||
| links: Sequence[Link] | None, | ||
| trace_state: TraceState | None, | ||
| ) -> bool: | ||
| return True | ||
|
|
||
| def __str__(self) -> str: | ||
| return "AlwaysMatch" | ||
|
|
||
|
|
||
| class AllPredicate: | ||
| def __init__(self, predicates: Sequence[PredicateT]): | ||
| self._predicates = tuple(predicates) | ||
|
|
||
| def __call__( | ||
| self, | ||
| parent_ctx: Context | None, | ||
| name: str, | ||
| span_kind: SpanKind | None, | ||
| attributes: Attributes, | ||
| links: Sequence[Link] | None, | ||
| trace_state: TraceState | None, | ||
| ) -> bool: | ||
| return all( | ||
| predicate( | ||
| parent_ctx, | ||
| name, | ||
| span_kind, | ||
| attributes, | ||
| links, | ||
| trace_state, | ||
| ) | ||
| for predicate in self._predicates | ||
| ) | ||
|
|
||
| def __str__(self) -> str: | ||
| return " && ".join(str(predicate) for predicate in self._predicates) | ||
|
|
||
|
|
||
| class AttributeValuesPredicate: | ||
| def __init__(self, key: str, values: Sequence[str]): | ||
| self._key = key | ||
| self._values = frozenset(values) | ||
|
|
||
| def __call__( | ||
| self, | ||
| parent_ctx: Context | None, | ||
| name: str, | ||
| span_kind: SpanKind | None, | ||
| attributes: Attributes, | ||
| links: Sequence[Link] | None, | ||
| trace_state: TraceState | None, | ||
| ) -> bool: | ||
| if not attributes or self._key not in attributes: | ||
| return False | ||
| return any( | ||
| str(value) in self._values | ||
| for value in _attribute_values(attributes[self._key]) | ||
| ) | ||
|
|
||
| def __str__(self) -> str: | ||
| values = ",".join(sorted(self._values)) | ||
| return f"{self._key} in [{values}]" | ||
|
|
||
|
|
||
| class AttributePatternsPredicate: | ||
| def __init__( | ||
| self, | ||
| key: str, | ||
| included: Sequence[str] | None = None, | ||
| excluded: Sequence[str] | None = None, | ||
| ): | ||
| self._key = key | ||
| self._included = tuple(included or ()) | ||
| self._excluded = tuple(excluded or ()) | ||
|
|
||
| def __call__( | ||
| self, | ||
| parent_ctx: Context | None, | ||
| name: str, | ||
| span_kind: SpanKind | None, | ||
| attributes: Attributes, | ||
| links: Sequence[Link] | None, | ||
| trace_state: TraceState | None, | ||
| ) -> bool: | ||
| if not attributes or self._key not in attributes: | ||
| return False | ||
| return any( | ||
| self._matches_value(str(value)) | ||
| for value in _attribute_values(attributes[self._key]) | ||
| ) | ||
|
|
||
| def _matches_value(self, value: str) -> bool: | ||
| included = not self._included or any( | ||
| _glob_matches(value, pattern) for pattern in self._included | ||
| ) | ||
| excluded = any( | ||
| _glob_matches(value, pattern) for pattern in self._excluded | ||
| ) | ||
| return included and not excluded | ||
|
|
||
| def __str__(self) -> str: | ||
| return f"{self._key} matches" | ||
|
|
||
|
|
||
| class SpanKindPredicate: | ||
| def __init__(self, span_kinds: Sequence[SpanKind]): | ||
| self._span_kinds = frozenset(span_kinds) | ||
|
|
||
| def __call__( | ||
| self, | ||
| parent_ctx: Context | None, | ||
| name: str, | ||
| span_kind: SpanKind | None, | ||
| attributes: Attributes, | ||
| links: Sequence[Link] | None, | ||
| trace_state: TraceState | None, | ||
| ) -> bool: | ||
| return span_kind in self._span_kinds | ||
|
|
||
| def __str__(self) -> str: | ||
| kinds = ",".join(kind.name.lower() for kind in self._span_kinds) | ||
| return f"span_kind in [{kinds}]" | ||
|
|
||
|
|
||
| class ParentPredicate: | ||
| def __init__(self, parents: Sequence[str]): | ||
| self._parents = frozenset(parents) | ||
|
|
||
| def __call__( | ||
| self, | ||
| parent_ctx: Context | None, | ||
| name: str, | ||
| span_kind: SpanKind | None, | ||
| attributes: Attributes, | ||
| links: Sequence[Link] | None, | ||
| trace_state: TraceState | None, | ||
| ) -> bool: | ||
| parent_span_context = get_current_span(parent_ctx).get_span_context() | ||
| if not parent_span_context.is_valid: | ||
| parent = "none" | ||
| elif parent_span_context.is_remote: | ||
| parent = "remote" | ||
| else: | ||
| parent = "local" | ||
| return parent in self._parents | ||
|
|
||
| def __str__(self) -> str: | ||
| parents = ",".join(self._parents) | ||
| return f"parent in [{parents}]" | ||
|
|
||
|
|
||
| def _attribute_values(value): | ||
| if isinstance(value, Sequence) and not isinstance( | ||
| value, (str, bytes, bytearray) | ||
| ): | ||
| return value | ||
| return (value,) | ||
|
|
||
|
|
||
| def _glob_matches(value: str, glob_pattern: str) -> bool: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not use |
||
| pattern_parts = [] | ||
| for char in glob_pattern: | ||
| if char == "*": | ||
| pattern_parts.append(".*") | ||
| elif char == "?": | ||
| pattern_parts.append(".") | ||
| else: | ||
| pattern_parts.append(re.escape(char)) | ||
| return re.fullmatch("".join(pattern_parts), value) is not None | ||
|
|
||
|
|
||
| RulesT = Sequence[tuple[PredicateT, ComposableSampler]] | ||
|
|
||
| _non_sampling_intent = SamplingIntent( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even if this is still in development this is not gated under an option to filter stable only components because with declarative config the enablement of experimental stuff is explicit by the user