odigos-opentelemetry-python 1.0.83__tar.gz → 1.0.84__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.83 → odigos_opentelemetry_python-1.0.84}/PKG-INFO +1 -1
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/initializer/odigos_sampler.py +49 -31
- odigos_opentelemetry_python-1.0.84/initializer/odigos_sampler_helpers.py +22 -0
- odigos_opentelemetry_python-1.0.84/initializer/version.py +1 -0
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/odigos_opentelemetry_python.egg-info/PKG-INFO +1 -1
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/odigos_opentelemetry_python.egg-info/SOURCES.txt +1 -0
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/pyproject.toml +1 -1
- odigos_opentelemetry_python-1.0.83/initializer/version.py +0 -1
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/LICENSE +0 -0
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/README.md +0 -0
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/initializer/__init__.py +0 -0
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/initializer/components.py +0 -0
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/initializer/distro/__init__.py +0 -0
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/initializer/distro/distro.py +0 -0
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/initializer/distro/instrumentation_registry.py +0 -0
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/initializer/envs.py +0 -0
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/initializer/exit_hook.py +0 -0
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/initializer/lib_handling.py +0 -0
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/initializer/post_instrument.py +0 -0
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/initializer/process_resource.py +0 -0
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/initializer/schemas/__init__.py +0 -0
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/initializer/schemas/sampling.py +0 -0
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/odigos_opentelemetry_python.egg-info/dependency_links.txt +0 -0
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/odigos_opentelemetry_python.egg-info/entry_points.txt +0 -0
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/odigos_opentelemetry_python.egg-info/requires.txt +0 -0
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/odigos_opentelemetry_python.egg-info/top_level.txt +0 -0
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/opamp/__init__.py +0 -0
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/opamp/anyvalue_pb2.py +0 -0
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/opamp/config.py +0 -0
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/opamp/health_status.py +0 -0
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/opamp/http_client.py +0 -0
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/opamp/opamp_pb2.py +0 -0
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/opamp/opamp_registry.py +0 -0
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/opamp/utils.py +0 -0
- {odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/setup.cfg +0 -0
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from threading import Lock
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Mapping, Optional, Sequence
|
|
4
|
+
from urllib.parse import urlsplit
|
|
4
5
|
|
|
5
6
|
from opentelemetry.context import Context
|
|
6
7
|
from opentelemetry.sdk.trace.sampling import Decision, Sampler, SamplingResult, _get_parent_trace_state
|
|
7
8
|
from opentelemetry.semconv.attributes import http_attributes as http_attributes_semconv
|
|
8
9
|
from opentelemetry.semconv.attributes import server_attributes as server_attributes_semconv
|
|
10
|
+
from opentelemetry.semconv.attributes import url_attributes as url_attributes_semconv
|
|
9
11
|
from opentelemetry.trace import Link, SpanKind, TraceState, get_current_span
|
|
10
|
-
from opentelemetry.util.types import Attributes
|
|
12
|
+
from opentelemetry.util.types import Attributes, AttributeValue
|
|
11
13
|
|
|
14
|
+
from initializer.odigos_sampler_helpers import get_attribute, strip_port
|
|
12
15
|
from initializer.schemas.sampling import (
|
|
13
16
|
HeadSamplingConfig,
|
|
14
17
|
HeadSamplingHttpClientOperationMatcher,
|
|
@@ -130,25 +133,19 @@ class OdigosSampler(Sampler):
|
|
|
130
133
|
# sampler_logger.debug('No noisy operation matched, sampling the trace')
|
|
131
134
|
return SamplingResult(Decision.RECORD_AND_SAMPLE, attributes=attributes, trace_state=_get_parent_trace_state(parent_context))
|
|
132
135
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
for key in keys:
|
|
137
|
-
value = span_attributes.get(key)
|
|
138
|
-
if value is not None:
|
|
139
|
-
return value
|
|
140
|
-
return None
|
|
141
|
-
|
|
142
|
-
def _match_http_server_sample_rule(self, http_server_rule: HeadSamplingHttpServerOperationMatcher, span_attributes: Mapping[str, Any]) -> bool:
|
|
136
|
+
def _match_http_server_sample_rule(
|
|
137
|
+
self, http_server_rule: HeadSamplingHttpServerOperationMatcher, span_attributes: Mapping[str, AttributeValue]
|
|
138
|
+
) -> bool:
|
|
143
139
|
"""
|
|
144
140
|
Try to match all the http server's attributes to the definitions of the noisy action
|
|
145
141
|
"""
|
|
146
|
-
|
|
142
|
+
raw_route = span_attributes.get(http_attributes_semconv.HTTP_ROUTE)
|
|
143
|
+
span_route: Optional[str] = raw_route if isinstance(raw_route, str) else None
|
|
147
144
|
# Old semconv: "http.method" → new semconv: "http.request.method"
|
|
148
|
-
span_method =
|
|
145
|
+
span_method = get_attribute(span_attributes, http_attributes_semconv.HTTP_REQUEST_METHOD, "http.method")
|
|
149
146
|
|
|
150
147
|
# Some instrumentations send target instead of route
|
|
151
|
-
target =
|
|
148
|
+
target = get_attribute(span_attributes, "http.target", "url.path")
|
|
152
149
|
|
|
153
150
|
if http_server_rule.get("route") and http_server_rule["route"] != span_route:
|
|
154
151
|
# If we have route in the span attributes, evaluate it first (use template matching
|
|
@@ -166,46 +163,67 @@ class OdigosSampler(Sampler):
|
|
|
166
163
|
|
|
167
164
|
return True
|
|
168
165
|
|
|
169
|
-
def _match_http_client_sample_rule(
|
|
166
|
+
def _match_http_client_sample_rule(
|
|
167
|
+
self,
|
|
168
|
+
http_client_rule: HeadSamplingHttpClientOperationMatcher,
|
|
169
|
+
span_attributes: Mapping[str, AttributeValue],
|
|
170
|
+
) -> bool:
|
|
170
171
|
"""
|
|
171
172
|
Try to match all the http client's attributes to the definitions of the noisy action
|
|
172
173
|
"""
|
|
173
|
-
# Old semconv: "net.peer.name"
|
|
174
|
-
server_address =
|
|
175
|
-
|
|
174
|
+
# Old semconv: "http.host"/"net.peer.name" → new semconv: "server.address"
|
|
175
|
+
server_address = get_attribute(span_attributes, server_attributes_semconv.SERVER_ADDRESS, "net.peer.name", "http.host")
|
|
176
|
+
if server_address:
|
|
177
|
+
server_address = strip_port(server_address)
|
|
178
|
+
|
|
179
|
+
# Old semconv for url is a target that we need to get the client_path from → new semconv: "url.template"/"url.path"
|
|
180
|
+
client_path = get_attribute(span_attributes, "url.template", url_attributes_semconv.URL_PATH)
|
|
181
|
+
if not client_path:
|
|
182
|
+
# Old semconv: "http.target" is a path (may carry a query); "http.url"/"url.full" is a full URL we parse the path out of.
|
|
183
|
+
target = get_attribute(span_attributes, "http.target", "url.full", "http.url")
|
|
184
|
+
client_path = urlsplit(target).path if target else None
|
|
185
|
+
|
|
176
186
|
# Old semconv: "http.method" → new semconv: "http.request.method"
|
|
177
|
-
method =
|
|
187
|
+
method = get_attribute(span_attributes, http_attributes_semconv.HTTP_REQUEST_METHOD, "http.method")
|
|
178
188
|
|
|
179
189
|
if http_client_rule.get("serverAddress") and http_client_rule["serverAddress"] != server_address:
|
|
180
190
|
return False
|
|
181
|
-
|
|
191
|
+
templated_path = http_client_rule.get("templatedPath")
|
|
192
|
+
if templated_path and not self._match_rule_route_to_span(templated_path, client_path or ""):
|
|
182
193
|
return False
|
|
183
|
-
|
|
194
|
+
templated_path_prefix = http_client_rule.get("templatedPathPrefix")
|
|
195
|
+
if templated_path_prefix and not self._match_rule_route_to_span(templated_path_prefix, client_path or "", route_has_prefix=True):
|
|
184
196
|
return False
|
|
185
197
|
if http_client_rule.get("method") and http_client_rule["method"] != method:
|
|
186
198
|
return False
|
|
187
199
|
|
|
188
200
|
return True
|
|
189
201
|
|
|
190
|
-
def _match_rule_route_to_span(self, rule_route: str, span_value: str) -> bool:
|
|
202
|
+
def _match_rule_route_to_span(self, rule_route: str, span_value: str, route_has_prefix: bool = False) -> bool:
|
|
191
203
|
"""
|
|
192
204
|
Match a sampling rule route (e.g. /item/{id}) against a span value, which can be either
|
|
193
205
|
a templated route (e.g. /item/{item_id}) or a full URL target (e.g. /item/123?param=hello).
|
|
194
|
-
|
|
206
|
+
Wildcard segments ({...}, :name, *) in the rule match any single span segment.
|
|
207
|
+
With prefix=True the span may have extra trailing segments (prefix match).
|
|
195
208
|
"""
|
|
196
209
|
span_value = span_value.split('?')[0]
|
|
197
210
|
rule_parts = [part for part in rule_route.split('/') if part]
|
|
198
211
|
span_parts = [part for part in span_value.split('/') if part]
|
|
199
212
|
|
|
200
|
-
if
|
|
201
|
-
|
|
213
|
+
if route_has_prefix:
|
|
214
|
+
# Prefix match: span may have extra trailing segments, but not fewer.
|
|
215
|
+
if len(span_parts) < len(rule_parts):
|
|
216
|
+
return False
|
|
217
|
+
else:
|
|
218
|
+
# Exact match: segment counts must be identical.
|
|
219
|
+
if len(span_parts) != len(rule_parts):
|
|
220
|
+
return False
|
|
202
221
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if rule_parts[i].startswith('{'):
|
|
222
|
+
for rule_segment, span_segment in zip(rule_parts, span_parts):
|
|
223
|
+
# Wildcard segment ({text}, :name, *) matches any single span segment
|
|
224
|
+
if rule_segment == '*' or (rule_segment.startswith('{') and rule_segment.endswith('}')) or rule_segment.startswith(':'):
|
|
207
225
|
continue
|
|
208
|
-
if
|
|
226
|
+
if rule_segment != span_segment:
|
|
209
227
|
return False
|
|
210
228
|
|
|
211
229
|
return True
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from typing import Mapping, Optional
|
|
2
|
+
from urllib.parse import urlsplit
|
|
3
|
+
|
|
4
|
+
from opentelemetry.util.types import AttributeValue
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def strip_port(host: str) -> str:
|
|
8
|
+
parsed = urlsplit(f'//{host}')
|
|
9
|
+
return parsed.hostname or host
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_attribute(span_attributes: Mapping[str, AttributeValue], *keys: str) -> Optional[str]:
|
|
13
|
+
"""Return the first string value found for the given attribute keys.
|
|
14
|
+
|
|
15
|
+
Span attribute values can be str/bool/int/float or sequences of those; the HTTP
|
|
16
|
+
semconv keys we look up are always strings, so non-str values are ignored.
|
|
17
|
+
"""
|
|
18
|
+
for key in keys:
|
|
19
|
+
value = span_attributes.get(key)
|
|
20
|
+
if isinstance(value, str):
|
|
21
|
+
return value
|
|
22
|
+
return None
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VERSION = "1.0.84"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
VERSION = "1.0.83"
|
|
File without changes
|
|
File without changes
|
{odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/initializer/__init__.py
RENAMED
|
File without changes
|
{odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/initializer/components.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/initializer/envs.py
RENAMED
|
File without changes
|
{odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/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
|
|
File without changes
|
|
File without changes
|
{odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/opamp/anyvalue_pb2.py
RENAMED
|
File without changes
|
|
File without changes
|
{odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/opamp/health_status.py
RENAMED
|
File without changes
|
{odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/opamp/http_client.py
RENAMED
|
File without changes
|
{odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/opamp/opamp_pb2.py
RENAMED
|
File without changes
|
{odigos_opentelemetry_python-1.0.83 → odigos_opentelemetry_python-1.0.84}/opamp/opamp_registry.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|