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.
Files changed (38) hide show
  1. {odigos_opentelemetry_python-1.0.88/odigos_opentelemetry_python.egg-info → odigos_opentelemetry_python-1.0.90}/PKG-INFO +3 -5
  2. odigos_opentelemetry_python-1.0.90/initializer/__init__.py +24 -0
  3. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/initializer/components.py +21 -1
  4. odigos_opentelemetry_python-1.0.90/initializer/diagnose.py +59 -0
  5. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/initializer/lib_handling.py +42 -6
  6. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/initializer/odigos_sampler.py +21 -12
  7. odigos_opentelemetry_python-1.0.90/initializer/version.py +1 -0
  8. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90/odigos_opentelemetry_python.egg-info}/PKG-INFO +3 -5
  9. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/odigos_opentelemetry_python.egg-info/SOURCES.txt +1 -0
  10. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/odigos_opentelemetry_python.egg-info/requires.txt +2 -4
  11. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/opamp/http_client.py +9 -9
  12. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/opamp/transport.py +18 -13
  13. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/pyproject.toml +3 -5
  14. odigos_opentelemetry_python-1.0.88/initializer/__init__.py +0 -22
  15. odigos_opentelemetry_python-1.0.88/initializer/version.py +0 -1
  16. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/LICENSE +0 -0
  17. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/README.md +0 -0
  18. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/initializer/distro/__init__.py +0 -0
  19. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/initializer/distro/distro.py +0 -0
  20. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/initializer/distro/instrumentation_registry.py +0 -0
  21. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/initializer/envs.py +0 -0
  22. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/initializer/exit_hook.py +0 -0
  23. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/initializer/odigos_sampler_helpers.py +0 -0
  24. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/initializer/post_instrument.py +0 -0
  25. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/initializer/process_resource.py +0 -0
  26. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/initializer/schemas/__init__.py +0 -0
  27. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/initializer/schemas/sampling.py +0 -0
  28. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/odigos_opentelemetry_python.egg-info/dependency_links.txt +0 -0
  29. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/odigos_opentelemetry_python.egg-info/entry_points.txt +0 -0
  30. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/odigos_opentelemetry_python.egg-info/top_level.txt +0 -0
  31. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/opamp/__init__.py +0 -0
  32. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/opamp/anyvalue_pb2.py +0 -0
  33. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/opamp/config.py +0 -0
  34. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/opamp/health_status.py +0 -0
  35. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/opamp/opamp_pb2.py +0 -0
  36. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/opamp/opamp_registry.py +0 -0
  37. {odigos_opentelemetry_python-1.0.88 → odigos_opentelemetry_python-1.0.90}/opamp/utils.py +0 -0
  38. {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.88
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-odigos==2.2.2
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()
@@ -1,5 +1,11 @@
1
1
  from typing import Union
2
- from .lib_handling import reorder_python_path, reload_distro_modules, handle_django_instrumentation, handle_eventlet_instrumentation
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
- # sampler_logger.debug(f'Running Should_sample a span with the following attributes: {attributes}')
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
- # sampler_logger.debug('No configuration is set, returning RECORD_AND_SAMPLE')
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
- # sampler_logger.debug(f'Evaluating noisy operation: {noisy_operation}')
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
- # sampler_logger.debug(f'Noisy operation matched: {matched}, percentage: {percentage_to_sample}')
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
- # sampler_logger.debug(f'Dry run [{trace_id}] would_be_sampled={sampled} (lowest_percentage={lowest_percentage})')
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
- # sampler_logger.debug(f'Trace [{trace_id}] is sampled with lowest percentage {lowest_percentage}')
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
- # sampler_logger.debug(f'Trace [{trace_id}] is dropped with lowest percentage {lowest_percentage}')
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
- # sampler_logger.debug('No noisy operation matched, sampling the trace')
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") and not (span_route or "").startswith(http_server_rule["routePrefix"]):
160
- return False
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.88
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-odigos==2.2.2
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
@@ -3,6 +3,7 @@ README.md
3
3
  pyproject.toml
4
4
  initializer/__init__.py
5
5
  initializer/components.py
6
+ initializer/diagnose.py
6
7
  initializer/envs.py
7
8
  initializer/exit_hook.py
8
9
  initializer/lib_handling.py
@@ -1,11 +1,8 @@
1
1
  uuid7==0.1.0
2
- urllib3-odigos==2.2.2
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
@@ -5,7 +5,7 @@ import json
5
5
  import time
6
6
  import base64
7
7
  import threading
8
- import requests_odigos
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
- # opamp_logger.warning(f"{error_message}, sending disconnect message to OpAMP server...")
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
- # opamp_logger.error(f"Error running OpAMP client: {e}")
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
- # opamp_logger.info("Received request to report full state")
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 requests_odigos.RequestException:
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
- # opamp_logger.debug("Sending heartbeat to OpAMP server...")
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 requests_odigos.RequestException:
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
- # opamp_logger.info("Sending agent disconnect message to OpAMP server...")
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,
@@ -2,19 +2,31 @@ import os
2
2
  import socket
3
3
  import http.client
4
4
 
5
- import requests_odigos
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._url = f"http://{host}/v1/opamp"
25
+ self._host = host
13
26
 
14
27
  def post(self, body: bytes, headers: dict, timeout: float) -> bytes:
15
- response = requests_odigos.post(self._url, data=body, headers=headers, timeout=timeout)
16
- response.raise_for_status()
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
- try:
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.88"
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-odigos == 2.2.2",
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"