monocle-apptrace 0.3.0b3__py3-none-any.whl → 0.3.0b5__py3-none-any.whl

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.

Potentially problematic release.


This version of monocle-apptrace might be problematic. Click here for more details.

Files changed (38) hide show
  1. monocle_apptrace/__main__.py +19 -0
  2. monocle_apptrace/exporters/aws/s3_exporter.py +17 -8
  3. monocle_apptrace/exporters/monocle_exporters.py +5 -4
  4. monocle_apptrace/instrumentation/common/constants.py +5 -0
  5. monocle_apptrace/instrumentation/common/instrumentor.py +82 -11
  6. monocle_apptrace/instrumentation/common/span_handler.py +27 -12
  7. monocle_apptrace/instrumentation/common/utils.py +112 -5
  8. monocle_apptrace/instrumentation/common/wrapper.py +48 -23
  9. monocle_apptrace/instrumentation/common/wrapper_method.py +30 -7
  10. monocle_apptrace/instrumentation/metamodel/botocore/_helper.py +0 -31
  11. monocle_apptrace/instrumentation/metamodel/botocore/handlers/botocore_span_handler.py +25 -0
  12. monocle_apptrace/instrumentation/metamodel/botocore/methods.py +6 -6
  13. monocle_apptrace/instrumentation/metamodel/flask/__init__.py +0 -0
  14. monocle_apptrace/instrumentation/metamodel/flask/_helper.py +29 -0
  15. monocle_apptrace/instrumentation/metamodel/flask/methods.py +13 -0
  16. monocle_apptrace/instrumentation/metamodel/langchain/_helper.py +6 -0
  17. monocle_apptrace/instrumentation/metamodel/langgraph/__init__.py +0 -0
  18. monocle_apptrace/instrumentation/metamodel/langgraph/_helper.py +48 -0
  19. monocle_apptrace/instrumentation/metamodel/langgraph/entities/__init__.py +0 -0
  20. monocle_apptrace/instrumentation/metamodel/langgraph/entities/inference.py +56 -0
  21. monocle_apptrace/instrumentation/metamodel/langgraph/methods.py +14 -0
  22. monocle_apptrace/instrumentation/metamodel/llamaindex/_helper.py +37 -19
  23. monocle_apptrace/instrumentation/metamodel/llamaindex/entities/agent.py +47 -0
  24. monocle_apptrace/instrumentation/metamodel/llamaindex/methods.py +9 -0
  25. monocle_apptrace/instrumentation/metamodel/openai/__init__.py +0 -0
  26. monocle_apptrace/instrumentation/metamodel/openai/_helper.py +88 -0
  27. monocle_apptrace/instrumentation/metamodel/openai/entities/__init__.py +0 -0
  28. monocle_apptrace/instrumentation/metamodel/openai/entities/inference.py +71 -0
  29. monocle_apptrace/instrumentation/metamodel/openai/entities/retrieval.py +24 -0
  30. monocle_apptrace/instrumentation/metamodel/openai/methods.py +25 -0
  31. monocle_apptrace/instrumentation/metamodel/requests/__init__.py +2 -0
  32. monocle_apptrace/instrumentation/metamodel/requests/_helper.py +31 -0
  33. monocle_apptrace/instrumentation/metamodel/requests/methods.py +12 -0
  34. {monocle_apptrace-0.3.0b3.dist-info → monocle_apptrace-0.3.0b5.dist-info}/METADATA +2 -1
  35. {monocle_apptrace-0.3.0b3.dist-info → monocle_apptrace-0.3.0b5.dist-info}/RECORD +38 -18
  36. {monocle_apptrace-0.3.0b3.dist-info → monocle_apptrace-0.3.0b5.dist-info}/WHEEL +0 -0
  37. {monocle_apptrace-0.3.0b3.dist-info → monocle_apptrace-0.3.0b5.dist-info}/licenses/LICENSE +0 -0
  38. {monocle_apptrace-0.3.0b3.dist-info → monocle_apptrace-0.3.0b5.dist-info}/licenses/NOTICE +0 -0
@@ -0,0 +1,19 @@
1
+ import sys, os
2
+ import runpy
3
+ from monocle_apptrace.instrumentation.common.instrumentor import setup_monocle_telemetry
4
+
5
+ def main():
6
+ if len(sys.argv) < 2 or not sys.argv[1].endswith(".py"):
7
+ print("Usage: python -m monocle_apptrace <your-main-module-file> <args>")
8
+ sys.exit(1)
9
+ file_name = os.path.basename(sys.argv[1])
10
+ workflow_name = file_name[:-3]
11
+ setup_monocle_telemetry(workflow_name=workflow_name)
12
+ sys.argv.pop(0)
13
+
14
+ try:
15
+ runpy.run_path(path_name=sys.argv[0], run_name="__main__")
16
+ except Exception as e:
17
+ print(e)
18
+ if __name__ == "__main__":
19
+ main()
@@ -28,14 +28,22 @@ class S3SpanExporter(SpanExporterBase):
28
28
  DEFAULT_TIME_FORMAT = "%Y-%m-%d__%H.%M.%S"
29
29
  self.max_batch_size = 500
30
30
  self.export_interval = 1
31
- self.s3_client = boto3.client(
32
- 's3',
33
- aws_access_key_id=os.getenv('AWS_ACCESS_KEY_ID'),
34
- aws_secret_access_key=os.getenv('AWS_SECRET_ACCESS_KEY'),
35
- region_name=region_name,
36
- )
31
+ if(os.getenv('MONOCLE_AWS_ACCESS_KEY_ID') and os.getenv('MONOCLE_AWS_SECRET_ACCESS_KEY')):
32
+ self.s3_client = boto3.client(
33
+ 's3',
34
+ aws_access_key_id=os.getenv('MONOCLE_AWS_ACCESS_KEY_ID'),
35
+ aws_secret_access_key=os.getenv('MONOCLE_AWS_SECRET_ACCESS_KEY'),
36
+ region_name=region_name,
37
+ )
38
+ else:
39
+ self.s3_client = boto3.client(
40
+ 's3',
41
+ aws_access_key_id=os.getenv('AWS_ACCESS_KEY_ID'),
42
+ aws_secret_access_key=os.getenv('AWS_SECRET_ACCESS_KEY'),
43
+ region_name=region_name,
44
+ )
37
45
  self.bucket_name = bucket_name or os.getenv('MONOCLE_S3_BUCKET_NAME','default-bucket')
38
- self.file_prefix = DEFAULT_FILE_PREFIX
46
+ self.file_prefix = os.getenv('MONOCLE_S3_KEY_PREFIX', DEFAULT_FILE_PREFIX)
39
47
  self.time_format = DEFAULT_TIME_FORMAT
40
48
  self.export_queue = []
41
49
  self.last_export_time = time.time()
@@ -142,7 +150,8 @@ class S3SpanExporter(SpanExporterBase):
142
150
  @SpanExporterBase.retry_with_backoff(exceptions=(EndpointConnectionError, ConnectionClosedError, ReadTimeoutError, ConnectTimeoutError))
143
151
  def __upload_to_s3(self, span_data_batch: str):
144
152
  current_time = datetime.datetime.now().strftime(self.time_format)
145
- file_name = f"{self.file_prefix}{current_time}.ndjson"
153
+ prefix = self.file_prefix + os.environ.get('MONOCLE_S3_KEY_PREFIX_CURRENT', '')
154
+ file_name = f"{prefix}{current_time}.ndjson"
146
155
  self.s3_client.put_object(
147
156
  Bucket=self.bucket_name,
148
157
  Key=file_name,
@@ -1,9 +1,10 @@
1
1
  from typing import Dict, Any, List
2
2
  import os
3
- import warnings
3
+ import logging
4
4
  from importlib import import_module
5
5
  from opentelemetry.sdk.trace.export import SpanExporter, ConsoleSpanExporter
6
6
  from monocle_apptrace.exporters.file_exporter import FileSpanExporter
7
+ logger = logging.getLogger(__name__)
7
8
 
8
9
  monocle_exporters: Dict[str, Any] = {
9
10
  "s3": {"module": "monocle_apptrace.exporters.aws.s3_exporter", "class": "S3SpanExporter"},
@@ -25,21 +26,21 @@ def get_monocle_exporter() -> List[SpanExporter]:
25
26
  try:
26
27
  exporter_class_path = monocle_exporters[exporter_name]
27
28
  except KeyError:
28
- warnings.warn(f"Unsupported Monocle span exporter '{exporter_name}', skipping.")
29
+ logger.debug(f"Unsupported Monocle span exporter '{exporter_name}', skipping.")
29
30
  continue
30
31
  try:
31
32
  exporter_module = import_module(exporter_class_path["module"])
32
33
  exporter_class = getattr(exporter_module, exporter_class_path["class"])
33
34
  exporters.append(exporter_class())
34
35
  except Exception as ex:
35
- warnings.warn(
36
+ logger.debug(
36
37
  f"Unable to initialize Monocle span exporter '{exporter_name}', error: {ex}. Using ConsoleSpanExporter as a fallback.")
37
38
  exporters.append(ConsoleSpanExporter())
38
39
  continue
39
40
 
40
41
  # If no exporters were created, default to FileSpanExporter
41
42
  if not exporters:
42
- warnings.warn("No valid Monocle span exporters configured. Defaulting to FileSpanExporter.")
43
+ logger.debug("No valid Monocle span exporters configured. Defaulting to FileSpanExporter.")
43
44
  exporters.append(FileSpanExporter())
44
45
 
45
46
  return exporters
@@ -37,6 +37,7 @@ service_name_map = {
37
37
  GITHUB_CODESPACE_SERVICE_NAME: GITHUB_CODESPACE_IDENTIFIER_ENV_NAME
38
38
  }
39
39
 
40
+ MONOCLE_INSTRUMENTOR = "monocle_apptrace"
40
41
  WORKFLOW_TYPE_KEY = "workflow_type"
41
42
  DATA_INPUT_KEY = "data.input"
42
43
  DATA_OUTPUT_KEY = "data.output"
@@ -47,3 +48,7 @@ RESPONSE = "response"
47
48
  SESSION_PROPERTIES_KEY = "session"
48
49
  INFRA_SERVICE_KEY = "infra_service_name"
49
50
  META_DATA = 'metadata'
51
+ MONOCLE_SCOPE_NAME_PREFIX = "monocle.scope."
52
+ SCOPE_METHOD_LIST = 'MONOCLE_SCOPE_METHODS'
53
+ SCOPE_METHOD_FILE = 'monocle_scopes.json'
54
+ SCOPE_CONFIG_PATH = 'MONOCLE_SCOPE_CONFIG_PATH'
@@ -3,6 +3,7 @@ from typing import Collection, Dict, List, Union
3
3
  import random
4
4
  import uuid
5
5
  from opentelemetry import trace
6
+ from contextlib import contextmanager
6
7
  from opentelemetry.context import attach, get_value, set_value, get_current, detach
7
8
  from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
8
9
  from opentelemetry.instrumentation.utils import unwrap
@@ -19,8 +20,14 @@ from monocle_apptrace.instrumentation.common.span_handler import SpanHandler
19
20
  from monocle_apptrace.instrumentation.common.wrapper_method import (
20
21
  DEFAULT_METHODS_LIST,
21
22
  WrapperMethod,
23
+ MONOCLE_SPAN_HANDLERS
22
24
  )
23
-
25
+ from monocle_apptrace.instrumentation.common.wrapper import scope_wrapper
26
+ from monocle_apptrace.instrumentation.common.utils import (
27
+ set_scope, remove_scope, http_route_handler, load_scopes
28
+ )
29
+ from monocle_apptrace.instrumentation.common.constants import MONOCLE_INSTRUMENTOR
30
+ from functools import wraps
24
31
  logger = logging.getLogger(__name__)
25
32
 
26
33
  SESSION_PROPERTIES_KEY = "session"
@@ -29,8 +36,6 @@ _instruments = ()
29
36
 
30
37
  monocle_tracer_provider: TracerProvider = None
31
38
 
32
- MONOCLE_INSTRUMENTOR = "monocle_apptrace"
33
-
34
39
  class MonocleInstrumentor(BaseInstrumentor):
35
40
  workflow_name: str = ""
36
41
  user_wrapper_methods: list[Union[dict,WrapperMethod]] = []
@@ -45,17 +50,36 @@ class MonocleInstrumentor(BaseInstrumentor):
45
50
  union_with_default_methods: bool = True
46
51
  ) -> None:
47
52
  self.user_wrapper_methods = user_wrapper_methods or []
48
- self.handlers = handlers or {'default':SpanHandler()}
53
+ self.handlers = handlers
54
+ if self.handlers is not None:
55
+ for key, val in MONOCLE_SPAN_HANDLERS.items():
56
+ if key not in self.handlers:
57
+ self.handlers[key] = val
58
+ else:
59
+ self.handlers = MONOCLE_SPAN_HANDLERS
49
60
  self.union_with_default_methods = union_with_default_methods
50
61
  super().__init__()
51
62
 
63
+ def get_instrumentor(self, tracer):
64
+ def instrumented_endpoint_invoke(to_wrap,wrapped, span_name, instance,fn):
65
+ @wraps(fn)
66
+ def with_instrumentation(*args, **kwargs):
67
+ handler = SpanHandler()
68
+ with tracer.start_as_current_span(span_name) as span:
69
+ response = fn(*args, **kwargs)
70
+ handler.hydrate_span(to_wrap, span=span, wrapped=wrapped, instance=instance, args=args, kwargs=kwargs,
71
+ result=response)
72
+ return response
73
+
74
+ return with_instrumentation
75
+ return instrumented_endpoint_invoke
76
+
52
77
  def instrumentation_dependencies(self) -> Collection[str]:
53
78
  return _instruments
54
79
 
55
80
  def _instrument(self, **kwargs):
56
81
  tracer_provider: TracerProvider = kwargs.get("tracer_provider")
57
- global monocle_tracer_provider
58
- monocle_tracer_provider = tracer_provider
82
+ set_tracer_provider(tracer_provider)
59
83
  tracer = get_tracer(instrumenting_module_name=MONOCLE_INSTRUMENTOR, tracer_provider=tracer_provider)
60
84
 
61
85
  final_method_list = []
@@ -67,6 +91,10 @@ class MonocleInstrumentor(BaseInstrumentor):
67
91
  final_method_list.append(method)
68
92
  elif isinstance(method, WrapperMethod):
69
93
  final_method_list.append(method.to_dict())
94
+
95
+ for method in load_scopes():
96
+ method['wrapper_method'] = scope_wrapper
97
+ final_method_list.append(method)
70
98
 
71
99
  for method_config in final_method_list:
72
100
  target_package = method_config.get("package", None)
@@ -77,6 +105,10 @@ class MonocleInstrumentor(BaseInstrumentor):
77
105
  handler_key = method_config.get("span_handler",'default')
78
106
  try:
79
107
  handler = self.handlers.get(handler_key)
108
+ if not handler:
109
+ logger.warning("incorrect or empty handler falling back to default handler")
110
+ handler = self.handlers.get('default')
111
+ handler.set_instrumentor(self.get_instrumentor(tracer))
80
112
  wrap_function_wrapper(
81
113
  target_package,
82
114
  f"{target_object}.{target_method}" if target_object else target_method,
@@ -108,6 +140,14 @@ class MonocleInstrumentor(BaseInstrumentor):
108
140
  object:{wrap_object},
109
141
  method:{wrap_method}""")
110
142
 
143
+ def set_tracer_provider(tracer_provider: TracerProvider):
144
+ global monocle_tracer_provider
145
+ monocle_tracer_provider = tracer_provider
146
+
147
+ def get_tracer_provider() -> TracerProvider:
148
+ global monocle_tracer_provider
149
+ return monocle_tracer_provider
150
+
111
151
  def setup_monocle_telemetry(
112
152
  workflow_name: str,
113
153
  span_processors: List[SpanProcessor] = None,
@@ -119,7 +159,7 @@ def setup_monocle_telemetry(
119
159
  })
120
160
  exporters = get_monocle_exporter()
121
161
  span_processors = span_processors or [BatchSpanProcessor(exporter) for exporter in exporters]
122
- trace_provider = TracerProvider(resource=resource)
162
+ set_tracer_provider(TracerProvider(resource=resource))
123
163
  attach(set_value("workflow_name", workflow_name))
124
164
  tracer_provider_default = trace.get_tracer_provider()
125
165
  provider_type = type(tracer_provider_default).__name__
@@ -129,14 +169,14 @@ def setup_monocle_telemetry(
129
169
  if not is_proxy_provider:
130
170
  tracer_provider_default.add_span_processor(processor)
131
171
  else:
132
- trace_provider.add_span_processor(processor)
172
+ get_tracer_provider().add_span_processor(processor)
133
173
  if is_proxy_provider:
134
- trace.set_tracer_provider(trace_provider)
174
+ trace.set_tracer_provider(get_tracer_provider())
135
175
  instrumentor = MonocleInstrumentor(user_wrapper_methods=wrapper_methods or [],
136
176
  handlers=span_handlers, union_with_default_methods = union_with_default_methods)
137
177
  # instrumentor.app_name = workflow_name
138
178
  if not instrumentor.is_instrumented_by_opentelemetry:
139
- instrumentor.instrument(trace_provider=trace_provider)
179
+ instrumentor.instrument(trace_provider=get_tracer_provider())
140
180
 
141
181
  return instrumentor
142
182
 
@@ -156,7 +196,7 @@ def propagate_trace_id(traceId = "", use_trace_context = False):
156
196
  try:
157
197
  if traceId.startswith("0x"):
158
198
  traceId = traceId.lstrip("0x")
159
- tracer = get_tracer(instrumenting_module_name= MONOCLE_INSTRUMENTOR, tracer_provider= monocle_tracer_provider)
199
+ tracer = get_tracer(instrumenting_module_name= MONOCLE_INSTRUMENTOR, tracer_provider= get_tracer_provider())
160
200
  initial_id_generator = tracer.id_generator
161
201
  _parent_span_context = get_current() if use_trace_context else None
162
202
  if traceId and is_valid_trace_id_uuid(traceId):
@@ -194,6 +234,37 @@ def is_valid_trace_id_uuid(traceId: str) -> bool:
194
234
  pass
195
235
  return False
196
236
 
237
+ def start_scope(scope_name: str, scope_value:str = None) -> object:
238
+ return set_scope(scope_name, scope_value)
239
+
240
+ def stop_scope(token:object) -> None:
241
+ remove_scope(token)
242
+ return
243
+
244
+ @contextmanager
245
+ def monocle_trace_scope(scope_name: str, scope_value:str = None):
246
+ token = start_scope(scope_name, scope_value)
247
+ try:
248
+ yield
249
+ finally:
250
+ stop_scope(token)
251
+
252
+ def monocle_trace_scope_method(scope_name: str):
253
+ def decorator(func):
254
+ def wrapper(*args, **kwargs):
255
+ token = start_scope(scope_name)
256
+ try:
257
+ result = func(*args, **kwargs)
258
+ return result
259
+ finally:
260
+ stop_scope(token)
261
+ return wrapper
262
+ return decorator
263
+
264
+ def monocle_trace_http_route(func):
265
+ def wrapper(req):
266
+ return http_route_handler(req.headers, func, req)
267
+ return wrapper
197
268
 
198
269
  class FixedIdGenerator(id_generator.IdGenerator):
199
270
  def __init__(
@@ -10,7 +10,7 @@ from monocle_apptrace.instrumentation.common.constants import (
10
10
  service_name_map,
11
11
  service_type_map,
12
12
  )
13
- from monocle_apptrace.instrumentation.common.utils import set_attribute
13
+ from monocle_apptrace.instrumentation.common.utils import set_attribute, get_scopes
14
14
 
15
15
  logger = logging.getLogger(__name__)
16
16
 
@@ -24,10 +24,25 @@ WORKFLOW_TYPE_MAP = {
24
24
 
25
25
  class SpanHandler:
26
26
 
27
+ def __init__(self,instrumentor=None):
28
+ self.instrumentor=instrumentor
29
+
30
+ def set_instrumentor(self, instrumentor):
31
+ self.instrumentor = instrumentor
32
+
27
33
  def validate(self, to_wrap, wrapped, instance, args, kwargs):
28
34
  pass
29
35
 
30
- def pre_task_processing(self, to_wrap, wrapped, instance, args, span):
36
+ def pre_tracing(self, to_wrap, wrapped, instance, args, kwargs):
37
+ pass
38
+
39
+ def post_tracing(self, to_wrap, wrapped, instance, args, kwargs, return_value):
40
+ pass
41
+
42
+ def skip_span(self, to_wrap, wrapped, instance, args, kwargs) -> bool:
43
+ return False
44
+
45
+ def pre_task_processing(self, to_wrap, wrapped, instance, args,kwargs, span):
31
46
  if self.__is_root_span(span):
32
47
  try:
33
48
  sdk_version = version("monocle_apptrace")
@@ -37,14 +52,9 @@ class SpanHandler:
37
52
  if "pipeline" in to_wrap['package']:
38
53
  set_attribute(QUERY, args[0]['prompt_builder']['question'])
39
54
 
40
-
41
-
42
55
  def post_task_processing(self, to_wrap, wrapped, instance, args, kwargs, result, span):
43
56
  pass
44
57
 
45
- def set_context_properties(self, to_wrap, wrapped, instance, args, kwargs):
46
- pass
47
-
48
58
  def hydrate_span(self, to_wrap, wrapped, instance, args, kwargs, result, span):
49
59
  self.hydrate_attributes(to_wrap, wrapped, instance, args, kwargs, result, span)
50
60
  self.hydrate_events(to_wrap, wrapped, instance, args, kwargs, result, span)
@@ -72,16 +82,21 @@ class SpanHandler:
72
82
  try:
73
83
  arguments = {"instance":instance, "args":args, "kwargs":kwargs, "result":result}
74
84
  result = accessor(arguments)
75
- if result and isinstance(result, str):
85
+ if result and isinstance(result, (str, list)):
76
86
  span.set_attribute(attribute_name, result)
77
87
  except Exception as e:
78
- logger.error(f"Error processing accessor: {e}")
88
+ logger.debug(f"Error processing accessor: {e}")
79
89
  else:
80
- logger.warning(f"{' and '.join([key for key in ['attribute', 'accessor'] if not processor.get(key)])} not found or incorrect in entity JSON")
90
+ logger.debug(f"{' and '.join([key for key in ['attribute', 'accessor'] if not processor.get(key)])} not found or incorrect in entity JSON")
81
91
  span_index += 1
82
92
  else:
83
- logger.warning("attributes not found or incorrect written in entity json")
93
+ logger.debug("attributes not found or incorrect written in entity json")
84
94
 
95
+ # set scopes as attributes by calling get_scopes()
96
+ # scopes is a Mapping[str:object], iterate directly with .items()
97
+ for scope_key, scope_value in get_scopes().items():
98
+ span.set_attribute(f"scope.{scope_key}", scope_value)
99
+
85
100
  if span_index > 0:
86
101
  span.set_attribute("entity.count", span_index)
87
102
 
@@ -106,7 +121,7 @@ class SpanHandler:
106
121
  else:
107
122
  event_attributes.update(accessor(arguments))
108
123
  except Exception as e:
109
- logger.error(f"Error evaluating accessor for attribute '{attribute_key}': {e}")
124
+ logger.debug(f"Error evaluating accessor for attribute '{attribute_key}': {e}")
110
125
  span.add_event(name=event_name, attributes=event_attributes)
111
126
 
112
127
 
@@ -1,16 +1,33 @@
1
- import logging
2
- from typing import Callable, Generic, Optional, TypeVar
1
+ import logging, json
2
+ import os
3
+ from typing import Callable, Generic, Optional, TypeVar, Mapping
4
+ from threading import local
3
5
 
4
- from opentelemetry.context import attach, detach, get_current, get_value, set_value
5
- from opentelemetry.trace import NonRecordingSpan, Span
6
+ from opentelemetry.context import attach, detach, get_current, get_value, set_value, Context
7
+ from opentelemetry.trace import NonRecordingSpan, Span, get_tracer
6
8
  from opentelemetry.trace.propagation import _SPAN_KEY
9
+ from opentelemetry.sdk.trace import id_generator, TracerProvider
10
+ from opentelemetry.propagate import inject, extract
11
+ from opentelemetry import baggage
12
+ from monocle_apptrace.instrumentation.common.constants import MONOCLE_SCOPE_NAME_PREFIX, SCOPE_METHOD_FILE, SCOPE_CONFIG_PATH
7
13
 
8
14
  T = TypeVar('T')
9
15
  U = TypeVar('U')
10
16
 
11
17
  logger = logging.getLogger(__name__)
12
18
 
19
+ monocle_tracer_provider: TracerProvider = None
13
20
  embedding_model_context = {}
21
+ scope_id_generator = id_generator.RandomIdGenerator()
22
+ http_scopes:dict[str:str] = {}
23
+
24
+ def set_tracer_provider(tracer_provider: TracerProvider):
25
+ global monocle_tracer_provider
26
+ monocle_tracer_provider = tracer_provider
27
+
28
+ def get_tracer_provider() -> TracerProvider:
29
+ global monocle_tracer_provider
30
+ return monocle_tracer_provider
14
31
 
15
32
  def set_span_attribute(span, name, value):
16
33
  if value is not None:
@@ -139,6 +156,89 @@ def get_nested_value(data, keys):
139
156
  def get_keys_as_tuple(dictionary, *keys):
140
157
  return tuple(next((value for key, value in dictionary.items() if key.endswith(k) and value is not None), None) for k in keys)
141
158
 
159
+ def load_scopes() -> dict:
160
+ methods_data = []
161
+ scope_methods = []
162
+ try:
163
+ scope_config_file_path = os.environ.get(SCOPE_CONFIG_PATH,
164
+ os.path.join(os.getcwd(), SCOPE_METHOD_FILE))
165
+ with open(scope_config_file_path) as f:
166
+ methods_data = json.load(f)
167
+ for method in methods_data:
168
+ if method.get('http_header'):
169
+ http_scopes[method.get('http_header')] = method.get('scope_name')
170
+ else:
171
+ scope_methods.append(method)
172
+ except Exception as e:
173
+ logger.debug(f"Error loading scope methods from file: {e}")
174
+ return scope_methods
175
+
176
+ def __generate_scope_id() -> str:
177
+ global scope_id_generator
178
+ return f"{hex(scope_id_generator.generate_trace_id())}"
179
+
180
+ def set_scope(scope_name: str, scope_value:str = None) -> object:
181
+ return set_scopes({scope_name: scope_value})
182
+
183
+ def set_scopes(scopes:dict[str, object], baggage_context:Context = None) -> object:
184
+ if baggage_context is None:
185
+ baggage_context:Context = get_current()
186
+ for scope_name, scope_value in scopes.items():
187
+ if scope_value is None:
188
+ scope_value = __generate_scope_id()
189
+ baggage_context = baggage.set_baggage(f"{MONOCLE_SCOPE_NAME_PREFIX}{scope_name}", scope_value, baggage_context)
190
+
191
+ token:object = attach(baggage_context)
192
+ return token
193
+
194
+ def remove_scope(token:object) -> None:
195
+ remove_scopes(token)
196
+
197
+ def remove_scopes(token:object) -> None:
198
+ if token is not None:
199
+ detach(token)
200
+
201
+ def get_scopes() -> dict[str, object]:
202
+ monocle_scopes:dict[str, object] = {}
203
+ for key, val in baggage.get_all().items():
204
+ if key.startswith(MONOCLE_SCOPE_NAME_PREFIX):
205
+ monocle_scopes[key[len(MONOCLE_SCOPE_NAME_PREFIX):]] = val
206
+ return monocle_scopes
207
+
208
+ def get_baggage_for_scopes():
209
+ baggage_context:Context = None
210
+ for scope_key, scope_value in get_scopes():
211
+ monocle_scope_name = f"{MONOCLE_SCOPE_NAME_PREFIX}{scope_key}"
212
+ baggage_context = baggage.set_baggage(monocle_scope_name, scope_value, context=baggage_context)
213
+ return baggage_context
214
+
215
+ def set_scopes_from_baggage(baggage_context:Context):
216
+ for scope_key, scope_value in baggage.get_all(baggage_context):
217
+ if scope_key.startswith(MONOCLE_SCOPE_NAME_PREFIX):
218
+ scope_name = scope_key[len(MONOCLE_SCOPE_NAME_PREFIX):]
219
+ set_scope(scope_name, scope_value)
220
+
221
+ def extract_http_headers(headers) -> object:
222
+ global http_scopes
223
+ trace_context:Context = extract(headers, context=get_current())
224
+ imported_scope:dict[str, object] = {}
225
+ for http_header, http_scope in http_scopes.items():
226
+ if http_header in headers:
227
+ imported_scope[http_scope] = f"{http_header}: {headers[http_header]}"
228
+ token = set_scopes(imported_scope, trace_context)
229
+ return token
230
+
231
+ def clear_http_scopes(token:object) -> None:
232
+ global http_scopes
233
+ remove_scopes(token)
234
+
235
+ def http_route_handler(headers, func, req):
236
+ token = extract_http_headers(headers)
237
+ try:
238
+ result = func(req)
239
+ return result
240
+ finally:
241
+ clear_http_scopes(token)
142
242
 
143
243
  class Option(Generic[T]):
144
244
  def __init__(self, value: Optional[T]):
@@ -168,4 +268,11 @@ def try_option(func: Callable[..., T], *args, **kwargs) -> Option[T]:
168
268
  try:
169
269
  return Option(func(*args, **kwargs))
170
270
  except Exception:
171
- return Option(None)
271
+ return Option(None)
272
+
273
+ def resolve_from_alias(my_map, alias):
274
+ """Find a alias that is not none from list of aliases"""
275
+ for i in alias:
276
+ if i in my_map.keys():
277
+ return my_map[i]
278
+ return None
@@ -7,8 +7,9 @@ from monocle_apptrace.instrumentation.common.span_handler import SpanHandler
7
7
  from monocle_apptrace.instrumentation.common.utils import (
8
8
  get_fully_qualified_class_name,
9
9
  with_tracer_wrapper,
10
+ set_scope,
11
+ remove_scope
10
12
  )
11
- from monocle_apptrace.instrumentation.metamodel.botocore import _helper
12
13
  logger = logging.getLogger(__name__)
13
14
 
14
15
 
@@ -26,22 +27,20 @@ def task_wrapper(tracer: Tracer, handler: SpanHandler, to_wrap, wrapped, instanc
26
27
  else:
27
28
  name = get_fully_qualified_class_name(instance)
28
29
 
29
- handler.validate(to_wrap, wrapped, instance, args, kwargs)
30
- handler.set_context_properties(to_wrap, wrapped, instance, args, kwargs)
31
-
32
- if to_wrap.get('skip_span'):
33
- return_value = wrapped(*args, **kwargs)
34
- _helper.botocore_processor(tracer, to_wrap, wrapped, instance, args, kwargs, return_value)
30
+ return_value = None
31
+ try:
32
+ handler.pre_tracing(to_wrap, wrapped, instance, args, kwargs)
33
+ if to_wrap.get('skip_span') or handler.skip_span(to_wrap, wrapped, instance, args, kwargs):
34
+ return_value = wrapped(*args, **kwargs)
35
+ else:
36
+ with tracer.start_as_current_span(name) as span:
37
+ handler.pre_task_processing(to_wrap, wrapped, instance, args, kwargs, span)
38
+ return_value = wrapped(*args, **kwargs)
39
+ handler.hydrate_span(to_wrap, wrapped, instance, args, kwargs, return_value, span)
40
+ handler.post_task_processing(to_wrap, wrapped, instance, args, kwargs, return_value, span)
35
41
  return return_value
36
-
37
- with tracer.start_as_current_span(name) as span:
38
- handler.pre_task_processing(to_wrap, wrapped, instance, args, span)
39
- return_value = wrapped(*args, **kwargs)
40
- handler.hydrate_span(to_wrap, wrapped, instance, args, kwargs, return_value, span)
41
- handler.post_task_processing(to_wrap, wrapped, instance, args, kwargs, return_value, span)
42
-
43
- return return_value
44
-
42
+ finally:
43
+ handler.post_tracing(to_wrap, wrapped, instance, args, kwargs, return_value)
45
44
 
46
45
  @with_tracer_wrapper
47
46
  async def atask_wrapper(tracer: Tracer, handler: SpanHandler, to_wrap, wrapped, instance, args, kwargs):
@@ -58,12 +57,38 @@ async def atask_wrapper(tracer: Tracer, handler: SpanHandler, to_wrap, wrapped,
58
57
  else:
59
58
  name = get_fully_qualified_class_name(instance)
60
59
 
61
- handler.validate(to_wrap, wrapped, instance, args, kwargs)
62
- handler.set_context_properties(to_wrap, wrapped, instance, args, kwargs)
63
- with tracer.start_as_current_span(name) as span:
64
- handler.pre_task_processing(to_wrap, wrapped, instance, args, span)
65
- return_value = wrapped(*args, **kwargs)
66
- handler.hydrate_span(to_wrap, wrapped, instance, args, kwargs, return_value, span)
67
- handler.post_task_processing(to_wrap, wrapped, instance, args, kwargs, return_value, span)
60
+ return_value = None
61
+ try:
62
+ handler.pre_tracing(to_wrap, wrapped, instance, args, kwargs)
63
+ if to_wrap.get('skip_span') or handler.skip_span(to_wrap, wrapped, instance, args, kwargs):
64
+ return_value = wrapped(*args, **kwargs)
65
+ else:
66
+ with tracer.start_as_current_span(name) as span:
67
+ handler.pre_task_processing(to_wrap, wrapped, instance, args, kwargs, span)
68
+ return_value = wrapped(*args, **kwargs)
69
+ handler.hydrate_span(to_wrap, wrapped, instance, args, kwargs, return_value, span)
70
+ handler.post_task_processing(to_wrap, wrapped, instance, args, kwargs, return_value, span)
71
+ return return_value
72
+ finally:
73
+ handler.post_tracing(to_wrap, wrapped, instance, args, kwargs, return_value)
68
74
 
75
+ @with_tracer_wrapper
76
+ def scope_wrapper(tracer: Tracer, handler: SpanHandler, to_wrap, wrapped, instance, args, kwargs):
77
+ scope_name = to_wrap.get('scope_name', None)
78
+ if scope_name:
79
+ token = set_scope(scope_name)
80
+ return_value = wrapped(*args, **kwargs)
81
+ if scope_name:
82
+ remove_scope(token)
69
83
  return return_value
84
+
85
+
86
+ @with_tracer_wrapper
87
+ async def ascope_wrapper(tracer: Tracer, handler: SpanHandler, to_wrap, wrapped, instance, args, kwargs):
88
+ scope_name = to_wrap.get('scope_name', None)
89
+ if scope_name:
90
+ token = set_scope(scope_name)
91
+ return_value = wrapped(*args, **kwargs)
92
+ if scope_name:
93
+ remove_scope(token)
94
+ return return_value