odigos-opentelemetry-python 1.0.85__tar.gz → 1.0.87__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 (37) hide show
  1. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/PKG-INFO +1 -1
  2. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/initializer/__init__.py +5 -4
  3. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/initializer/components.py +8 -7
  4. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/initializer/distro/distro.py +3 -6
  5. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/initializer/distro/instrumentation_registry.py +2 -1
  6. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/initializer/envs.py +2 -1
  7. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/initializer/exit_hook.py +4 -1
  8. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/initializer/lib_handling.py +19 -22
  9. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/initializer/post_instrument.py +1 -0
  10. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/initializer/process_resource.py +24 -18
  11. odigos_opentelemetry_python-1.0.87/initializer/version.py +1 -0
  12. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/odigos_opentelemetry_python.egg-info/PKG-INFO +1 -1
  13. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/odigos_opentelemetry_python.egg-info/SOURCES.txt +1 -0
  14. odigos_opentelemetry_python-1.0.87/opamp/anyvalue_pb2.py +80 -0
  15. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/opamp/health_status.py +1 -0
  16. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/opamp/http_client.py +26 -27
  17. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/opamp/opamp_registry.py +4 -2
  18. odigos_opentelemetry_python-1.0.87/opamp/transport.py +64 -0
  19. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/opamp/utils.py +3 -1
  20. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/pyproject.toml +6 -1
  21. odigos_opentelemetry_python-1.0.85/initializer/version.py +0 -1
  22. odigos_opentelemetry_python-1.0.85/opamp/anyvalue_pb2.py +0 -65
  23. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/LICENSE +0 -0
  24. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/README.md +0 -0
  25. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/initializer/distro/__init__.py +0 -0
  26. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/initializer/odigos_sampler.py +0 -0
  27. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/initializer/odigos_sampler_helpers.py +0 -0
  28. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/initializer/schemas/__init__.py +0 -0
  29. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/initializer/schemas/sampling.py +0 -0
  30. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/odigos_opentelemetry_python.egg-info/dependency_links.txt +0 -0
  31. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/odigos_opentelemetry_python.egg-info/entry_points.txt +0 -0
  32. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/odigos_opentelemetry_python.egg-info/requires.txt +0 -0
  33. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/odigos_opentelemetry_python.egg-info/top_level.txt +0 -0
  34. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/opamp/__init__.py +0 -0
  35. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/opamp/config.py +0 -0
  36. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/opamp/opamp_pb2.py +0 -0
  37. {odigos_opentelemetry_python-1.0.85 → odigos_opentelemetry_python-1.0.87}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: odigos-opentelemetry-python
3
- Version: 1.0.85
3
+ Version: 1.0.87
4
4
  Summary: Odigos Initializer for Python OpenTelemetry Components
5
5
  Author-email: odigos-io <support@odigos.io>
6
6
  Requires-Python: >=3.9
@@ -1,4 +1,4 @@
1
- import logging
1
+ import logging
2
2
 
3
3
 
4
4
  # This is the main odigos-opentelemetry-python logger setup function.
@@ -10,12 +10,13 @@ def setup_logging():
10
10
  formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
11
11
  handler.setFormatter(formatter)
12
12
  odigos_logger.addHandler(handler)
13
-
13
+
14
14
  odigos_logger.propagate = False # Prevent logs from reaching the root logger
15
15
  odigos_logger.disabled = True # Comment to enable the logger
16
-
16
+
17
17
  # Suppress third-party loggers used by odigos-opentelemetry-python (before they are used)
18
18
  logging.getLogger('urllib3_odigos').setLevel(logging.CRITICAL)
19
19
  logging.getLogger('requests_odigos').setLevel(logging.CRITICAL)
20
20
 
21
- setup_logging()
21
+
22
+ setup_logging()
@@ -1,3 +1,4 @@
1
+ from typing import Union
1
2
  from .lib_handling import reorder_python_path, reload_distro_modules, handle_django_instrumentation, handle_eventlet_instrumentation
2
3
 
3
4
  handle_eventlet_instrumentation()
@@ -74,7 +75,7 @@ def initialize_components(trace_exporters=False, span_processor=None):
74
75
  # we will use the default value which is enable traces only.
75
76
  if opamp_connection_event.error:
76
77
  container_config = None
77
- supported_signals = {"traceSignal": True}
78
+ supported_signals: dict[str, Union[str, bool]] = {"traceSignal": True}
78
79
  initial_sampler_config = None
79
80
  initial_remote_config = None
80
81
  else:
@@ -109,8 +110,8 @@ def initialize_components(trace_exporters=False, span_processor=None):
109
110
  odigos_sampler = initialize_traces_if_enabled(
110
111
  trace_exporters,
111
112
  resource,
112
- span_processor,
113
113
  supported_signals,
114
+ span_processor,
114
115
  initial_sampler_config,
115
116
  initial_remote_config,
116
117
  )
@@ -132,8 +133,8 @@ def initialize_components(trace_exporters=False, span_processor=None):
132
133
  def initialize_traces_if_enabled(
133
134
  trace_exporters,
134
135
  resource,
136
+ signals: dict[str, Union[str, bool]],
135
137
  span_processor=None,
136
- signals=None,
137
138
  initial_sampler_config=None,
138
139
  initial_remote_config=None,
139
140
  ):
@@ -301,7 +302,7 @@ def update_agent_config(conf: Config):
301
302
  proc.update_config(conf)
302
303
 
303
304
 
304
- def is_supported_python_version():
305
+ def is_supported_python_version() -> bool:
305
306
  return sys.version_info >= MINIMUM_PYTHON_SUPPORTED_VERSION
306
307
 
307
308
 
@@ -374,11 +375,11 @@ class ProxyResource(Resource):
374
375
  @property
375
376
  def attributes(self):
376
377
  self._refresh_if_needed()
377
- return self._cached.attributes
378
+ return self._cached.attributes if self._cached else None
378
379
 
379
380
  def merge(self, other: Resource):
380
381
  self._refresh_if_needed()
381
- return self._cached.merge(other)
382
+ return self._cached.merge(other) if self._cached else None
382
383
 
383
384
 
384
385
  def _after_in_child():
@@ -390,7 +391,7 @@ def _after_in_child():
390
391
  # Without this, the forked client's sampler is None and config updates
391
392
  # from OpAMP heartbeats are silently dropped.
392
393
  provider = get_tracer_provider()
393
- if isinstance(getattr(provider, "resource", None), ProxyResource):
394
+ if isinstance(provider, TracerProvider) and isinstance(provider.resource, ProxyResource):
394
395
  provider.resource._child_refreshed = False
395
396
  client = start_opamp_client(OpampConnectionEvent())
396
397
 
@@ -1,6 +1,6 @@
1
1
  from opentelemetry.instrumentation.distro import BaseDistro
2
2
  from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
3
- from importlib.metadata import EntryPoint
3
+ from opentelemetry.util._importlib_metadata import EntryPoint
4
4
  from .instrumentation_registry import add_instrumented_library
5
5
 
6
6
 
@@ -22,14 +22,11 @@ class OdigosDistro(BaseDistro):
22
22
  pass
23
23
 
24
24
  def load_instrumentor(self, entry_point: EntryPoint, **kwargs) -> None:
25
- instrumentor: BaseInstrumentor = entry_point.load()
25
+ instrumentor: type[BaseInstrumentor] = entry_point.load()
26
26
  instance = instrumentor()
27
27
  instance.instrument(**kwargs)
28
28
 
29
29
  # Later we can add more details here, like the version of the library, etc.
30
- instrumentation_details = {
31
- "is_standard_lib": entry_point.name in STANDARD_LIB_MODULES
32
- }
30
+ instrumentation_details = {"is_standard_lib": entry_point.name in STANDARD_LIB_MODULES}
33
31
 
34
32
  add_instrumented_library(entry_point.name, instrumentation_details)
35
-
@@ -1,8 +1,9 @@
1
-
2
1
  _instrumented = {}
3
2
 
3
+
4
4
  def add_instrumented_library(name, instrumentation_details) -> None:
5
5
  _instrumented[name] = instrumentation_details
6
6
 
7
+
7
8
  def get_instrumented_libraries() -> dict:
8
9
  return _instrumented
@@ -4,6 +4,7 @@ static_env_dict = {
4
4
  "OTEL_PYTHON_LOG_CORRELATION": "true",
5
5
  }
6
6
 
7
+
7
8
  def set_static_otel_env():
8
9
  """
9
10
  Injects environment variables from a dictionary.
@@ -11,4 +12,4 @@ def set_static_otel_env():
11
12
  if not isinstance(static_env_dict, dict):
12
13
  raise TypeError("static_env_dict must be a dictionary")
13
14
 
14
- os.environ.update(static_env_dict)
15
+ os.environ.update(static_env_dict)
@@ -1,9 +1,12 @@
1
1
  import sys
2
+
3
+
2
4
  class ExitHooks(object):
3
5
  """
4
6
  The ExitHooks class intercepts program exits to capture the reason for termination.
5
7
  It hooks into sys.exit to record exit codes
6
8
  """
9
+
7
10
  def __init__(self):
8
11
  self.exit_code = None
9
12
  self.exception = None
@@ -11,7 +14,7 @@ class ExitHooks(object):
11
14
  def hook(self):
12
15
  self._orig_exit = sys.exit
13
16
  self._orig_excepthook = sys.excepthook
14
- sys.exit = self.exit
17
+ sys.exit = self.exit # ty: ignore[invalid-assignment]
15
18
  sys.excepthook = self.exc_handler
16
19
 
17
20
  def exit(self, code=0):
@@ -2,16 +2,17 @@ import sys
2
2
  import importlib
3
3
  import os
4
4
 
5
+
5
6
  def reorder_python_path():
6
7
  # Moves '/var/odigos/' [k8s] and '/etc/odigos-vmagent/' [vmagent] paths to end of sys.path
7
8
  # to prioritize user dependencies over odigos ones
8
9
  paths_to_move = [path for path in sys.path if path.startswith(('/var/odigos/', '/etc/odigos-vmagent/'))]
9
-
10
+
10
11
  for path in paths_to_move:
11
12
  sys.path.remove(path)
12
13
  sys.path.append(path)
13
-
14
-
14
+
15
+
15
16
  def reload_distro_modules() -> None:
16
17
  # Delete distro modules and their sub-modules, as they have been imported before the path was reordered.
17
18
  # The distro modules will be re-imported from the new path.
@@ -30,62 +31,58 @@ def reload_distro_modules() -> None:
30
31
  'uuid_extensions.uuid7',
31
32
  'typing_extensions',
32
33
  ]
33
-
34
- excluded_modules = [
35
- 'urllib3_odigos',
36
- 'requests_odigos'
37
- ]
38
-
34
+
35
+ excluded_modules = ['urllib3_odigos', 'requests_odigos']
36
+
39
37
  for module in list(sys.modules):
40
38
  # Check if the module starts with any of the needed prefixes, but not with any of the excluded prefixes.
41
39
  if any(module.startswith(prefix) for prefix in excluded_modules):
42
40
  continue
43
-
41
+
44
42
  if any(module.startswith(prefix) for prefix in needed_module_prefixes):
45
43
  module_file = getattr(sys.modules[module], '__file__', None) # Safely get __file__
46
44
  if module_file and ('/etc/odigos-vmagent/' in module_file or '/var/odigos/' in module_file):
47
45
  del sys.modules[module]
48
46
 
49
47
 
50
-
51
48
  ### Django
52
49
 
50
+
53
51
  # Disables Django instrumentation if DJANGO_SETTINGS_MODULE is unset and adds the current directory to sys.path for proper Django instrumentation.
54
52
  # These changes address this issue: https://github.com/open-telemetry/opentelemetry-python-contrib/issues/2495.
55
53
  # TODO: Remove once the bug is fixed.
56
54
  def handle_django_instrumentation():
57
55
  # Get the DJANGO_SETTINGS_MODULE environment variable value.
58
56
  django_settings_module = os.getenv('DJANGO_SETTINGS_MODULE', None)
59
-
57
+
60
58
  if django_settings_module is None:
61
59
  os.environ.setdefault("OTEL_PYTHON_DJANGO_INSTRUMENT", 'False')
62
-
60
+
63
61
  else:
64
62
  cwd_path = os.getcwd()
65
63
  if cwd_path not in sys.path:
66
- sys.path.insert(0, cwd_path)
67
-
64
+ sys.path.insert(0, cwd_path)
65
+
68
66
  # As an additional safeguard, we're ensuring that DJANGO_SETTINGS_MODULE is importable.
69
67
  # This is done to prevent instrumentation from being enabled if the Django settings module cannot be imported.
70
68
  try:
71
- importlib.import_module(django_settings_module)
72
- except:
69
+ importlib.import_module(django_settings_module)
70
+ except Exception:
73
71
  os.environ.setdefault("OTEL_PYTHON_DJANGO_INSTRUMENT", 'False')
74
-
72
+
75
73
 
76
74
  def handle_eventlet_instrumentation():
77
75
  """Checks if eventlet is importable and applies eventlet.monkey_patch if available."""
78
76
  # Apply monkey_patch early to enable Eventlet's green threads for non-blocking I/O.
79
77
  # Per Eventlet docs, it must run before imports or class definitions that use patched modules (e.g., socket).
80
78
  # https://eventlet.readthedocs.io/en/v0.35.1/patching.html#monkey-patch
81
-
79
+
82
80
  # Currently should run only if no OPAMP client is running, as tested only for the VM agent.
83
81
  if os.getenv('DISABLE_OPAMP_CLIENT', 'false').strip().lower() == 'true':
84
82
  try:
85
83
  eventlet = importlib.import_module("eventlet")
86
84
  if not getattr(eventlet, "_opamp_patched", False): # Avoid multiple patches
87
85
  eventlet.monkey_patch()
88
- eventlet._opamp_patched = True
86
+ setattr(eventlet, "_opamp_patched", True)
89
87
  except ImportError:
90
- pass
91
-
88
+ pass
@@ -1,6 +1,7 @@
1
1
  from opamp import opamp_registry
2
2
  from .distro import instrumentation_registry
3
3
 
4
+
4
5
  def run_post_hook():
5
6
  client = opamp_registry.get_client()
6
7
 
@@ -1,8 +1,8 @@
1
- # This file contains code inspired by OpenTelemetry's resource detection mechanisms
2
- # (https://opentelemetry.io/). It adapts and extends the logic to include additional
1
+ # This file contains code inspired by OpenTelemetry's resource detection mechanisms
2
+ # (https://opentelemetry.io/). It adapts and extends the logic to include additional
3
3
  # attributes required for our use case.
4
4
  #
5
- # OpenTelemetry is licensed under the Apache License, Version 2.0. This adaptation respects
5
+ # OpenTelemetry is licensed under the Apache License, Version 2.0. This adaptation respects
6
6
  # the original licensing terms and acknowledges OpenTelemetry as a source of reference.
7
7
  #
8
8
  # For OpenTelemetry’s original implementation, see:
@@ -10,20 +10,26 @@
10
10
 
11
11
  import os
12
12
  from opentelemetry.sdk.resources import Resource, ProcessResourceDetector
13
- from opentelemetry.semconv.resource import ResourceAttributes
13
+ from opentelemetry.semconv._incubating.attributes.process_attributes import (
14
+ PROCESS_COMMAND,
15
+ PROCESS_COMMAND_ARGS,
16
+ PROCESS_COMMAND_LINE,
17
+ PROCESS_PID,
18
+ )
14
19
 
15
20
  PROCESS_VPID = "process.vpid"
16
21
 
17
22
  # Custom implementation of ProcessResourceDetector.
18
- #
19
- # This detector is based on OpenTelemetry's resource detection logic but has been
20
- # implemented separately because the standard OpenTelemetry detector does not support
21
- # the "process.vpid" attribute. This attribute is necessary for our use case to track
23
+ #
24
+ # This detector is based on OpenTelemetry's resource detection logic but has been
25
+ # implemented separately because the standard OpenTelemetry detector does not support
26
+ # the "process.vpid" attribute. This attribute is necessary for our use case to track
22
27
  # process-specific information that is not covered by OpenTelemetry’s built-in attributes.
23
28
  #
24
- # By extending the OpenTelemetry approach, we ensure compatibility while adding the
29
+ # By extending the OpenTelemetry approach, we ensure compatibility while adding the
25
30
  # additional attribute we require.
26
31
 
32
+
27
33
  # ProcessResourceDetector
28
34
  class OdigosProcessResourceDetector(ProcessResourceDetector):
29
35
  def __init__(self, pid):
@@ -37,18 +43,18 @@ class OdigosProcessResourceDetector(ProcessResourceDetector):
37
43
  # Extract attributes as a dictionary (resource_info is a Resource object)
38
44
  attributes = dict(resource_info.attributes)
39
45
 
40
- attributes.pop(ResourceAttributes.PROCESS_COMMAND_ARGS, None) # Remove PROCESS_COMMAND_ARGS if exists
41
-
46
+ attributes.pop(PROCESS_COMMAND_ARGS, None) # Remove PROCESS_COMMAND_ARGS if exists
47
+
42
48
  if os.getenv("DISABLE_OPAMP_CLIENT", "false").strip().lower() == "false":
43
- attributes.pop(ResourceAttributes.PROCESS_PID, None) # Remove PROCESS_PID if exists
49
+ attributes.pop(PROCESS_PID, None) # Remove PROCESS_PID if exists
44
50
  attributes[PROCESS_VPID] = self.pid
45
-
51
+
46
52
  # Fix for cases where the app is run via `python -m <module>`:
47
53
  # sys.argv[0] becomes "-m", which leads to wrong process.command attribute.
48
54
  # To get the real command, read from /proc/self/cmdline (Linux only).
49
55
  # Falls back silently if reading fails.
50
56
  # See: https://github.com/open-telemetry/opentelemetry-python/issues/4518
51
- process_command = attributes.get(ResourceAttributes.PROCESS_COMMAND, None)
57
+ process_command = attributes.get(PROCESS_COMMAND, None)
52
58
  if process_command is not None and process_command == '-m':
53
59
  try:
54
60
  with open("/proc/self/cmdline", "rb") as command_file:
@@ -57,12 +63,12 @@ class OdigosProcessResourceDetector(ProcessResourceDetector):
57
63
  if raw_cmdline:
58
64
  command_line = raw_cmdline.decode(errors="ignore").split('\0')
59
65
  if command_line and command_line[0]:
60
- attributes[ResourceAttributes.PROCESS_COMMAND] = command_line[0]
61
- attributes[ResourceAttributes.PROCESS_COMMAND_LINE] = " ".join(command_line)
62
-
66
+ attributes[PROCESS_COMMAND] = command_line[0]
67
+ attributes[PROCESS_COMMAND_LINE] = " ".join(command_line)
68
+
63
69
  # On failure, we retain the default behavior from the base resource detector
64
70
  except Exception:
65
71
  pass
66
72
 
67
73
  # Return a new Resource instance with updated attributes
68
- return Resource.create(attributes)
74
+ return Resource.create(attributes)
@@ -0,0 +1 @@
1
+ VERSION = "1.0.87"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: odigos-opentelemetry-python
3
- Version: 1.0.85
3
+ Version: 1.0.87
4
4
  Summary: Odigos Initializer for Python OpenTelemetry Components
5
5
  Author-email: odigos-io <support@odigos.io>
6
6
  Requires-Python: >=3.9
@@ -29,4 +29,5 @@ opamp/health_status.py
29
29
  opamp/http_client.py
30
30
  opamp/opamp_pb2.py
31
31
  opamp/opamp_registry.py
32
+ opamp/transport.py
32
33
  opamp/utils.py
@@ -0,0 +1,80 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # source: anyvalue.proto
4
+ """Generated protocol buffer code."""
5
+
6
+ from google.protobuf import descriptor as _descriptor
7
+ from google.protobuf import descriptor_pool as _descriptor_pool
8
+ from google.protobuf import message as _message
9
+ from google.protobuf import reflection as _reflection
10
+ from google.protobuf import symbol_database as _symbol_database
11
+ # @@protoc_insertion_point(imports)
12
+
13
+ _sym_db = _symbol_database.Default()
14
+
15
+
16
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
17
+ b'\n\x0e\x61nyvalue.proto\x12\x0bopamp.proto"\xe8\x01\n\x08\x41nyValue\x12\x16\n\x0cstring_value\x18\x01 \x01(\tH\x00\x12\x14\n\nbool_value\x18\x02 \x01(\x08H\x00\x12\x13\n\tint_value\x18\x03 \x01(\x03H\x00\x12\x16\n\x0c\x64ouble_value\x18\x04 \x01(\x01H\x00\x12.\n\x0b\x61rray_value\x18\x05 \x01(\x0b\x32\x17.opamp.proto.ArrayValueH\x00\x12\x31\n\x0ckvlist_value\x18\x06 \x01(\x0b\x32\x19.opamp.proto.KeyValueListH\x00\x12\x15\n\x0b\x62ytes_value\x18\x07 \x01(\x0cH\x00\x42\x07\n\x05value"3\n\nArrayValue\x12%\n\x06values\x18\x01 \x03(\x0b\x32\x15.opamp.proto.AnyValue"5\n\x0cKeyValueList\x12%\n\x06values\x18\x01 \x03(\x0b\x32\x15.opamp.proto.KeyValue"=\n\x08KeyValue\x12\x0b\n\x03key\x18\x01 \x01(\t\x12$\n\x05value\x18\x02 \x01(\x0b\x32\x15.opamp.proto.AnyValueB.Z,github.com/open-telemetry/opamp-go/protobufsb\x06proto3'
18
+ )
19
+
20
+
21
+ _ANYVALUE = DESCRIPTOR.message_types_by_name['AnyValue']
22
+ _ARRAYVALUE = DESCRIPTOR.message_types_by_name['ArrayValue']
23
+ _KEYVALUELIST = DESCRIPTOR.message_types_by_name['KeyValueList']
24
+ _KEYVALUE = DESCRIPTOR.message_types_by_name['KeyValue']
25
+ AnyValue = _reflection.GeneratedProtocolMessageType(
26
+ 'AnyValue',
27
+ (_message.Message,),
28
+ {
29
+ 'DESCRIPTOR': _ANYVALUE,
30
+ '__module__': 'anyvalue_pb2',
31
+ # @@protoc_insertion_point(class_scope:opamp.proto.AnyValue)
32
+ },
33
+ )
34
+ _sym_db.RegisterMessage(AnyValue)
35
+
36
+ ArrayValue = _reflection.GeneratedProtocolMessageType(
37
+ 'ArrayValue',
38
+ (_message.Message,),
39
+ {
40
+ 'DESCRIPTOR': _ARRAYVALUE,
41
+ '__module__': 'anyvalue_pb2',
42
+ # @@protoc_insertion_point(class_scope:opamp.proto.ArrayValue)
43
+ },
44
+ )
45
+ _sym_db.RegisterMessage(ArrayValue)
46
+
47
+ KeyValueList = _reflection.GeneratedProtocolMessageType(
48
+ 'KeyValueList',
49
+ (_message.Message,),
50
+ {
51
+ 'DESCRIPTOR': _KEYVALUELIST,
52
+ '__module__': 'anyvalue_pb2',
53
+ # @@protoc_insertion_point(class_scope:opamp.proto.KeyValueList)
54
+ },
55
+ )
56
+ _sym_db.RegisterMessage(KeyValueList)
57
+
58
+ KeyValue = _reflection.GeneratedProtocolMessageType(
59
+ 'KeyValue',
60
+ (_message.Message,),
61
+ {
62
+ 'DESCRIPTOR': _KEYVALUE,
63
+ '__module__': 'anyvalue_pb2',
64
+ # @@protoc_insertion_point(class_scope:opamp.proto.KeyValue)
65
+ },
66
+ )
67
+ _sym_db.RegisterMessage(KeyValue)
68
+
69
+ if _descriptor._USE_C_DESCRIPTORS == False:
70
+ DESCRIPTOR._options = None
71
+ DESCRIPTOR._serialized_options = b'Z,github.com/open-telemetry/opamp-go/protobufs'
72
+ _ANYVALUE._serialized_start = 32
73
+ _ANYVALUE._serialized_end = 264
74
+ _ARRAYVALUE._serialized_start = 266
75
+ _ARRAYVALUE._serialized_end = 317
76
+ _KEYVALUELIST._serialized_start = 319
77
+ _KEYVALUELIST._serialized_end = 372
78
+ _KEYVALUE._serialized_start = 374
79
+ _KEYVALUE._serialized_end = 435
80
+ # @@protoc_insertion_point(module_scope)
@@ -1,5 +1,6 @@
1
1
  from enum import Enum
2
2
 
3
+
3
4
  class AgentHealthStatus(str, Enum):
4
5
  HEALTHY = "Healthy"
5
6
  STARTING = "Starting"
@@ -1,3 +1,4 @@
1
+ from typing import Optional, cast
1
2
  import os
2
3
  import sys
3
4
  import json
@@ -8,7 +9,14 @@ import requests_odigos
8
9
  import logging
9
10
 
10
11
  from uuid_extensions import uuid7
11
- from opentelemetry.semconv.resource import ResourceAttributes
12
+ from opentelemetry.semconv._incubating.attributes.k8s_attributes import (
13
+ K8S_CONTAINER_NAME,
14
+ K8S_NAMESPACE_NAME,
15
+ K8S_POD_NAME,
16
+ )
17
+ from opentelemetry.semconv._incubating.attributes.process_attributes import PROCESS_PID
18
+ from opentelemetry.semconv.attributes.service_attributes import SERVICE_INSTANCE_ID
19
+ from opentelemetry.semconv.attributes.telemetry_attributes import TELEMETRY_SDK_LANGUAGE
12
20
  from opentelemetry.context import (
13
21
  _SUPPRESS_HTTP_INSTRUMENTATION_KEY,
14
22
  attach,
@@ -16,7 +24,7 @@ from opentelemetry.context import (
16
24
  set_value,
17
25
  )
18
26
 
19
- from opamp import opamp_pb2, anyvalue_pb2, utils
27
+ from opamp import opamp_pb2, anyvalue_pb2, transport, utils
20
28
  from opamp.health_status import AgentHealthStatus
21
29
  from initializer.process_resource import PROCESS_VPID
22
30
 
@@ -31,16 +39,15 @@ opamp_logger = logging.getLogger('odigos')
31
39
  _debugger_instance = None
32
40
 
33
41
  env_var_mappings = {
34
- "ODIGOS_WORKLOAD_NAMESPACE": ResourceAttributes.K8S_NAMESPACE_NAME,
35
- "ODIGOS_CONTAINER_NAME": ResourceAttributes.K8S_CONTAINER_NAME,
36
- "ODIGOS_POD_NAME": ResourceAttributes.K8S_POD_NAME,
42
+ "ODIGOS_WORKLOAD_NAMESPACE": K8S_NAMESPACE_NAME,
43
+ "ODIGOS_CONTAINER_NAME": K8S_CONTAINER_NAME,
44
+ "ODIGOS_POD_NAME": K8S_POD_NAME,
37
45
  }
38
46
 
39
47
 
40
48
  class OpAMPHTTPClient:
41
49
  def __init__(self, opamp_connection_event, condition: threading.Condition):
42
- self.server_host = os.getenv('ODIGOS_OPAMP_SERVER_HOST')
43
- self.server_url = f"http://{self.server_host}/v1/opamp"
50
+ self._transport = transport.from_env()
44
51
  self.container_config = {}
45
52
  self.running = True
46
53
  self.condition = condition
@@ -57,7 +64,7 @@ class OpAMPHTTPClient:
57
64
  def __repr__(self):
58
65
  return f"<OpAMPHTTPClient instance_uid={self.instance_uid} pid={self.pid}>"
59
66
 
60
- def start(self, python_version_supported: bool = None):
67
+ def start(self, python_version_supported: bool = False):
61
68
  if not python_version_supported:
62
69
  python_version = f'{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}'
63
70
  error_message = f"Opentelemetry SDK require Python in version 3.8 or higher [{python_version} is not supported]"
@@ -263,7 +270,7 @@ class OpAMPHTTPClient:
263
270
 
264
271
  self.send_agent_to_server_message(opamp_pb2.AgentToServer(health=health_msg))
265
272
 
266
- def get_remote_config(self, message) -> dict:
273
+ def get_remote_config(self, message) -> Config:
267
274
  """
268
275
  Given a Protobuf message with a 'remote_config' field,
269
276
  decode all bytes and base64-encoded subfields into a clean Python dict.
@@ -306,7 +313,7 @@ class OpAMPHTTPClient:
306
313
  config = Config()
307
314
 
308
315
  # Extract container_config if present
309
- container_config = decoded_map.get("container_config", None)
316
+ container_config: Optional[dict] = cast(Optional[dict], decoded_map.get("container_config"))
310
317
  if container_config is not None:
311
318
  config.container_config = container_config
312
319
 
@@ -344,14 +351,14 @@ class OpAMPHTTPClient:
344
351
  # Add additional attributes from environment variables
345
352
 
346
353
  if os.getenv("DISABLE_OPAMP_CLIENT", "false").strip().lower() == "true":
347
- process_id_key = ResourceAttributes.PROCESS_PID
354
+ process_id_key = PROCESS_PID
348
355
  else:
349
356
  process_id_key = PROCESS_VPID
350
357
 
351
358
  identifying_attributes = [
352
- anyvalue_pb2.KeyValue(key=ResourceAttributes.SERVICE_INSTANCE_ID, value=anyvalue_pb2.AnyValue(string_value=self.instance_uid)),
359
+ anyvalue_pb2.KeyValue(key=SERVICE_INSTANCE_ID, value=anyvalue_pb2.AnyValue(string_value=self.instance_uid)),
353
360
  anyvalue_pb2.KeyValue(key=process_id_key, value=anyvalue_pb2.AnyValue(int_value=self.pid)),
354
- anyvalue_pb2.KeyValue(key=ResourceAttributes.TELEMETRY_SDK_LANGUAGE, value=anyvalue_pb2.AnyValue(string_value="python")),
361
+ anyvalue_pb2.KeyValue(key=TELEMETRY_SDK_LANGUAGE, value=anyvalue_pb2.AnyValue(string_value="python")),
355
362
  ]
356
363
 
357
364
  # Add additional attributes from environment variables
@@ -392,8 +399,7 @@ class OpAMPHTTPClient:
392
399
 
393
400
  try:
394
401
  agent_message = attach(set_value(_SUPPRESS_HTTP_INSTRUMENTATION_KEY, True))
395
- response = requests_odigos.post(self.server_url, data=message_bytes, headers=headers, timeout=5)
396
- response.raise_for_status()
402
+ response_bytes = self._transport.post(message_bytes, headers, timeout=5)
397
403
  except Exception:
398
404
  return opamp_pb2.ServerToAgent()
399
405
  finally:
@@ -401,24 +407,17 @@ class OpAMPHTTPClient:
401
407
 
402
408
  server_to_agent = opamp_pb2.ServerToAgent()
403
409
  try:
404
- server_to_agent.ParseFromString(response.content)
410
+ server_to_agent.ParseFromString(response_bytes)
405
411
  except (DecodeError, NotImplementedError):
406
412
  return opamp_pb2.ServerToAgent()
407
413
  return server_to_agent
408
414
 
409
415
  def mandatory_env_vars_set(self):
410
- mandatory_env_vars = {
411
- "ODIGOS_OPAMP_SERVER_HOST": self.server_host,
412
- }
413
-
414
- for env_var, value in mandatory_env_vars.items():
415
- if not value:
416
- # opamp_logger.error(f"{env_var} environment variable not set")
417
- return False
418
-
419
- return True
416
+ # Either ODIGOS_OPAMP_UNIX_SOCKET or ODIGOS_OPAMP_SERVER_HOST must be set;
417
+ # transport.from_env() returns None when neither is configured.
418
+ return self._transport is not None
420
419
 
421
- def shutdown(self, custom_failure_message: str = None, component_health: bool = False):
420
+ def shutdown(self, custom_failure_message: Optional[str] = None, component_health: bool = False):
422
421
  self.running = False
423
422
  # opamp_logger.info("Sending agent disconnect message to OpAMP server...")
424
423
  if custom_failure_message:
@@ -1,17 +1,19 @@
1
1
  import os
2
-
2
+ from typing import Optional
3
3
  from opamp.http_client import OpAMPHTTPClient
4
4
 
5
5
 
6
6
  _client = None
7
7
  _client_pid = None
8
8
 
9
+
9
10
  def set_client(client) -> None:
10
11
  global _client, _client_pid
11
12
  _client = client
12
13
  _client_pid = os.getpid()
13
14
 
14
- def get_client() -> OpAMPHTTPClient:
15
+
16
+ def get_client() -> Optional[OpAMPHTTPClient]:
15
17
  if _client_pid != os.getpid():
16
18
  return None # Defensive: ensure we don't return a client from a different process
17
19
  return _client
@@ -0,0 +1,64 @@
1
+ import os
2
+ import socket
3
+ import http.client
4
+
5
+ import requests_odigos
6
+
7
+
8
+ class TCPTransport:
9
+ """HTTP/1.1 over TCP. Talks to ODIGOS_OPAMP_SERVER_HOST."""
10
+
11
+ def __init__(self, host: str):
12
+ self._url = f"http://{host}/v1/opamp"
13
+
14
+ 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
18
+
19
+
20
+ class UnixTransport:
21
+ """HTTP/1.1 over an AF_UNIX stream socket. Talks to ODIGOS_OPAMP_UNIX_SOCKET."""
22
+
23
+ def __init__(self, socket_path: str):
24
+ self._socket_path = socket_path
25
+
26
+ def post(self, body: bytes, headers: dict, timeout: float) -> bytes:
27
+ 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()
36
+
37
+
38
+ class _UnixHTTPConnection(http.client.HTTPConnection):
39
+ """http.client.HTTPConnection that talks over a unix domain socket."""
40
+
41
+ def __init__(self, socket_path: str, timeout: float):
42
+ super().__init__("localhost", timeout=timeout)
43
+ self._socket_path = socket_path
44
+
45
+ def connect(self):
46
+ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
47
+ sock.settimeout(self.timeout)
48
+ sock.connect(self._socket_path)
49
+ self.sock = sock
50
+
51
+
52
+ def from_env():
53
+ """Pick an OpAMP transport from env vars. ODIGOS_OPAMP_UNIX_SOCKET wins if both are set.
54
+ Returns None if neither is set so the client can fail fast on missing config.
55
+ """
56
+ socket_path = os.getenv("ODIGOS_OPAMP_UNIX_SOCKET")
57
+ if socket_path:
58
+ return UnixTransport(socket_path)
59
+
60
+ host = os.getenv("ODIGOS_OPAMP_SERVER_HOST")
61
+ if host:
62
+ return TCPTransport(host)
63
+
64
+ return None
@@ -1,5 +1,7 @@
1
+ from typing import Union
1
2
  import json
2
3
 
4
+
3
5
  def get_container_config(config_map):
4
6
  """
5
7
  Extracts and parses the container configuration from the config map.
@@ -13,7 +15,7 @@ def get_container_config(config_map):
13
15
  return {}
14
16
 
15
17
 
16
- def parse_first_message_signals(container_config): # type: ignore
18
+ def parse_first_message_signals(container_config) -> dict[str, Union[str, bool]]:
17
19
  """
18
20
  Parses the trace, logs, and metrics signals from the container config.
19
21
  Signal is considered enabled if its key exists in the config.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "odigos-opentelemetry-python"
7
- version = "1.0.85"
7
+ version = "1.0.87"
8
8
  description = "Odigos Initializer for Python OpenTelemetry Components"
9
9
  requires-python = ">=3.9"
10
10
  authors = [
@@ -139,6 +139,8 @@ extend-exclude = [
139
139
  "build",
140
140
  "*.egg-info",
141
141
  "tests/integration/configurator",
142
+ "instrumentations/",
143
+ "*_pb2.py",
142
144
  ]
143
145
 
144
146
  [tool.ruff.lint]
@@ -161,4 +163,7 @@ exclude = [
161
163
  "dist",
162
164
  "build",
163
165
  "tests/integration/configurator",
166
+ "tests/integration/apps",
167
+ "instrumentations/",
168
+ "opamp/opamp_pb2.py"
164
169
  ]
@@ -1 +0,0 @@
1
- VERSION = "1.0.85"
@@ -1,65 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- # Generated by the protocol buffer compiler. DO NOT EDIT!
3
- # source: anyvalue.proto
4
- """Generated protocol buffer code."""
5
- from google.protobuf import descriptor as _descriptor
6
- from google.protobuf import descriptor_pool as _descriptor_pool
7
- from google.protobuf import message as _message
8
- from google.protobuf import reflection as _reflection
9
- from google.protobuf import symbol_database as _symbol_database
10
- # @@protoc_insertion_point(imports)
11
-
12
- _sym_db = _symbol_database.Default()
13
-
14
-
15
-
16
-
17
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0e\x61nyvalue.proto\x12\x0bopamp.proto\"\xe8\x01\n\x08\x41nyValue\x12\x16\n\x0cstring_value\x18\x01 \x01(\tH\x00\x12\x14\n\nbool_value\x18\x02 \x01(\x08H\x00\x12\x13\n\tint_value\x18\x03 \x01(\x03H\x00\x12\x16\n\x0c\x64ouble_value\x18\x04 \x01(\x01H\x00\x12.\n\x0b\x61rray_value\x18\x05 \x01(\x0b\x32\x17.opamp.proto.ArrayValueH\x00\x12\x31\n\x0ckvlist_value\x18\x06 \x01(\x0b\x32\x19.opamp.proto.KeyValueListH\x00\x12\x15\n\x0b\x62ytes_value\x18\x07 \x01(\x0cH\x00\x42\x07\n\x05value\"3\n\nArrayValue\x12%\n\x06values\x18\x01 \x03(\x0b\x32\x15.opamp.proto.AnyValue\"5\n\x0cKeyValueList\x12%\n\x06values\x18\x01 \x03(\x0b\x32\x15.opamp.proto.KeyValue\"=\n\x08KeyValue\x12\x0b\n\x03key\x18\x01 \x01(\t\x12$\n\x05value\x18\x02 \x01(\x0b\x32\x15.opamp.proto.AnyValueB.Z,github.com/open-telemetry/opamp-go/protobufsb\x06proto3')
18
-
19
-
20
-
21
- _ANYVALUE = DESCRIPTOR.message_types_by_name['AnyValue']
22
- _ARRAYVALUE = DESCRIPTOR.message_types_by_name['ArrayValue']
23
- _KEYVALUELIST = DESCRIPTOR.message_types_by_name['KeyValueList']
24
- _KEYVALUE = DESCRIPTOR.message_types_by_name['KeyValue']
25
- AnyValue = _reflection.GeneratedProtocolMessageType('AnyValue', (_message.Message,), {
26
- 'DESCRIPTOR' : _ANYVALUE,
27
- '__module__' : 'anyvalue_pb2'
28
- # @@protoc_insertion_point(class_scope:opamp.proto.AnyValue)
29
- })
30
- _sym_db.RegisterMessage(AnyValue)
31
-
32
- ArrayValue = _reflection.GeneratedProtocolMessageType('ArrayValue', (_message.Message,), {
33
- 'DESCRIPTOR' : _ARRAYVALUE,
34
- '__module__' : 'anyvalue_pb2'
35
- # @@protoc_insertion_point(class_scope:opamp.proto.ArrayValue)
36
- })
37
- _sym_db.RegisterMessage(ArrayValue)
38
-
39
- KeyValueList = _reflection.GeneratedProtocolMessageType('KeyValueList', (_message.Message,), {
40
- 'DESCRIPTOR' : _KEYVALUELIST,
41
- '__module__' : 'anyvalue_pb2'
42
- # @@protoc_insertion_point(class_scope:opamp.proto.KeyValueList)
43
- })
44
- _sym_db.RegisterMessage(KeyValueList)
45
-
46
- KeyValue = _reflection.GeneratedProtocolMessageType('KeyValue', (_message.Message,), {
47
- 'DESCRIPTOR' : _KEYVALUE,
48
- '__module__' : 'anyvalue_pb2'
49
- # @@protoc_insertion_point(class_scope:opamp.proto.KeyValue)
50
- })
51
- _sym_db.RegisterMessage(KeyValue)
52
-
53
- if _descriptor._USE_C_DESCRIPTORS == False:
54
-
55
- DESCRIPTOR._options = None
56
- DESCRIPTOR._serialized_options = b'Z,github.com/open-telemetry/opamp-go/protobufs'
57
- _ANYVALUE._serialized_start=32
58
- _ANYVALUE._serialized_end=264
59
- _ARRAYVALUE._serialized_start=266
60
- _ARRAYVALUE._serialized_end=317
61
- _KEYVALUELIST._serialized_start=319
62
- _KEYVALUELIST._serialized_end=372
63
- _KEYVALUE._serialized_start=374
64
- _KEYVALUE._serialized_end=435
65
- # @@protoc_insertion_point(module_scope)