odigos-opentelemetry-python 1.0.88__tar.gz → 1.0.90__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.88/odigos_opentelemetry_python.egg-info → odigos_opentelemetry_python-1.0.90}/PKG-INFO +3 -5
- odigos_opentelemetry_python-1.0.90/initializer/__init__.py +24 -0
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/initializer/components.py +21 -1
- odigos_opentelemetry_python-1.0.90/initializer/diagnose.py +59 -0
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/initializer/lib_handling.py +42 -6
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/initializer/odigos_sampler.py +21 -12
- odigos_opentelemetry_python-1.0.90/initializer/version.py +1 -0
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90/odigos_opentelemetry_python.egg-info}/PKG-INFO +3 -5
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/odigos_opentelemetry_python.egg-info/SOURCES.txt +1 -0
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/odigos_opentelemetry_python.egg-info/requires.txt +2 -4
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/opamp/http_client.py +9 -9
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/opamp/transport.py +18 -13
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/pyproject.toml +3 -5
- odigos_opentelemetry_python-1.0.88/initializer/__init__.py +0 -22
- odigos_opentelemetry_python-1.0.88/initializer/version.py +0 -1
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/LICENSE +0 -0
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/README.md +0 -0
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/initializer/distro/__init__.py +0 -0
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/initializer/distro/distro.py +0 -0
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/initializer/distro/instrumentation_registry.py +0 -0
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/initializer/envs.py +0 -0
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/initializer/exit_hook.py +0 -0
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/initializer/odigos_sampler_helpers.py +0 -0
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/initializer/post_instrument.py +0 -0
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/initializer/process_resource.py +0 -0
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/initializer/schemas/__init__.py +0 -0
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/initializer/schemas/sampling.py +0 -0
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/odigos_opentelemetry_python.egg-info/dependency_links.txt +0 -0
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/odigos_opentelemetry_python.egg-info/entry_points.txt +0 -0
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/odigos_opentelemetry_python.egg-info/top_level.txt +0 -0
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/opamp/__init__.py +0 -0
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/opamp/anyvalue_pb2.py +0 -0
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/opamp/config.py +0 -0
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/opamp/health_status.py +0 -0
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/opamp/opamp_pb2.py +0 -0
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/opamp/opamp_registry.py +0 -0
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/opamp/utils.py +0 -0
- {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/setup.cfg +0 -0
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: odigos-opentelemetry-python
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.90
|
|
4
4
|
Summary: Odigos Initializer for Python OpenTelemetry Components
|
|
5
5
|
Author-email: odigos-io <support@odigos.io>
|
|
6
6
|
Requires-Python: >=3.9
|
|
7
7
|
License-File: LICENSE
|
|
8
8
|
Requires-Dist: uuid7==0.1.0
|
|
9
|
-
Requires-Dist: urllib3
|
|
10
|
-
Requires-Dist: urllib3===2.2.3
|
|
11
|
-
Requires-Dist: odigos-requests==2.32.3.dev0
|
|
9
|
+
Requires-Dist: urllib3>=2.6.3
|
|
12
10
|
Requires-Dist: zipp==3.20.2
|
|
13
11
|
Requires-Dist: idna==3.10
|
|
14
12
|
Requires-Dist: asgiref==3.8.1
|
|
15
|
-
Requires-Dist: requests==2.32.3
|
|
16
13
|
Requires-Dist: certifi==2025.1.31
|
|
17
14
|
Requires-Dist: charset-normalizer==3.4.1
|
|
18
15
|
Requires-Dist: googleapis-common-protos==1.66.0
|
|
@@ -79,4 +76,5 @@ Requires-Dist: opentelemetry-instrumentation-vertexai==2.1b0
|
|
|
79
76
|
Requires-Dist: opentelemetry-instrumentation-weaviate==0.50.1
|
|
80
77
|
Provides-Extra: test
|
|
81
78
|
Requires-Dist: pytest>=8.0; extra == "test"
|
|
79
|
+
Requires-Dist: pyyaml>=6.0; extra == "test"
|
|
82
80
|
Dynamic: license-file
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from .diagnose import ODIGOS_LOGGER_NAME, OPENTELEMETRY_LOGGER_NAME, apply_log_levels_from_env
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# This is the main odigos-opentelemetry-python logger setup function.
|
|
7
|
+
# It is used by all modules within this package to ensure that all logs are captured by the same logger.
|
|
8
|
+
#
|
|
9
|
+
# Each controlled logger gets its own console handler and does not propagate to the root logger.
|
|
10
|
+
def setup_logging():
|
|
11
|
+
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
12
|
+
|
|
13
|
+
for logger_name in (ODIGOS_LOGGER_NAME, OPENTELEMETRY_LOGGER_NAME):
|
|
14
|
+
logger = logging.getLogger(logger_name)
|
|
15
|
+
handler = logging.StreamHandler()
|
|
16
|
+
handler.setFormatter(formatter)
|
|
17
|
+
logger.addHandler(handler)
|
|
18
|
+
logger.propagate = False # Prevent logs from reaching the root logger
|
|
19
|
+
|
|
20
|
+
# Apply the diagnostics log levels from environment variables for the startup window, before the first OpAMP remote config.
|
|
21
|
+
apply_log_levels_from_env()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
setup_logging()
|
{odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/initializer/components.py
RENAMED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
from typing import Union
|
|
2
|
-
from .lib_handling import
|
|
2
|
+
from .lib_handling import (
|
|
3
|
+
reorder_python_path,
|
|
4
|
+
reload_distro_modules,
|
|
5
|
+
handle_django_instrumentation,
|
|
6
|
+
handle_eventlet_instrumentation,
|
|
7
|
+
patch_otlp_span_flags,
|
|
8
|
+
)
|
|
3
9
|
|
|
4
10
|
handle_eventlet_instrumentation()
|
|
5
11
|
|
|
@@ -38,11 +44,17 @@ from opentelemetry.trace import set_tracer_provider, get_tracer_provider
|
|
|
38
44
|
# before reshuffling) and before opamp below (it imports protobuf—the app should own that).
|
|
39
45
|
reorder_python_path()
|
|
40
46
|
|
|
47
|
+
# Backport OTLP Span.flags trace-flags encoding
|
|
48
|
+
# (upstream PR #4761: https://github.com/open-telemetry/opentelemetry-python/pull/4761)
|
|
49
|
+
# onto the canonical exporter module now that sys.path prefers the agent's copy.
|
|
50
|
+
patch_otlp_span_flags()
|
|
51
|
+
|
|
41
52
|
from .version import VERSION
|
|
42
53
|
from .exit_hook import ExitHooks
|
|
43
54
|
from .envs import set_static_otel_env
|
|
44
55
|
|
|
45
56
|
from .odigos_sampler import OdigosSampler
|
|
57
|
+
from .diagnose import apply_log_levels_from_opamp
|
|
46
58
|
|
|
47
59
|
from opamp.config import Config
|
|
48
60
|
from opamp import opamp_registry
|
|
@@ -93,6 +105,10 @@ def initialize_components(trace_exporters=False, span_processor=None):
|
|
|
93
105
|
except AttributeError:
|
|
94
106
|
initial_remote_config = None
|
|
95
107
|
|
|
108
|
+
# Let the remote config take over the diagnostics log levels from the environment variables.
|
|
109
|
+
# On the OpAMP error path container_config is None, which silences the loggers.
|
|
110
|
+
apply_log_levels_from_opamp(container_config)
|
|
111
|
+
|
|
96
112
|
handle_django_instrumentation()
|
|
97
113
|
|
|
98
114
|
auto_resource = {
|
|
@@ -264,6 +280,10 @@ def update_agent_config(conf: Config):
|
|
|
264
280
|
:return: If the `provider` is `None` or if `active_proc` is `None`, then the function will return
|
|
265
281
|
without performing any further actions.
|
|
266
282
|
"""
|
|
283
|
+
# Update the diagnostics log levels on every remote config, even before the tracer provider exists.
|
|
284
|
+
if hasattr(conf, 'container_config'):
|
|
285
|
+
apply_log_levels_from_opamp(conf.container_config)
|
|
286
|
+
|
|
267
287
|
provider = get_tracer_provider()
|
|
268
288
|
if provider is None:
|
|
269
289
|
return
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
|
|
5
|
+
# log level of the odigos agent itself
|
|
6
|
+
ODIGOS_LOG_LEVEL: str = "ODIGOS_LOG_LEVEL"
|
|
7
|
+
ODIGOS_LOGGER_NAME: str = "odigos"
|
|
8
|
+
# log level of the OpenTelemetry components
|
|
9
|
+
OTEL_LOG_LEVEL: str = "OTEL_LOG_LEVEL"
|
|
10
|
+
OPENTELEMETRY_LOGGER_NAME: str = "opentelemetry"
|
|
11
|
+
|
|
12
|
+
# Env var vocabulary: the OpenTelemetry standard log level words (case-insensitive).
|
|
13
|
+
_ENV_LEVEL_MAP: Dict[str, Optional[int]] = {
|
|
14
|
+
"ALL": logging.NOTSET,
|
|
15
|
+
"VERBOSE": logging.DEBUG,
|
|
16
|
+
"DEBUG": logging.DEBUG,
|
|
17
|
+
"INFO": logging.INFO,
|
|
18
|
+
"WARN": logging.WARNING,
|
|
19
|
+
"ERROR": logging.ERROR,
|
|
20
|
+
"NONE": None,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
# Remote config vocabulary: log level set sent by the OpAMP server.
|
|
24
|
+
# The key is optional so a missing config level (None) resolves to None, which silences the logger.
|
|
25
|
+
_CONFIG_LEVEL_MAP: Dict[Optional[str], int] = {
|
|
26
|
+
"error": logging.ERROR,
|
|
27
|
+
"warn": logging.WARNING,
|
|
28
|
+
"info": logging.INFO,
|
|
29
|
+
"debug": logging.DEBUG,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _set_logger_level(logger_name: str, level: Optional[int]) -> None:
|
|
34
|
+
logger = logging.getLogger(logger_name)
|
|
35
|
+
if level is None:
|
|
36
|
+
# No level configured for this logger, so silence it.
|
|
37
|
+
logger.disabled = True
|
|
38
|
+
else:
|
|
39
|
+
logger.disabled = False
|
|
40
|
+
logger.setLevel(level)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def apply_log_levels_from_env() -> None:
|
|
44
|
+
"""
|
|
45
|
+
This governs the startup window before the first OpAMP remote config is received.
|
|
46
|
+
An unset or unrecognized value silences the logger.
|
|
47
|
+
"""
|
|
48
|
+
_set_logger_level(ODIGOS_LOGGER_NAME, _ENV_LEVEL_MAP.get((os.getenv(ODIGOS_LOG_LEVEL) or "").upper()))
|
|
49
|
+
_set_logger_level(OPENTELEMETRY_LOGGER_NAME, _ENV_LEVEL_MAP.get((os.getenv(OTEL_LOG_LEVEL) or "").upper()))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def apply_log_levels_from_opamp(container_config: Optional[Dict[str, Any]]) -> None:
|
|
53
|
+
"""
|
|
54
|
+
Apply the log levels from the OpAMP remote container config.
|
|
55
|
+
The remote config fully overrides the environment variables: a missing level silences the logger rather than falling back to the environment.
|
|
56
|
+
"""
|
|
57
|
+
agent_diagnostics: Dict[str, Any] = (container_config or {}).get("agentDiagnostics", {}) or {}
|
|
58
|
+
_set_logger_level(ODIGOS_LOGGER_NAME, _CONFIG_LEVEL_MAP.get(agent_diagnostics.get("odigosLogLevel")))
|
|
59
|
+
_set_logger_level(OPENTELEMETRY_LOGGER_NAME, _CONFIG_LEVEL_MAP.get(agent_diagnostics.get("openTelemetryComponentsLogLevel")))
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import sys
|
|
2
4
|
import importlib
|
|
3
5
|
import os
|
|
6
|
+
from typing import TYPE_CHECKING, Optional, Sequence
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from opentelemetry.proto.trace.v1.trace_pb2 import Span as PB2Span
|
|
10
|
+
from opentelemetry.sdk.trace import ReadableSpan
|
|
11
|
+
from opentelemetry.trace import Link
|
|
4
12
|
|
|
5
13
|
|
|
6
14
|
def reorder_python_path():
|
|
@@ -32,13 +40,7 @@ def reload_distro_modules() -> None:
|
|
|
32
40
|
'typing_extensions',
|
|
33
41
|
]
|
|
34
42
|
|
|
35
|
-
excluded_modules = ['urllib3_odigos', 'requests_odigos']
|
|
36
|
-
|
|
37
43
|
for module in list(sys.modules):
|
|
38
|
-
# Check if the module starts with any of the needed prefixes, but not with any of the excluded prefixes.
|
|
39
|
-
if any(module.startswith(prefix) for prefix in excluded_modules):
|
|
40
|
-
continue
|
|
41
|
-
|
|
42
44
|
if any(module.startswith(prefix) for prefix in needed_module_prefixes):
|
|
43
45
|
module_file = getattr(sys.modules[module], '__file__', None) # Safely get __file__
|
|
44
46
|
if module_file and ('/etc/odigos-vmagent/' in module_file or '/var/odigos/' in module_file):
|
|
@@ -71,6 +73,40 @@ def handle_django_instrumentation():
|
|
|
71
73
|
os.environ.setdefault("OTEL_PYTHON_DJANGO_INSTRUMENT", 'False')
|
|
72
74
|
|
|
73
75
|
|
|
76
|
+
def patch_otlp_span_flags() -> None:
|
|
77
|
+
# Backport of https://github.com/open-telemetry/opentelemetry-python/pull/4761:
|
|
78
|
+
# include W3C TraceFlags (lower 8 bits, e.g. the sampled bit) in the OTLP Span.flags / Link.flags alongside the existing is-remote bits (8-9).
|
|
79
|
+
# We wrap the encode helpers instead of replacing _span_flags because the PR also changed that function's signature and both of its call sites.
|
|
80
|
+
try:
|
|
81
|
+
from opentelemetry.exporter.otlp.proto.common._internal import trace_encoder
|
|
82
|
+
except ImportError:
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
original_encode_span = trace_encoder._encode_span
|
|
86
|
+
original_encode_links = trace_encoder._encode_links
|
|
87
|
+
|
|
88
|
+
# W3C TraceFlags is a single byte (values 0-255), and the encoder's existing
|
|
89
|
+
# flags only ever set the higher has-remote / is-remote bits.
|
|
90
|
+
def encode_span(sdk_span: ReadableSpan) -> PB2Span:
|
|
91
|
+
encoded_span = original_encode_span(sdk_span)
|
|
92
|
+
# THE FIX: fold the W3C trace flags into Span.flags (PR #4761).
|
|
93
|
+
encoded_span.flags |= int(sdk_span.get_span_context().trace_flags)
|
|
94
|
+
return encoded_span
|
|
95
|
+
|
|
96
|
+
def encode_links(links: Sequence[Link]) -> Optional[Sequence[PB2Span.Link]]:
|
|
97
|
+
encoded_links = original_encode_links(links)
|
|
98
|
+
if encoded_links:
|
|
99
|
+
# Propagate the trace flags to links as well
|
|
100
|
+
for link, encoded_link in zip(links, encoded_links):
|
|
101
|
+
# THE FIX: fold the W3C trace flags into Link.flags (PR #4761).
|
|
102
|
+
encoded_link.flags |= int(link.context.trace_flags)
|
|
103
|
+
return encoded_links
|
|
104
|
+
|
|
105
|
+
setattr(trace_encoder, "_encode_span", encode_span)
|
|
106
|
+
setattr(trace_encoder, "_encode_links", encode_links)
|
|
107
|
+
setattr(trace_encoder, "_odigos_trace_flags_patched", True)
|
|
108
|
+
|
|
109
|
+
|
|
74
110
|
def handle_eventlet_instrumentation():
|
|
75
111
|
"""Checks if eventlet is importable and applies eventlet.monkey_patch if available."""
|
|
76
112
|
# Apply monkey_patch early to enable Eventlet's green threads for non-blocking I/O.
|
|
@@ -71,10 +71,10 @@ class OdigosSampler(Sampler):
|
|
|
71
71
|
return SamplingResult(Decision.RECORD_AND_SAMPLE, attributes=attributes, trace_state=_get_parent_trace_state(parent_context))
|
|
72
72
|
else:
|
|
73
73
|
return SamplingResult(self._not_sampled_decision(), trace_state=_get_parent_trace_state(parent_context))
|
|
74
|
-
|
|
74
|
+
sampler_logger.debug(f'Running Should_sample a span with the following attributes: {attributes}')
|
|
75
75
|
|
|
76
76
|
if self._config is None:
|
|
77
|
-
|
|
77
|
+
sampler_logger.debug('No configuration is set, returning RECORD_AND_SAMPLE')
|
|
78
78
|
return SamplingResult(Decision.RECORD_AND_SAMPLE, attributes=attributes, trace_state=_get_parent_trace_state(parent_context))
|
|
79
79
|
|
|
80
80
|
is_dry_run: bool = self._config.get('dryRun', False)
|
|
@@ -90,7 +90,7 @@ class OdigosSampler(Sampler):
|
|
|
90
90
|
|
|
91
91
|
http_operation_sample: Optional[HeadSamplingOperationMatcher] = noisy_operation.get("operation")
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
sampler_logger.debug(f'Evaluating noisy operation: {noisy_operation}')
|
|
94
94
|
|
|
95
95
|
# No operation matcher -> rule applies workload-wide (matches every span)
|
|
96
96
|
# This supports Sampling rules that scope by sourceScopes (workload) without specific HTTP matcher
|
|
@@ -98,13 +98,13 @@ class OdigosSampler(Sampler):
|
|
|
98
98
|
matched = True
|
|
99
99
|
else: # When we have a granular operation, check it vs the server/client rules specified in the noisy operation
|
|
100
100
|
matched = False
|
|
101
|
-
if http_server_sample := http_operation_sample.get("httpServer"):
|
|
101
|
+
if (http_server_sample := http_operation_sample.get("httpServer")) and kind == SpanKind.SERVER:
|
|
102
102
|
matched = self._match_http_server_sample_rule(http_server_sample, attributes) or matched
|
|
103
103
|
|
|
104
|
-
if http_client_sample := http_operation_sample.get("httpClient"):
|
|
104
|
+
if (http_client_sample := http_operation_sample.get("httpClient")) and kind == SpanKind.CLIENT:
|
|
105
105
|
matched = self._match_http_client_sample_rule(http_client_sample, attributes) or matched
|
|
106
106
|
|
|
107
|
-
|
|
107
|
+
sampler_logger.debug(f'Noisy operation matched: {matched}, percentage: {percentage_to_sample}')
|
|
108
108
|
if matched and (winning_operation is None or percentage_to_sample < winning_operation.get("percentageAtMost", 0.0)):
|
|
109
109
|
winning_operation = noisy_operation
|
|
110
110
|
|
|
@@ -120,17 +120,17 @@ class OdigosSampler(Sampler):
|
|
|
120
120
|
|
|
121
121
|
# In dry-run mode, never actually drop spans
|
|
122
122
|
if is_dry_run:
|
|
123
|
-
|
|
123
|
+
sampler_logger.debug(f'Dry run [{trace_id}] would_be_sampled={sampled} (lowest_percentage={lowest_percentage})')
|
|
124
124
|
return SamplingResult(Decision.RECORD_AND_SAMPLE, attributes=attributes, trace_state=trace_state)
|
|
125
125
|
|
|
126
126
|
if sampled:
|
|
127
|
-
|
|
127
|
+
sampler_logger.debug(f'Trace [{trace_id}] is sampled with lowest percentage {lowest_percentage}')
|
|
128
128
|
return SamplingResult(Decision.RECORD_AND_SAMPLE, attributes=attributes, trace_state=trace_state)
|
|
129
129
|
else:
|
|
130
|
-
|
|
130
|
+
sampler_logger.debug(f'Trace [{trace_id}] is dropped with lowest percentage {lowest_percentage}')
|
|
131
131
|
return SamplingResult(self._not_sampled_decision(), trace_state=trace_state)
|
|
132
132
|
|
|
133
|
-
|
|
133
|
+
sampler_logger.debug('No noisy operation matched, sampling the trace')
|
|
134
134
|
return SamplingResult(Decision.RECORD_AND_SAMPLE, attributes=attributes, trace_state=_get_parent_trace_state(parent_context))
|
|
135
135
|
|
|
136
136
|
def _match_http_server_sample_rule(
|
|
@@ -156,8 +156,10 @@ class OdigosSampler(Sampler):
|
|
|
156
156
|
if target:
|
|
157
157
|
return self._match_rule_route_to_span(http_server_rule["route"], target)
|
|
158
158
|
|
|
159
|
-
if http_server_rule.get("routePrefix")
|
|
160
|
-
|
|
159
|
+
if route_prefix := http_server_rule.get("routePrefix"):
|
|
160
|
+
candidate = span_route or target
|
|
161
|
+
if not candidate or not self._match_rule_route_to_span(route_prefix, candidate, route_has_prefix=True):
|
|
162
|
+
return False
|
|
161
163
|
if http_server_rule.get("method") and http_server_rule["method"] != span_method:
|
|
162
164
|
return False
|
|
163
165
|
|
|
@@ -175,6 +177,13 @@ class OdigosSampler(Sampler):
|
|
|
175
177
|
server_address = get_attribute(span_attributes, server_attributes_semconv.SERVER_ADDRESS, "net.peer.name", "http.host")
|
|
176
178
|
if server_address:
|
|
177
179
|
server_address = strip_port(server_address)
|
|
180
|
+
else:
|
|
181
|
+
# some instrumentations (like urllib) never puts a host attribute on the span. it sets only method + url,
|
|
182
|
+
# so if we want the host we need to extract it from the full url
|
|
183
|
+
# default (old) semconv mode the full URL is http.url; in stable mode url.full.
|
|
184
|
+
full_url = get_attribute(span_attributes, "url.full", "http.url")
|
|
185
|
+
if full_url:
|
|
186
|
+
server_address = urlsplit(full_url).hostname
|
|
178
187
|
|
|
179
188
|
# Old semconv for url is a target that we need to get the client_path from → new semconv: "url.template"/"url.path"
|
|
180
189
|
client_path = get_attribute(span_attributes, "url.template", url_attributes_semconv.URL_PATH)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VERSION = "1.0.90"
|
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: odigos-opentelemetry-python
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.90
|
|
4
4
|
Summary: Odigos Initializer for Python OpenTelemetry Components
|
|
5
5
|
Author-email: odigos-io <support@odigos.io>
|
|
6
6
|
Requires-Python: >=3.9
|
|
7
7
|
License-File: LICENSE
|
|
8
8
|
Requires-Dist: uuid7==0.1.0
|
|
9
|
-
Requires-Dist: urllib3
|
|
10
|
-
Requires-Dist: urllib3===2.2.3
|
|
11
|
-
Requires-Dist: odigos-requests==2.32.3.dev0
|
|
9
|
+
Requires-Dist: urllib3>=2.6.3
|
|
12
10
|
Requires-Dist: zipp==3.20.2
|
|
13
11
|
Requires-Dist: idna==3.10
|
|
14
12
|
Requires-Dist: asgiref==3.8.1
|
|
15
|
-
Requires-Dist: requests==2.32.3
|
|
16
13
|
Requires-Dist: certifi==2025.1.31
|
|
17
14
|
Requires-Dist: charset-normalizer==3.4.1
|
|
18
15
|
Requires-Dist: googleapis-common-protos==1.66.0
|
|
@@ -79,4 +76,5 @@ Requires-Dist: opentelemetry-instrumentation-vertexai==2.1b0
|
|
|
79
76
|
Requires-Dist: opentelemetry-instrumentation-weaviate==0.50.1
|
|
80
77
|
Provides-Extra: test
|
|
81
78
|
Requires-Dist: pytest>=8.0; extra == "test"
|
|
79
|
+
Requires-Dist: pyyaml>=6.0; extra == "test"
|
|
82
80
|
Dynamic: license-file
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
uuid7==0.1.0
|
|
2
|
-
urllib3
|
|
3
|
-
urllib3===2.2.3
|
|
4
|
-
odigos-requests==2.32.3.dev0
|
|
2
|
+
urllib3>=2.6.3
|
|
5
3
|
zipp==3.20.2
|
|
6
4
|
idna==3.10
|
|
7
5
|
asgiref==3.8.1
|
|
8
|
-
requests==2.32.3
|
|
9
6
|
certifi==2025.1.31
|
|
10
7
|
charset-normalizer==3.4.1
|
|
11
8
|
googleapis-common-protos==1.66.0
|
|
@@ -73,3 +70,4 @@ opentelemetry-instrumentation-weaviate==0.50.1
|
|
|
73
70
|
|
|
74
71
|
[test]
|
|
75
72
|
pytest>=8.0
|
|
73
|
+
pyyaml>=6.0
|
{odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/opamp/http_client.py
RENAMED
|
@@ -5,7 +5,7 @@ import json
|
|
|
5
5
|
import time
|
|
6
6
|
import base64
|
|
7
7
|
import threading
|
|
8
|
-
import
|
|
8
|
+
import http.client
|
|
9
9
|
import logging
|
|
10
10
|
|
|
11
11
|
from uuid_extensions import uuid7
|
|
@@ -69,7 +69,7 @@ class OpAMPHTTPClient:
|
|
|
69
69
|
python_version = f'{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}'
|
|
70
70
|
error_message = f"Opentelemetry SDK require Python in version 3.8 or higher [{python_version} is not supported]"
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
opamp_logger.warning(f"{error_message}, sending disconnect message to OpAMP server...")
|
|
73
73
|
self.send_unsupported_version_disconnect_message(error_message=error_message)
|
|
74
74
|
self.opamp_connection_event.event.set()
|
|
75
75
|
return
|
|
@@ -94,7 +94,7 @@ class OpAMPHTTPClient:
|
|
|
94
94
|
self.worker()
|
|
95
95
|
|
|
96
96
|
except Exception as e:
|
|
97
|
-
|
|
97
|
+
opamp_logger.error(f"Error running OpAMP client: {e}")
|
|
98
98
|
failure_message = self.get_agent_failure_disconnect_message(error_message=str(e))
|
|
99
99
|
self.send_agent_to_server_message(failure_message)
|
|
100
100
|
|
|
@@ -234,7 +234,7 @@ class OpAMPHTTPClient:
|
|
|
234
234
|
pass
|
|
235
235
|
|
|
236
236
|
if server_to_agent.flags & opamp_pb2.ServerToAgentFlags_ReportFullState:
|
|
237
|
-
|
|
237
|
+
opamp_logger.info("Received request to report full state")
|
|
238
238
|
|
|
239
239
|
agent_description = self.get_agent_description()
|
|
240
240
|
agent_health = self.get_agent_health(component_health=True, status=AgentHealthStatus.HEALTHY.value)
|
|
@@ -251,8 +251,8 @@ class OpAMPHTTPClient:
|
|
|
251
251
|
# Catch exception and don't update the config
|
|
252
252
|
# The default config is preloaded when the EBPFSpanProcessor is initialized
|
|
253
253
|
pass
|
|
254
|
-
|
|
255
|
-
except
|
|
254
|
+
# OSError contain all socket or network failures, and HTTPException is for HTTP protocol errors
|
|
255
|
+
except (OSError, http.client.HTTPException):
|
|
256
256
|
pass
|
|
257
257
|
self.condition.wait(30)
|
|
258
258
|
|
|
@@ -330,11 +330,11 @@ class OpAMPHTTPClient:
|
|
|
330
330
|
return config
|
|
331
331
|
|
|
332
332
|
def send_heartbeat(self) -> opamp_pb2.ServerToAgent: # type: ignore
|
|
333
|
-
|
|
333
|
+
opamp_logger.debug("Sending heartbeat to OpAMP server...")
|
|
334
334
|
try:
|
|
335
335
|
agent_to_server = opamp_pb2.AgentToServer(remote_config_status=self.remote_config_status)
|
|
336
336
|
return self.send_agent_to_server_message(agent_to_server)
|
|
337
|
-
except
|
|
337
|
+
except (OSError, http.client.HTTPException):
|
|
338
338
|
pass
|
|
339
339
|
|
|
340
340
|
def get_agent_description(self) -> opamp_pb2.AgentDescription: # type: ignore
|
|
@@ -419,7 +419,7 @@ class OpAMPHTTPClient:
|
|
|
419
419
|
|
|
420
420
|
def shutdown(self, custom_failure_message: Optional[str] = None, component_health: bool = False):
|
|
421
421
|
self.running = False
|
|
422
|
-
|
|
422
|
+
opamp_logger.info("Sending agent disconnect message to OpAMP server...")
|
|
423
423
|
if custom_failure_message:
|
|
424
424
|
disconnect_message = self.get_agent_failure_disconnect_message(
|
|
425
425
|
error_message=custom_failure_message,
|
{odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/opamp/transport.py
RENAMED
|
@@ -2,19 +2,31 @@ import os
|
|
|
2
2
|
import socket
|
|
3
3
|
import http.client
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
_OPAMP_PATH = "/v1/opamp"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _post(conn: http.client.HTTPConnection, body: bytes, headers: dict) -> bytes:
|
|
9
|
+
try:
|
|
10
|
+
conn.request("POST", _OPAMP_PATH, body=body, headers=headers)
|
|
11
|
+
response = conn.getresponse()
|
|
12
|
+
status = response.status
|
|
13
|
+
data = response.read()
|
|
14
|
+
if status >= 400:
|
|
15
|
+
raise http.client.HTTPException(f"opamp POST returned status {status}")
|
|
16
|
+
return data
|
|
17
|
+
finally:
|
|
18
|
+
conn.close()
|
|
6
19
|
|
|
7
20
|
|
|
8
21
|
class TCPTransport:
|
|
9
22
|
"""HTTP/1.1 over TCP. Talks to ODIGOS_OPAMP_SERVER_HOST."""
|
|
10
23
|
|
|
11
24
|
def __init__(self, host: str):
|
|
12
|
-
self.
|
|
25
|
+
self._host = host
|
|
13
26
|
|
|
14
27
|
def post(self, body: bytes, headers: dict, timeout: float) -> bytes:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return response.content
|
|
28
|
+
conn = http.client.HTTPConnection(self._host, timeout=timeout)
|
|
29
|
+
return _post(conn, body, headers)
|
|
18
30
|
|
|
19
31
|
|
|
20
32
|
class UnixTransport:
|
|
@@ -25,14 +37,7 @@ class UnixTransport:
|
|
|
25
37
|
|
|
26
38
|
def post(self, body: bytes, headers: dict, timeout: float) -> bytes:
|
|
27
39
|
conn = _UnixHTTPConnection(self._socket_path, timeout=timeout)
|
|
28
|
-
|
|
29
|
-
conn.request("POST", "/v1/opamp", body=body, headers=headers)
|
|
30
|
-
response = conn.getresponse()
|
|
31
|
-
if response.status >= 400:
|
|
32
|
-
raise RuntimeError(f"opamp unix POST returned status {response.status}")
|
|
33
|
-
return response.read()
|
|
34
|
-
finally:
|
|
35
|
-
conn.close()
|
|
40
|
+
return _post(conn, body, headers)
|
|
36
41
|
|
|
37
42
|
|
|
38
43
|
class _UnixHTTPConnection(http.client.HTTPConnection):
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "odigos-opentelemetry-python"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.90"
|
|
8
8
|
description = "Odigos Initializer for Python OpenTelemetry Components"
|
|
9
9
|
requires-python = ">=3.9"
|
|
10
10
|
authors = [
|
|
@@ -12,13 +12,10 @@ authors = [
|
|
|
12
12
|
]
|
|
13
13
|
dependencies = [
|
|
14
14
|
"uuid7 == 0.1.0",
|
|
15
|
-
"urllib3
|
|
16
|
-
"urllib3 === 2.2.3",
|
|
17
|
-
"odigos-requests == 2.32.3.dev0",
|
|
15
|
+
"urllib3 >= 2.6.3", # security floor: urllib3 redirect/retry CVEs fixed in 2.5.0
|
|
18
16
|
"zipp == 3.20.2",
|
|
19
17
|
"idna == 3.10",
|
|
20
18
|
"asgiref == 3.8.1",
|
|
21
|
-
"requests == 2.32.3",
|
|
22
19
|
"certifi == 2025.1.31",
|
|
23
20
|
"charset-normalizer == 3.4.1",
|
|
24
21
|
"googleapis-common-protos == 1.66.0",
|
|
@@ -90,6 +87,7 @@ dependencies = [
|
|
|
90
87
|
[project.optional-dependencies]
|
|
91
88
|
test = [
|
|
92
89
|
"pytest >= 8.0",
|
|
90
|
+
"pyyaml >= 6.0",
|
|
93
91
|
]
|
|
94
92
|
|
|
95
93
|
[dependency-groups]
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
# This is the main odigos-opentelemetry-python logger setup function.
|
|
5
|
-
# It is used by all modules within this package to ensure that all logs are captured by the same logger.
|
|
6
|
-
def setup_logging():
|
|
7
|
-
odigos_logger = logging.getLogger('odigos')
|
|
8
|
-
odigos_logger.setLevel(logging.DEBUG)
|
|
9
|
-
handler = logging.StreamHandler()
|
|
10
|
-
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
11
|
-
handler.setFormatter(formatter)
|
|
12
|
-
odigos_logger.addHandler(handler)
|
|
13
|
-
|
|
14
|
-
odigos_logger.propagate = False # Prevent logs from reaching the root logger
|
|
15
|
-
odigos_logger.disabled = True # Comment to enable the logger
|
|
16
|
-
|
|
17
|
-
# Suppress third-party loggers used by odigos-opentelemetry-python (before they are used)
|
|
18
|
-
logging.getLogger('urllib3_odigos').setLevel(logging.CRITICAL)
|
|
19
|
-
logging.getLogger('requests_odigos').setLevel(logging.CRITICAL)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
setup_logging()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
VERSION = "1.0.88"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/initializer/envs.py
RENAMED
|
File without changes
|
{odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/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
|
{odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/opamp/anyvalue_pb2.py
RENAMED
|
File without changes
|
|
File without changes
|
{odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/opamp/health_status.py
RENAMED
|
File without changes
|
{odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/opamp/opamp_pb2.py
RENAMED
|
File without changes
|
{odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/opamp/opamp_registry.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|