odigos-opentelemetry-python 1.0.77__tar.gz → 1.0.79__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/PKG-INFO +1 -1
- {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/initializer/components.py +30 -7
- odigos_opentelemetry_python-1.0.79/initializer/odigos_sampler.py +168 -0
- odigos_opentelemetry_python-1.0.79/initializer/version.py +1 -0
- {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/odigos_opentelemetry_python.egg-info/PKG-INFO +1 -1
- {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/odigos_opentelemetry_python.egg-info/SOURCES.txt +0 -1
- {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/opamp/http_client.py +1 -2
- {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/pyproject.toml +1 -1
- odigos_opentelemetry_python-1.0.77/initializer/odigos_sampler.py +0 -98
- odigos_opentelemetry_python-1.0.77/initializer/sampling_operators.py +0 -7
- odigos_opentelemetry_python-1.0.77/initializer/version.py +0 -1
- {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/LICENSE +0 -0
- {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/README.md +0 -0
- {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/initializer/__init__.py +0 -0
- {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/initializer/distro/__init__.py +0 -0
- {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/initializer/distro/distro.py +0 -0
- {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/initializer/distro/instrumentation_registry.py +0 -0
- {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/initializer/envs.py +0 -0
- {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/initializer/exit_hook.py +0 -0
- {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/initializer/lib_handling.py +0 -0
- {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/initializer/post_instrument.py +0 -0
- {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/initializer/process_resource.py +0 -0
- {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/odigos_opentelemetry_python.egg-info/dependency_links.txt +0 -0
- {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/odigos_opentelemetry_python.egg-info/entry_points.txt +0 -0
- {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/odigos_opentelemetry_python.egg-info/requires.txt +0 -0
- {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/odigos_opentelemetry_python.egg-info/top_level.txt +0 -0
- {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/opamp/__init__.py +0 -0
- {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/opamp/anyvalue_pb2.py +0 -0
- {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/opamp/config.py +0 -0
- {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/opamp/health_status.py +0 -0
- {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/opamp/opamp_pb2.py +0 -0
- {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/opamp/opamp_registry.py +0 -0
- {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/opamp/utils.py +0 -0
- {odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/setup.cfg +0 -0
{odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/initializer/components.py
RENAMED
|
@@ -7,6 +7,19 @@ import atexit
|
|
|
7
7
|
import sys
|
|
8
8
|
import os
|
|
9
9
|
|
|
10
|
+
|
|
11
|
+
# opentelemetry is a namespace package — Python merges all opentelemetry/
|
|
12
|
+
# directories from sys.path into opentelemetry.__path__. If the user app
|
|
13
|
+
# bundles a different opentelemetry-api version, the user's copy can end up
|
|
14
|
+
# first and break agent imports (e.g. ImportError on std_to_otel).
|
|
15
|
+
# Fix: reorder opentelemetry.__path__ so agent paths are always searched first.
|
|
16
|
+
import opentelemetry
|
|
17
|
+
|
|
18
|
+
_agent_prefix = ('/var/odigos/', '/etc/odigos-vmagent/')
|
|
19
|
+
_agent_otel = [p for p in opentelemetry.__path__ if any(p.startswith(pfx) for pfx in _agent_prefix)]
|
|
20
|
+
_other_otel = [p for p in opentelemetry.__path__ if not any(p.startswith(pfx) for pfx in _agent_prefix)]
|
|
21
|
+
opentelemetry.__path__[:] = _agent_otel + _other_otel
|
|
22
|
+
|
|
10
23
|
import opentelemetry.sdk._configuration as sdk_config
|
|
11
24
|
from .process_resource import OdigosProcessResourceDetector
|
|
12
25
|
from opentelemetry.sdk.resources import Resource
|
|
@@ -40,6 +53,8 @@ from opamp.http_client import OpAMPHTTPClient, MockOpAMPClient
|
|
|
40
53
|
|
|
41
54
|
MINIMUM_PYTHON_SUPPORTED_VERSION = (3, 9)
|
|
42
55
|
|
|
56
|
+
_odigos_sampler = None
|
|
57
|
+
|
|
43
58
|
|
|
44
59
|
def initialize_components(trace_exporters = False, span_processor = None):
|
|
45
60
|
# Ensure each child process after fork gets a fresh OpAMP client
|
|
@@ -105,12 +120,14 @@ def initialize_components(trace_exporters = False, span_processor = None):
|
|
|
105
120
|
reload_distro_modules()
|
|
106
121
|
|
|
107
122
|
def initialize_traces_if_enabled(trace_exporters, resource, span_processor = None, signals = None, initial_sampler_config = None):
|
|
123
|
+
global _odigos_sampler
|
|
108
124
|
# In case collectors signals support receiving traces, initialize the tracer provider with the exporters
|
|
109
125
|
traces_enabled = signals.get("traceSignal", False)
|
|
110
126
|
|
|
111
127
|
if traces_enabled:
|
|
112
128
|
|
|
113
129
|
odigos_sampler = OdigosSampler()
|
|
130
|
+
_odigos_sampler = odigos_sampler
|
|
114
131
|
sampler = ParentBased(odigos_sampler)
|
|
115
132
|
|
|
116
133
|
# Apply initial sampler config if provided (from OpAMP first message)
|
|
@@ -237,10 +254,8 @@ def update_agent_config(conf: Config):
|
|
|
237
254
|
if 'traces' in conf.sample_config and 'headSampling' in conf.sample_config['traces']:
|
|
238
255
|
head_sampling_config = conf.sample_config['traces']['headSampling']
|
|
239
256
|
|
|
240
|
-
# Determine if we have valid sampling config (rules or fallback fraction)
|
|
241
257
|
if head_sampling_config and (
|
|
242
|
-
head_sampling_config.get('
|
|
243
|
-
head_sampling_config.get('fallbackFraction') is not None
|
|
258
|
+
head_sampling_config.get('noisyOperations') is not None
|
|
244
259
|
):
|
|
245
260
|
valid_config = head_sampling_config
|
|
246
261
|
else:
|
|
@@ -357,11 +372,19 @@ class ProxyResource(Resource):
|
|
|
357
372
|
|
|
358
373
|
def _after_in_child():
|
|
359
374
|
# This hook runs in every child process after fork().
|
|
360
|
-
# It does
|
|
361
|
-
# 1. Marks ProxyResource as "dirty"
|
|
362
|
-
# the process.* attributes are recomputed with the child’s PID.
|
|
375
|
+
# It does three things:
|
|
376
|
+
# 1. Marks ProxyResource as "dirty" so process.* attributes are recomputed.
|
|
363
377
|
# 2. Restarts the OpAMP client (threads/sockets do not survive fork).
|
|
378
|
+
# 3. Propagates the sampler to the new client so config updates reach it.
|
|
379
|
+
# Without this, the forked client's sampler is None and config updates
|
|
380
|
+
# from OpAMP heartbeats are silently dropped.
|
|
364
381
|
provider = get_tracer_provider()
|
|
365
382
|
if isinstance(getattr(provider, "resource", None), ProxyResource):
|
|
366
383
|
provider.resource._child_refreshed = False
|
|
367
|
-
start_opamp_client(OpampConnectionEvent())
|
|
384
|
+
client = start_opamp_client(OpampConnectionEvent())
|
|
385
|
+
|
|
386
|
+
if _odigos_sampler is not None:
|
|
387
|
+
client.sampler = _odigos_sampler
|
|
388
|
+
initial_config = client.get_initial_sampler_config()
|
|
389
|
+
if initial_config is not None:
|
|
390
|
+
_odigos_sampler.update_config(initial_config)
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
from opentelemetry.semconv.attributes import http_attributes as http_attributes_semconv
|
|
2
|
+
from opentelemetry.semconv.attributes import server_attributes as server_attributes_semconv
|
|
3
|
+
from opentelemetry.sdk.trace.sampling import Sampler, Decision, SamplingResult, _get_parent_trace_state
|
|
4
|
+
from threading import Lock
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# Setup the Sampler logger
|
|
9
|
+
sampler_logger = logging.getLogger('odigos')
|
|
10
|
+
|
|
11
|
+
class OdigosSampler(Sampler):
|
|
12
|
+
|
|
13
|
+
# For compatibility with 64 bit trace IDs, the sampler checks the 64
|
|
14
|
+
# low-order bits of the trace ID to decide whether to sample a given trace.
|
|
15
|
+
TRACE_ID_LIMIT = (1 << 64) - 1
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
self._lock = Lock()
|
|
19
|
+
self._config = None
|
|
20
|
+
|
|
21
|
+
def _trace_id_based_sampling(self, trace_id: int, percentage_to_sample: float) -> bool:
|
|
22
|
+
"""Mimic OpenTelemetry's trace ID-based sampling logic with 64-bit range."""
|
|
23
|
+
# Since percentage to sample is in percents and we need a fraction, divide by 100
|
|
24
|
+
fraction = percentage_to_sample / 100
|
|
25
|
+
|
|
26
|
+
# Calculate the bound based on the fraction using OpenTelemetry's approach
|
|
27
|
+
bound = round(fraction * (self.TRACE_ID_LIMIT + 1))
|
|
28
|
+
# Apply the TRACE_ID_LIMIT mask to ensure 64-bit range and compare it to the bound
|
|
29
|
+
return (trace_id & self.TRACE_ID_LIMIT) < bound
|
|
30
|
+
|
|
31
|
+
def should_sample(self, parent_context, trace_id, name, kind, attributes, links):
|
|
32
|
+
attributes = attributes or {}
|
|
33
|
+
with self._lock:
|
|
34
|
+
# sampler_logger.debug(f'Running Should_sample a span with the following attributes: {attributes}')
|
|
35
|
+
|
|
36
|
+
if self._config is None:
|
|
37
|
+
# sampler_logger.debug('No configuration is set, returning RECORD_AND_SAMPLE')
|
|
38
|
+
return SamplingResult(Decision.RECORD_AND_SAMPLE, attributes=attributes, trace_state=_get_parent_trace_state(parent_context))
|
|
39
|
+
|
|
40
|
+
noisy_operations = self._config.get('noisyOperations', [])
|
|
41
|
+
lowest_percentage = None
|
|
42
|
+
|
|
43
|
+
for noisy_operation in noisy_operations:
|
|
44
|
+
if noisy_operation.get("disabled"):
|
|
45
|
+
continue
|
|
46
|
+
|
|
47
|
+
percentage_to_sample = noisy_operation.get("percentageAtMost", 0.0)
|
|
48
|
+
|
|
49
|
+
http_operation_sample = noisy_operation.get("operation")
|
|
50
|
+
if not http_operation_sample:
|
|
51
|
+
continue
|
|
52
|
+
|
|
53
|
+
# sampler_logger.debug(f'Evaluating noisy operation: {noisy_operation}')
|
|
54
|
+
|
|
55
|
+
matched = False
|
|
56
|
+
|
|
57
|
+
if http_server_sample := http_operation_sample.get("httpServer"):
|
|
58
|
+
matched = self._match_http_server_sample_rule(http_server_sample, attributes) or matched
|
|
59
|
+
|
|
60
|
+
if http_client_sample := http_operation_sample.get("httpClient"):
|
|
61
|
+
matched = self._match_http_client_sample_rule(http_client_sample, attributes) or matched
|
|
62
|
+
|
|
63
|
+
# sampler_logger.debug(f'Noisy operation matched: {matched}, percentage: {percentage_to_sample}')
|
|
64
|
+
if matched: # For matched operations, compute the minimal lowest percentage
|
|
65
|
+
if lowest_percentage is None:
|
|
66
|
+
lowest_percentage = percentage_to_sample
|
|
67
|
+
else:
|
|
68
|
+
lowest_percentage = min(lowest_percentage, percentage_to_sample)
|
|
69
|
+
|
|
70
|
+
if lowest_percentage is not None:
|
|
71
|
+
if self._trace_id_based_sampling(trace_id, lowest_percentage):
|
|
72
|
+
# sampler_logger.debug(f'Trace [{trace_id}] is sampled with lowest percentage {lowest_percentage}')
|
|
73
|
+
return SamplingResult(Decision.RECORD_AND_SAMPLE, attributes=attributes, trace_state=_get_parent_trace_state(parent_context))
|
|
74
|
+
else:
|
|
75
|
+
# sampler_logger.debug(f'Trace [{trace_id}] is dropped with lowest percentage {lowest_percentage}')
|
|
76
|
+
return SamplingResult(Decision.DROP)
|
|
77
|
+
|
|
78
|
+
# sampler_logger.debug(f'No noisy operation matched, sampling the trace')
|
|
79
|
+
return SamplingResult(Decision.RECORD_AND_SAMPLE, attributes=attributes, trace_state=_get_parent_trace_state(parent_context))
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def _get_attribute(span_attributes: dict[str, str], *keys: str):
|
|
83
|
+
"""Return the first non-None value found for the given attribute keys."""
|
|
84
|
+
for key in keys:
|
|
85
|
+
value = span_attributes.get(key)
|
|
86
|
+
if value is not None:
|
|
87
|
+
return value
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
def _match_http_server_sample_rule(self, http_server_rule: dict[str, str], span_attributes: dict[str, str]) -> bool:
|
|
91
|
+
"""
|
|
92
|
+
Try to match all the http server's attributes to the definitions of the noisy action
|
|
93
|
+
"""
|
|
94
|
+
span_route = span_attributes.get(http_attributes_semconv.HTTP_ROUTE)
|
|
95
|
+
# Old semconv: "http.method" → new semconv: "http.request.method"
|
|
96
|
+
span_method = self._get_attribute(span_attributes, http_attributes_semconv.HTTP_REQUEST_METHOD, "http.method")
|
|
97
|
+
|
|
98
|
+
# Some instrumentations send target instead of route
|
|
99
|
+
target = self._get_attribute(span_attributes, "http.target", "url.path")
|
|
100
|
+
|
|
101
|
+
if http_server_rule.get("route") and http_server_rule["route"] != span_route:
|
|
102
|
+
# If we have route in the span attributes, evaluate it first (use template matching
|
|
103
|
+
# since param names may differ, e.g. /grml/{grml_id} vs /grml/{path_param})
|
|
104
|
+
if span_route:
|
|
105
|
+
return self._match_rule_route_to_span(http_server_rule["route"], span_route)
|
|
106
|
+
# If we have target, try to match it with the route in the sampling rule
|
|
107
|
+
if target:
|
|
108
|
+
return self._match_rule_route_to_span(http_server_rule["route"], target)
|
|
109
|
+
|
|
110
|
+
if http_server_rule.get("routePrefix") and not (span_route or "").startswith(http_server_rule["routePrefix"]):
|
|
111
|
+
return False
|
|
112
|
+
if http_server_rule.get("method") and http_server_rule["method"] != span_method:
|
|
113
|
+
return False
|
|
114
|
+
|
|
115
|
+
return True
|
|
116
|
+
|
|
117
|
+
def _match_http_client_sample_rule(self, http_client_rule: dict[str, str], span_attributes: dict[str, str]) -> bool:
|
|
118
|
+
"""
|
|
119
|
+
Try to match all the http client's attributes to the definitions of the noisy action
|
|
120
|
+
"""
|
|
121
|
+
# Old semconv: "net.peer.name" / "http.host" → new semconv: "server.address"
|
|
122
|
+
server_address = self._get_attribute(span_attributes, server_attributes_semconv.SERVER_ADDRESS, "net.peer.name", "http.host")
|
|
123
|
+
url_template = span_attributes.get("url.template")
|
|
124
|
+
# Old semconv: "http.method" → new semconv: "http.request.method"
|
|
125
|
+
method = self._get_attribute(span_attributes, http_attributes_semconv.HTTP_REQUEST_METHOD, "http.method")
|
|
126
|
+
|
|
127
|
+
if http_client_rule.get("serverAddress") and http_client_rule["serverAddress"] != server_address:
|
|
128
|
+
return False
|
|
129
|
+
if http_client_rule.get("templatedPath") and http_client_rule["templatedPath"] != url_template:
|
|
130
|
+
return False
|
|
131
|
+
if http_client_rule.get("templatedPathPrefix") and not (url_template or "").startswith(http_client_rule["templatedPathPrefix"]):
|
|
132
|
+
return False
|
|
133
|
+
if http_client_rule.get("method") and http_client_rule["method"] != method:
|
|
134
|
+
return False
|
|
135
|
+
|
|
136
|
+
return True
|
|
137
|
+
|
|
138
|
+
def _match_rule_route_to_span(self, rule_route: str, span_value: str) -> bool:
|
|
139
|
+
"""
|
|
140
|
+
Match a sampling rule route (e.g. /item/{id}) against a span value, which can be either
|
|
141
|
+
a templated route (e.g. /item/{item_id}) or a full URL target (e.g. /item/123?param=hello).
|
|
142
|
+
Path param segments ({...}) in the rule are treated as wildcards.
|
|
143
|
+
"""
|
|
144
|
+
span_value = span_value.split('?')[0]
|
|
145
|
+
rule_parts = [part for part in rule_route.split('/') if part]
|
|
146
|
+
span_parts = [part for part in span_value.split('/') if part]
|
|
147
|
+
|
|
148
|
+
if len(rule_parts) != len(span_parts):
|
|
149
|
+
return False
|
|
150
|
+
|
|
151
|
+
# Traverse both rule and span segments
|
|
152
|
+
for i in range(len(rule_parts)):
|
|
153
|
+
# If we hit a path param in the rule, it matches any span segment
|
|
154
|
+
if rule_parts[i].startswith('{'):
|
|
155
|
+
continue
|
|
156
|
+
if rule_parts[i] != span_parts[i]:
|
|
157
|
+
return False
|
|
158
|
+
|
|
159
|
+
return True
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def get_description(self):
|
|
163
|
+
return "OdigosSampler"
|
|
164
|
+
|
|
165
|
+
def update_config(self, new_config):
|
|
166
|
+
with self._lock:
|
|
167
|
+
# sampler_logger.debug(f'Updating the configuration with the new configuration: {new_config}')
|
|
168
|
+
self._config = new_config
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VERSION = "1.0.79"
|
{odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/opamp/http_client.py
RENAMED
|
@@ -452,8 +452,7 @@ class OpAMPHTTPClient:
|
|
|
452
452
|
|
|
453
453
|
# Check if we have valid sampling config (same validation as update_agent_config)
|
|
454
454
|
if head_sampling_config and (
|
|
455
|
-
head_sampling_config.get('
|
|
456
|
-
head_sampling_config.get('fallbackFraction') is not None
|
|
455
|
+
head_sampling_config.get('noisyOperations') is not None
|
|
457
456
|
):
|
|
458
457
|
return head_sampling_config
|
|
459
458
|
else:
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
from .sampling_operators import SamplingOperators
|
|
2
|
-
from opentelemetry.sdk.trace.sampling import Sampler, Decision, SamplingResult, _get_parent_trace_state
|
|
3
|
-
from threading import Lock
|
|
4
|
-
import logging
|
|
5
|
-
|
|
6
|
-
# Setup the Sampler logger
|
|
7
|
-
sampler_logger = logging.getLogger('odigos')
|
|
8
|
-
|
|
9
|
-
class OdigosSampler(Sampler):
|
|
10
|
-
|
|
11
|
-
# Define the sampling operator functions used in the configuration.
|
|
12
|
-
# These operations are specified in the Odigos InstrumentationConfig Kubernetes object.
|
|
13
|
-
_operations = {
|
|
14
|
-
SamplingOperators.EQUALS.value: lambda attr, value: attr == value,
|
|
15
|
-
SamplingOperators.NOT_EQUALS.value: lambda attr, value: attr != value,
|
|
16
|
-
SamplingOperators.END_WITH.value: lambda attr, value: attr.endswith(value),
|
|
17
|
-
SamplingOperators.START_WITH.value: lambda attr, value: attr.startswith(value)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
# For compatibility with 64 bit trace IDs, the sampler checks the 64
|
|
21
|
-
# low-order bits of the trace ID to decide whether to sample a given trace.
|
|
22
|
-
TRACE_ID_LIMIT = (1 << 64) - 1
|
|
23
|
-
|
|
24
|
-
def __init__(self):
|
|
25
|
-
self._lock = Lock()
|
|
26
|
-
self._config = None
|
|
27
|
-
|
|
28
|
-
def _trace_id_based_sampling(self, trace_id, fraction):
|
|
29
|
-
"""Mimic OpenTelemetry's trace ID-based sampling logic with 64-bit range."""
|
|
30
|
-
# Calculate the bound based on the fraction using OpenTelemetry's approach
|
|
31
|
-
bound = round(fraction * (self.TRACE_ID_LIMIT + 1))
|
|
32
|
-
# Apply the TRACE_ID_LIMIT mask to ensure 64-bit range and compare it to the bound
|
|
33
|
-
return (trace_id & self.TRACE_ID_LIMIT) < bound
|
|
34
|
-
|
|
35
|
-
def should_sample(self, parent_context, trace_id, name, kind, attributes, links):
|
|
36
|
-
with self._lock:
|
|
37
|
-
# sampler_logger.debug(f'Running Should_sample a span with the following attributes: {attributes}')
|
|
38
|
-
|
|
39
|
-
if self._config is None:
|
|
40
|
-
# sampler_logger.debug('No configuration is set, returning RECORD_AND_SAMPLE')
|
|
41
|
-
return SamplingResult(Decision.RECORD_AND_SAMPLE, attributes=attributes, trace_state=_get_parent_trace_state(parent_context))
|
|
42
|
-
|
|
43
|
-
rules = self._config.get('attributesAndSamplerRules', [])
|
|
44
|
-
global_fraction = self._config.get('fallbackFraction', 1) # default to 1 if not set which means always sample
|
|
45
|
-
|
|
46
|
-
for rule in rules: # The first attribute rule that evaluates to true is used to determine the sampling decision based on its fraction.
|
|
47
|
-
and_attributes_sampler_rules = rule.get('attributeConditions', [])
|
|
48
|
-
|
|
49
|
-
# sampler_logger.debug(f'"AND" rule operands are: {and_attributes_sampler_rules}')
|
|
50
|
-
|
|
51
|
-
and_rule_fraction = rule.get('fraction', 1) # default to 1 if not set which means always sample
|
|
52
|
-
and_rule_met = True
|
|
53
|
-
|
|
54
|
-
for and_rule in and_attributes_sampler_rules:
|
|
55
|
-
# If the "AND" rule is not met once, break the loop to avoid unnecessary checks
|
|
56
|
-
if not and_rule_met:
|
|
57
|
-
break
|
|
58
|
-
|
|
59
|
-
key = and_rule.get('key')
|
|
60
|
-
value = and_rule.get('val')
|
|
61
|
-
operator = and_rule.get('operator') # equals / notEquals / endWith / startWith
|
|
62
|
-
|
|
63
|
-
if key in attributes:
|
|
64
|
-
attr_value = attributes[key]
|
|
65
|
-
# Perform the corresponding operation
|
|
66
|
-
if operator in self._operations and self._operations[operator](attr_value, value):
|
|
67
|
-
pass # Condition matched
|
|
68
|
-
else:
|
|
69
|
-
# sampler_logger.debug(f'Operator {operator} is false, setting the "AND" rule flag to false')
|
|
70
|
-
and_rule_met = False
|
|
71
|
-
else:
|
|
72
|
-
# sampler_logger.debug(f'Attribute {key} is not present in the span attributes, setting the "AND" rule flag to false')
|
|
73
|
-
and_rule_met = False
|
|
74
|
-
|
|
75
|
-
if and_rule_met:
|
|
76
|
-
# Perform the sampling decision
|
|
77
|
-
if self._trace_id_based_sampling(trace_id, and_rule_fraction):
|
|
78
|
-
# sampler_logger.debug(f'Trace [{trace_id}] is sampled "And rules" are met with fraction {and_rule_fraction}')
|
|
79
|
-
return SamplingResult(Decision.RECORD_AND_SAMPLE, attributes=attributes, trace_state=_get_parent_trace_state(parent_context))
|
|
80
|
-
else:
|
|
81
|
-
# sampler_logger.debug(f'Trace [{trace_id}] is dropped "And rules" are met but fraction {and_rule_fraction} not met')
|
|
82
|
-
return SamplingResult(Decision.DROP)
|
|
83
|
-
|
|
84
|
-
# Fallback to the global fraction if no rule matches
|
|
85
|
-
# sampler_logger.debug(f'No rule matched, falling back to the global fraction {global_fraction}')
|
|
86
|
-
if self._trace_id_based_sampling(trace_id, global_fraction):
|
|
87
|
-
return SamplingResult(Decision.RECORD_AND_SAMPLE, attributes=attributes, trace_state=_get_parent_trace_state(parent_context))
|
|
88
|
-
else:
|
|
89
|
-
return SamplingResult(Decision.DROP)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def get_description(self):
|
|
93
|
-
return "OdigosSampler"
|
|
94
|
-
|
|
95
|
-
def update_config(self, new_config):
|
|
96
|
-
with self._lock:
|
|
97
|
-
# sampler_logger.debug(f'Updating the configuration with the new configuration: {new_config}')
|
|
98
|
-
self._config = new_config
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
VERSION = "1.0.77"
|
|
File without changes
|
|
File without changes
|
{odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/initializer/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/initializer/envs.py
RENAMED
|
File without changes
|
{odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/initializer/exit_hook.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/opamp/anyvalue_pb2.py
RENAMED
|
File without changes
|
|
File without changes
|
{odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/opamp/health_status.py
RENAMED
|
File without changes
|
{odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/opamp/opamp_pb2.py
RENAMED
|
File without changes
|
{odigos_opentelemetry_python-1.0.77 → odigos_opentelemetry_python-1.0.79}/opamp/opamp_registry.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|