monocle-apptrace 0.3.0b4__py3-none-any.whl → 0.3.0b6__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 (45) hide show
  1. monocle_apptrace/__main__.py +19 -0
  2. monocle_apptrace/exporters/azure/blob_exporter.py +7 -0
  3. monocle_apptrace/exporters/monocle_exporters.py +5 -4
  4. monocle_apptrace/exporters/okahu/okahu_exporter.py +1 -1
  5. monocle_apptrace/instrumentation/common/constants.py +22 -0
  6. monocle_apptrace/instrumentation/common/instrumentor.py +119 -39
  7. monocle_apptrace/instrumentation/common/span_handler.py +103 -44
  8. monocle_apptrace/instrumentation/common/utils.py +161 -5
  9. monocle_apptrace/instrumentation/common/wrapper.py +58 -37
  10. monocle_apptrace/instrumentation/common/wrapper_method.py +34 -7
  11. monocle_apptrace/instrumentation/metamodel/botocore/_helper.py +0 -31
  12. monocle_apptrace/instrumentation/metamodel/botocore/handlers/botocore_span_handler.py +25 -0
  13. monocle_apptrace/instrumentation/metamodel/botocore/methods.py +6 -6
  14. monocle_apptrace/instrumentation/metamodel/flask/__init__.py +0 -0
  15. monocle_apptrace/instrumentation/metamodel/flask/_helper.py +29 -0
  16. monocle_apptrace/instrumentation/metamodel/flask/methods.py +13 -0
  17. monocle_apptrace/instrumentation/metamodel/haystack/entities/inference.py +1 -1
  18. monocle_apptrace/instrumentation/metamodel/haystack/methods.py +2 -1
  19. monocle_apptrace/instrumentation/metamodel/langchain/_helper.py +4 -0
  20. monocle_apptrace/instrumentation/metamodel/langchain/entities/inference.py +3 -2
  21. monocle_apptrace/instrumentation/metamodel/langchain/methods.py +12 -6
  22. monocle_apptrace/instrumentation/metamodel/langgraph/__init__.py +0 -0
  23. monocle_apptrace/instrumentation/metamodel/langgraph/_helper.py +48 -0
  24. monocle_apptrace/instrumentation/metamodel/langgraph/entities/__init__.py +0 -0
  25. monocle_apptrace/instrumentation/metamodel/langgraph/entities/inference.py +56 -0
  26. monocle_apptrace/instrumentation/metamodel/langgraph/methods.py +14 -0
  27. monocle_apptrace/instrumentation/metamodel/llamaindex/_helper.py +37 -19
  28. monocle_apptrace/instrumentation/metamodel/llamaindex/entities/agent.py +47 -0
  29. monocle_apptrace/instrumentation/metamodel/llamaindex/entities/inference.py +5 -3
  30. monocle_apptrace/instrumentation/metamodel/llamaindex/methods.py +15 -3
  31. monocle_apptrace/instrumentation/metamodel/openai/__init__.py +0 -0
  32. monocle_apptrace/instrumentation/metamodel/openai/_helper.py +112 -0
  33. monocle_apptrace/instrumentation/metamodel/openai/entities/__init__.py +0 -0
  34. monocle_apptrace/instrumentation/metamodel/openai/entities/inference.py +71 -0
  35. monocle_apptrace/instrumentation/metamodel/openai/entities/retrieval.py +43 -0
  36. monocle_apptrace/instrumentation/metamodel/openai/methods.py +45 -0
  37. monocle_apptrace/instrumentation/metamodel/requests/__init__.py +4 -0
  38. monocle_apptrace/instrumentation/metamodel/requests/_helper.py +31 -0
  39. monocle_apptrace/instrumentation/metamodel/requests/methods.py +12 -0
  40. {monocle_apptrace-0.3.0b4.dist-info → monocle_apptrace-0.3.0b6.dist-info}/METADATA +2 -1
  41. monocle_apptrace-0.3.0b6.dist-info/RECORD +68 -0
  42. monocle_apptrace-0.3.0b4.dist-info/RECORD +0 -48
  43. {monocle_apptrace-0.3.0b4.dist-info → monocle_apptrace-0.3.0b6.dist-info}/WHEEL +0 -0
  44. {monocle_apptrace-0.3.0b4.dist-info → monocle_apptrace-0.3.0b6.dist-info}/licenses/LICENSE +0 -0
  45. {monocle_apptrace-0.3.0b4.dist-info → monocle_apptrace-0.3.0b6.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()
@@ -11,6 +11,7 @@ from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
11
11
  from typing import Sequence
12
12
  from monocle_apptrace.exporters.base_exporter import SpanExporterBase
13
13
  import json
14
+ from monocle_apptrace.instrumentation.common.constants import MONOCLE_SDK_VERSION
14
15
  logger = logging.getLogger(__name__)
15
16
 
16
17
  class AzureBlobSpanExporter(SpanExporterBase):
@@ -72,6 +73,12 @@ class AzureBlobSpanExporter(SpanExporterBase):
72
73
  """The actual async export logic is run here."""
73
74
  # Add spans to the export queue
74
75
  for span in spans:
76
+ # Azure blob library has a check to generate it's own span if OpenTelemetry is loaded and Azure trace package is installed (just pip install azure-trace-opentelemetry)
77
+ # With Monocle,OpenTelemetry is always loaded. If the Azure trace package is installed, then it triggers the blob trace generation on every blob operation.
78
+ # Thus, the Monocle span write ends up generating a blob span which again comes back to the exporter .. and would result in an infinite loop.
79
+ # To avoid this, we check if the span has the Monocle SDK version attribute and skip it if it doesn't. That way the blob span genearted by Azure library are not exported.
80
+ if not span.attributes.get(MONOCLE_SDK_VERSION):
81
+ continue # TODO: All exporters to use same base class and check it there
75
82
  self.export_queue.append(span)
76
83
  if len(self.export_queue) >= self.max_batch_size:
77
84
  await self.__export_spans()
@@ -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
@@ -83,7 +83,7 @@ class OkahuSpanExporter(SpanExporter):
83
83
  result.text,
84
84
  )
85
85
  return SpanExportResult.FAILURE
86
- logger.warning("spans successfully exported to okahu")
86
+ logger.debug("spans successfully exported to okahu")
87
87
  return SpanExportResult.SUCCESS
88
88
  except ReadTimeout as e:
89
89
  logger.warning("Trace export timed out: %s", str(e))
@@ -37,6 +37,20 @@ service_name_map = {
37
37
  GITHUB_CODESPACE_SERVICE_NAME: GITHUB_CODESPACE_IDENTIFIER_ENV_NAME
38
38
  }
39
39
 
40
+
41
+ llm_type_map = {
42
+ "sagemakerendpoint": "aws_sagemaker",
43
+ "azureopenai": "azure_openai",
44
+ "openai": "openai",
45
+ "chatopenai": "openai",
46
+ "azurechatopenai": "azure_openai",
47
+ "bedrock": "aws_bedrock",
48
+ "sagemakerllm": "aws_sagemaker",
49
+ "chatbedrock": "aws_bedrock",
50
+ "openaigenerator": "openai",
51
+ }
52
+
53
+ MONOCLE_INSTRUMENTOR = "monocle_apptrace"
40
54
  WORKFLOW_TYPE_KEY = "workflow_type"
41
55
  DATA_INPUT_KEY = "data.input"
42
56
  DATA_OUTPUT_KEY = "data.output"
@@ -47,3 +61,11 @@ RESPONSE = "response"
47
61
  SESSION_PROPERTIES_KEY = "session"
48
62
  INFRA_SERVICE_KEY = "infra_service_name"
49
63
  META_DATA = 'metadata'
64
+ MONOCLE_SCOPE_NAME_PREFIX = "monocle.scope."
65
+ SCOPE_METHOD_LIST = 'MONOCLE_SCOPE_METHODS'
66
+ SCOPE_METHOD_FILE = 'monocle_scopes.json'
67
+ SCOPE_CONFIG_PATH = 'MONOCLE_SCOPE_CONFIG_PATH'
68
+ TRACE_PROPOGATION_URLS = "MONOCLE_TRACE_PROPAGATATION_URLS"
69
+ WORKFLOW_TYPE_KEY = "monocle.workflow_type"
70
+ WORKFLOW_TYPE_GENERIC = "workflow.generic"
71
+ MONOCLE_SDK_VERSION = "monocle_apptrace.version"
@@ -1,8 +1,10 @@
1
1
  import logging
2
+ import inspect
2
3
  from typing import Collection, Dict, List, Union
3
4
  import random
4
5
  import uuid
5
6
  from opentelemetry import trace
7
+ from contextlib import contextmanager
6
8
  from opentelemetry.context import attach, get_value, set_value, get_current, detach
7
9
  from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
8
10
  from opentelemetry.instrumentation.utils import unwrap
@@ -13,14 +15,20 @@ from opentelemetry.sdk.trace import Span, TracerProvider
13
15
  from opentelemetry.sdk.trace.export import BatchSpanProcessor, SpanProcessor
14
16
  from opentelemetry.trace import get_tracer
15
17
  from wrapt import wrap_function_wrapper
16
- from opentelemetry.trace.propagation import set_span_in_context
18
+ from opentelemetry.trace.propagation import set_span_in_context, _SPAN_KEY
17
19
  from monocle_apptrace.exporters.monocle_exporters import get_monocle_exporter
18
20
  from monocle_apptrace.instrumentation.common.span_handler import SpanHandler
19
21
  from monocle_apptrace.instrumentation.common.wrapper_method import (
20
22
  DEFAULT_METHODS_LIST,
21
23
  WrapperMethod,
24
+ MONOCLE_SPAN_HANDLERS
22
25
  )
23
-
26
+ from monocle_apptrace.instrumentation.common.wrapper import scope_wrapper, ascope_wrapper
27
+ from monocle_apptrace.instrumentation.common.utils import (
28
+ set_scope, remove_scope, http_route_handler, load_scopes, async_wrapper, http_async_route_handler
29
+ )
30
+ from monocle_apptrace.instrumentation.common.constants import MONOCLE_INSTRUMENTOR, WORKFLOW_TYPE_KEY
31
+ from functools import wraps
24
32
  logger = logging.getLogger(__name__)
25
33
 
26
34
  SESSION_PROPERTIES_KEY = "session"
@@ -29,8 +37,6 @@ _instruments = ()
29
37
 
30
38
  monocle_tracer_provider: TracerProvider = None
31
39
 
32
- MONOCLE_INSTRUMENTOR = "monocle_apptrace"
33
-
34
40
  class MonocleInstrumentor(BaseInstrumentor):
35
41
  workflow_name: str = ""
36
42
  user_wrapper_methods: list[Union[dict,WrapperMethod]] = []
@@ -45,17 +51,36 @@ class MonocleInstrumentor(BaseInstrumentor):
45
51
  union_with_default_methods: bool = True
46
52
  ) -> None:
47
53
  self.user_wrapper_methods = user_wrapper_methods or []
48
- self.handlers = handlers or {'default':SpanHandler()}
54
+ self.handlers = handlers
55
+ if self.handlers is not None:
56
+ for key, val in MONOCLE_SPAN_HANDLERS.items():
57
+ if key not in self.handlers:
58
+ self.handlers[key] = val
59
+ else:
60
+ self.handlers = MONOCLE_SPAN_HANDLERS
49
61
  self.union_with_default_methods = union_with_default_methods
50
62
  super().__init__()
51
63
 
64
+ def get_instrumentor(self, tracer):
65
+ def instrumented_endpoint_invoke(to_wrap,wrapped, span_name, instance,fn):
66
+ @wraps(fn)
67
+ def with_instrumentation(*args, **kwargs):
68
+ handler = SpanHandler()
69
+ with tracer.start_as_current_span(span_name) as span:
70
+ response = fn(*args, **kwargs)
71
+ handler.hydrate_span(to_wrap, wrapped=wrapped, instance=instance, args=args, kwargs=kwargs,
72
+ result=response, span=span)
73
+ return response
74
+
75
+ return with_instrumentation
76
+ return instrumented_endpoint_invoke
77
+
52
78
  def instrumentation_dependencies(self) -> Collection[str]:
53
79
  return _instruments
54
80
 
55
81
  def _instrument(self, **kwargs):
56
82
  tracer_provider: TracerProvider = kwargs.get("tracer_provider")
57
- global monocle_tracer_provider
58
- monocle_tracer_provider = tracer_provider
83
+ set_tracer_provider(tracer_provider)
59
84
  tracer = get_tracer(instrumenting_module_name=MONOCLE_INSTRUMENTOR, tracer_provider=tracer_provider)
60
85
 
61
86
  final_method_list = []
@@ -67,6 +92,13 @@ class MonocleInstrumentor(BaseInstrumentor):
67
92
  final_method_list.append(method)
68
93
  elif isinstance(method, WrapperMethod):
69
94
  final_method_list.append(method.to_dict())
95
+
96
+ for method in load_scopes():
97
+ if method.get('async', False):
98
+ method['wrapper_method'] = ascope_wrapper
99
+ else:
100
+ method['wrapper_method'] = scope_wrapper
101
+ final_method_list.append(method)
70
102
 
71
103
  for method_config in final_method_list:
72
104
  target_package = method_config.get("package", None)
@@ -77,6 +109,10 @@ class MonocleInstrumentor(BaseInstrumentor):
77
109
  handler_key = method_config.get("span_handler",'default')
78
110
  try:
79
111
  handler = self.handlers.get(handler_key)
112
+ if not handler:
113
+ logger.warning("incorrect or empty handler falling back to default handler")
114
+ handler = self.handlers.get('default')
115
+ handler.set_instrumentor(self.get_instrumentor(tracer))
80
116
  wrap_function_wrapper(
81
117
  target_package,
82
118
  f"{target_object}.{target_method}" if target_object else target_method,
@@ -108,6 +144,14 @@ class MonocleInstrumentor(BaseInstrumentor):
108
144
  object:{wrap_object},
109
145
  method:{wrap_method}""")
110
146
 
147
+ def set_tracer_provider(tracer_provider: TracerProvider):
148
+ global monocle_tracer_provider
149
+ monocle_tracer_provider = tracer_provider
150
+
151
+ def get_tracer_provider() -> TracerProvider:
152
+ global monocle_tracer_provider
153
+ return monocle_tracer_provider
154
+
111
155
  def setup_monocle_telemetry(
112
156
  workflow_name: str,
113
157
  span_processors: List[SpanProcessor] = None,
@@ -119,7 +163,7 @@ def setup_monocle_telemetry(
119
163
  })
120
164
  exporters = get_monocle_exporter()
121
165
  span_processors = span_processors or [BatchSpanProcessor(exporter) for exporter in exporters]
122
- trace_provider = TracerProvider(resource=resource)
166
+ set_tracer_provider(TracerProvider(resource=resource))
123
167
  attach(set_value("workflow_name", workflow_name))
124
168
  tracer_provider_default = trace.get_tracer_provider()
125
169
  provider_type = type(tracer_provider_default).__name__
@@ -129,14 +173,14 @@ def setup_monocle_telemetry(
129
173
  if not is_proxy_provider:
130
174
  tracer_provider_default.add_span_processor(processor)
131
175
  else:
132
- trace_provider.add_span_processor(processor)
176
+ get_tracer_provider().add_span_processor(processor)
133
177
  if is_proxy_provider:
134
- trace.set_tracer_provider(trace_provider)
178
+ trace.set_tracer_provider(get_tracer_provider())
135
179
  instrumentor = MonocleInstrumentor(user_wrapper_methods=wrapper_methods or [],
136
180
  handlers=span_handlers, union_with_default_methods = union_with_default_methods)
137
181
  # instrumentor.app_name = workflow_name
138
182
  if not instrumentor.is_instrumented_by_opentelemetry:
139
- instrumentor.instrument(trace_provider=trace_provider)
183
+ instrumentor.instrument(trace_provider=get_tracer_provider())
140
184
 
141
185
  return instrumentor
142
186
 
@@ -151,40 +195,30 @@ def on_processor_start(span: Span, parent_context):
151
195
  def set_context_properties(properties: dict) -> None:
152
196
  attach(set_value(SESSION_PROPERTIES_KEY, properties))
153
197
 
154
-
155
- def propagate_trace_id(traceId = "", use_trace_context = False):
198
+ def start_trace():
156
199
  try:
157
- if traceId.startswith("0x"):
158
- traceId = traceId.lstrip("0x")
159
- tracer = get_tracer(instrumenting_module_name= MONOCLE_INSTRUMENTOR, tracer_provider= monocle_tracer_provider)
160
- initial_id_generator = tracer.id_generator
161
- _parent_span_context = get_current() if use_trace_context else None
162
- if traceId and is_valid_trace_id_uuid(traceId):
163
- tracer.id_generator = FixedIdGenerator(uuid.UUID(traceId).int)
164
-
165
- span = tracer.start_span(name = "parent_placeholder_span", context= _parent_span_context)
166
- updated_span_context = set_span_in_context(span=span, context= _parent_span_context)
167
- updated_span_context = set_value("root_span_id", span.get_span_context().span_id, updated_span_context)
168
- token = attach(updated_span_context)
169
-
170
- span.end()
171
- tracer.id_generator = initial_id_generator
200
+ tracer = get_tracer(instrumenting_module_name= MONOCLE_INSTRUMENTOR, tracer_provider= get_tracer_provider())
201
+ span = tracer.start_span(name = "workflow")
202
+ updated_span_context = set_span_in_context(span=span)
203
+ SpanHandler.set_default_monocle_attributes(span)
204
+ SpanHandler.set_workflow_properties(span)
205
+ token = SpanHandler.attach_workflow_type(context=updated_span_context)
172
206
  return token
173
207
  except:
174
- logger.warning("Failed to propagate trace id")
175
- return
208
+ logger.warning("Failed to start trace")
209
+ return None
176
210
 
177
-
178
- def propagate_trace_id_from_traceparent():
179
- propagate_trace_id(use_trace_context = True)
180
-
181
-
182
- def stop_propagate_trace_id(token) -> None:
211
+ def stop_trace(token) -> None:
183
212
  try:
184
- detach(token)
213
+ _parent_span_context = get_current()
214
+ if _parent_span_context is not None:
215
+ parent_span: Span = _parent_span_context.get(_SPAN_KEY, None)
216
+ if parent_span is not None:
217
+ parent_span.end()
218
+ if token is not None:
219
+ SpanHandler.detach_workflow_type(token)
185
220
  except:
186
- logger.warning("Failed to stop propagating trace id")
187
-
221
+ logger.warning("Failed to stop trace")
188
222
 
189
223
  def is_valid_trace_id_uuid(traceId: str) -> bool:
190
224
  try:
@@ -194,6 +228,52 @@ def is_valid_trace_id_uuid(traceId: str) -> bool:
194
228
  pass
195
229
  return False
196
230
 
231
+ def start_scope(scope_name: str, scope_value:str = None) -> object:
232
+ return set_scope(scope_name, scope_value)
233
+
234
+ def stop_scope(token:object) -> None:
235
+ remove_scope(token)
236
+ return
237
+
238
+ @contextmanager
239
+ def monocle_trace_scope(scope_name: str, scope_value:str = None):
240
+ token = start_scope(scope_name, scope_value)
241
+ try:
242
+ yield
243
+ finally:
244
+ stop_scope(token)
245
+
246
+ def monocle_trace_scope_method(scope_name: str):
247
+ def decorator(func):
248
+ if inspect.iscoroutinefunction(func):
249
+ @wraps(func)
250
+ async def wrapper(*args, **kwargs):
251
+ result = async_wrapper(func, scope_name, None, *args, **kwargs)
252
+ return result
253
+ return wrapper
254
+ else:
255
+ @wraps(func)
256
+ def wrapper(*args, **kwargs):
257
+ token = start_scope(scope_name)
258
+ try:
259
+ result = func(*args, **kwargs)
260
+ return result
261
+ finally:
262
+ stop_scope(token)
263
+ return wrapper
264
+ return decorator
265
+
266
+ def monocle_trace_http_route(func):
267
+ if inspect.iscoroutinefunction(func):
268
+ @wraps(func)
269
+ async def wrapper(*args, **kwargs):
270
+ return http_async_route_handler(func, *args, **kwargs)
271
+ return wrapper
272
+ else:
273
+ @wraps(func)
274
+ def wrapper(*args, **kwargs):
275
+ return http_route_handler(func, *args, **kwargs)
276
+ return wrapper
197
277
 
198
278
  class FixedIdGenerator(id_generator.IdGenerator):
199
279
  def __init__(
@@ -1,16 +1,17 @@
1
1
  import logging
2
2
  import os
3
3
  from importlib.metadata import version
4
- from opentelemetry.context import get_current
5
- from opentelemetry.context import get_value
4
+ from opentelemetry.context import get_value, set_value, attach, detach
6
5
  from opentelemetry.sdk.trace import Span
7
6
 
8
7
  from monocle_apptrace.instrumentation.common.constants import (
9
8
  QUERY,
10
9
  service_name_map,
11
10
  service_type_map,
11
+ MONOCLE_SDK_VERSION
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
+ from monocle_apptrace.instrumentation.common.constants import WORKFLOW_TYPE_KEY, WORKFLOW_TYPE_GENERIC
14
15
 
15
16
  logger = logging.getLogger(__name__)
16
17
 
@@ -20,41 +21,61 @@ WORKFLOW_TYPE_MAP = {
20
21
  "haystack": "workflow.haystack"
21
22
  }
22
23
 
24
+ class SpanHandler:
23
25
 
26
+ def __init__(self,instrumentor=None):
27
+ self.instrumentor=instrumentor
24
28
 
25
- class SpanHandler:
29
+ def set_instrumentor(self, instrumentor):
30
+ self.instrumentor = instrumentor
26
31
 
27
32
  def validate(self, to_wrap, wrapped, instance, args, kwargs):
28
33
  pass
29
34
 
30
- def pre_task_processing(self, to_wrap, wrapped, instance, args, span):
31
- if self.__is_root_span(span):
32
- try:
33
- sdk_version = version("monocle_apptrace")
34
- span.set_attribute("monocle_apptrace.version", sdk_version)
35
- except Exception as e:
36
- logger.warning("Exception finding monocle-apptrace version.")
35
+ def pre_tracing(self, to_wrap, wrapped, instance, args, kwargs):
36
+ pass
37
+
38
+ def post_tracing(self, to_wrap, wrapped, instance, args, kwargs, return_value):
39
+ pass
40
+
41
+ def skip_span(self, to_wrap, wrapped, instance, args, kwargs) -> bool:
42
+ # If this is a workflow span type and a workflow span is already generated, then skip generating this span
43
+ if to_wrap.get('span_type') == "workflow" and self.is_workflow_span_active():
44
+ return True
45
+ return False
46
+
47
+ def pre_task_processing(self, to_wrap, wrapped, instance, args,kwargs, span):
37
48
  if "pipeline" in to_wrap['package']:
38
49
  set_attribute(QUERY, args[0]['prompt_builder']['question'])
39
50
 
51
+ @staticmethod
52
+ def set_default_monocle_attributes(span: Span):
53
+ """ Set default monocle attributes for all spans """
54
+ try:
55
+ sdk_version = version("monocle_apptrace")
56
+ span.set_attribute(MONOCLE_SDK_VERSION, sdk_version)
57
+ except Exception as e:
58
+ logger.warning("Exception finding monocle-apptrace version.")
59
+ for scope_key, scope_value in get_scopes().items():
60
+ span.set_attribute(f"scope.{scope_key}", scope_value)
40
61
 
62
+ @staticmethod
63
+ def set_workflow_properties(span: Span, to_wrap = None):
64
+ """ Set attributes of workflow if this is a root span"""
65
+ SpanHandler.set_workflow_attributes(to_wrap, span)
66
+ SpanHandler.set_app_hosting_identifier_attribute(span)
41
67
 
42
68
  def post_task_processing(self, to_wrap, wrapped, instance, args, kwargs, result, span):
43
69
  pass
44
70
 
45
- def set_context_properties(self, to_wrap, wrapped, instance, args, kwargs):
46
- pass
47
-
48
71
  def hydrate_span(self, to_wrap, wrapped, instance, args, kwargs, result, span):
49
72
  self.hydrate_attributes(to_wrap, wrapped, instance, args, kwargs, result, span)
50
73
  self.hydrate_events(to_wrap, wrapped, instance, args, kwargs, result, span)
51
74
 
52
75
  def hydrate_attributes(self, to_wrap, wrapped, instance, args, kwargs, result, span):
53
76
  span_index = 0
54
- if self.__is_root_span(span):
55
- span_index += self.set_workflow_attributes(to_wrap, span, span_index+1)
56
- span_index += self.set_app_hosting_identifier_attribute(span, span_index+1)
57
-
77
+ if SpanHandler.is_root_span(span):
78
+ span_index = 2 # root span will have workflow and hosting entities pre-populated
58
79
  if 'output_processor' in to_wrap and to_wrap["output_processor"] is not None:
59
80
  output_processor=to_wrap['output_processor']
60
81
  if 'type' in output_processor:
@@ -72,16 +93,21 @@ class SpanHandler:
72
93
  try:
73
94
  arguments = {"instance":instance, "args":args, "kwargs":kwargs, "result":result}
74
95
  result = accessor(arguments)
75
- if result and isinstance(result, str):
96
+ if result and isinstance(result, (str, list)):
76
97
  span.set_attribute(attribute_name, result)
77
98
  except Exception as e:
78
99
  logger.debug(f"Error processing accessor: {e}")
79
100
  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")
101
+ logger.debug(f"{' and '.join([key for key in ['attribute', 'accessor'] if not processor.get(key)])} not found or incorrect in entity JSON")
81
102
  span_index += 1
82
103
  else:
83
- logger.warning("attributes not found or incorrect written in entity json")
104
+ logger.debug("attributes not found or incorrect written in entity json")
84
105
 
106
+ # set scopes as attributes by calling get_scopes()
107
+ # scopes is a Mapping[str:object], iterate directly with .items()
108
+ for scope_key, scope_value in get_scopes().items():
109
+ span.set_attribute(f"scope.{scope_key}", scope_value)
110
+
85
111
  if span_index > 0:
86
112
  span.set_attribute("entity.count", span_index)
87
113
 
@@ -109,46 +135,79 @@ class SpanHandler:
109
135
  logger.debug(f"Error evaluating accessor for attribute '{attribute_key}': {e}")
110
136
  span.add_event(name=event_name, attributes=event_attributes)
111
137
 
112
-
113
-
114
- def set_workflow_attributes(self, to_wrap, span: Span, span_index):
115
- return_value = 1
116
- workflow_name = self.get_workflow_name(span=span)
138
+ @staticmethod
139
+ def set_workflow_attributes(to_wrap, span: Span):
140
+ span_index = 1
141
+ workflow_name = SpanHandler.get_workflow_name(span=span)
117
142
  if workflow_name:
118
143
  span.set_attribute("span.type", "workflow")
119
144
  span.set_attribute(f"entity.{span_index}.name", workflow_name)
120
- # workflow type
121
- package_name = to_wrap.get('package')
122
- workflow_type_set = False
123
- for (package, workflow_type) in WORKFLOW_TYPE_MAP.items():
124
- if (package_name is not None and package in package_name):
125
- span.set_attribute(f"entity.{span_index}.type", workflow_type)
126
- workflow_type_set = True
127
- if not workflow_type_set:
128
- span.set_attribute(f"entity.{span_index}.type", "workflow.generic")
129
- return return_value
130
-
131
- def set_app_hosting_identifier_attribute(self, span, span_index):
132
- return_value = 0
145
+ workflow_type = SpanHandler.get_workflow_type(to_wrap)
146
+ span.set_attribute(f"entity.{span_index}.type", workflow_type)
147
+
148
+ @staticmethod
149
+ def get_workflow_type(to_wrap):
150
+ # workflow type
151
+ workflow_type = WORKFLOW_TYPE_GENERIC
152
+ if to_wrap is not None:
153
+ package_name = to_wrap.get('package')
154
+ for (package, framework_workflow_type) in WORKFLOW_TYPE_MAP.items():
155
+ if (package_name is not None and package in package_name):
156
+ workflow_type = framework_workflow_type
157
+ break
158
+ return workflow_type
159
+
160
+ def set_app_hosting_identifier_attribute(span):
161
+ span_index = 2
133
162
  # Search env to indentify the infra service type, if found check env for service name if possible
163
+ span.set_attribute(f"entity.{span_index}.type", f"app_hosting.generic")
164
+ span.set_attribute(f"entity.{span_index}.name", "generic")
134
165
  for type_env, type_name in service_type_map.items():
135
166
  if type_env in os.environ:
136
- return_value = 1
137
167
  span.set_attribute(f"entity.{span_index}.type", f"app_hosting.{type_name}")
138
168
  entity_name_env = service_name_map.get(type_name, "unknown")
139
169
  span.set_attribute(f"entity.{span_index}.name", os.environ.get(entity_name_env, "generic"))
140
- return return_value
141
170
 
142
- def get_workflow_name(self, span: Span) -> str:
171
+ @staticmethod
172
+ def get_workflow_name(span: Span) -> str:
143
173
  try:
144
174
  return get_value("workflow_name") or span.resource.attributes.get("service.name")
145
175
  except Exception as e:
146
176
  logger.exception(f"Error getting workflow name: {e}")
147
177
  return None
148
178
 
149
- def __is_root_span(self, curr_span: Span) -> bool:
179
+ @staticmethod
180
+ def is_root_span(curr_span: Span) -> bool:
150
181
  try:
151
182
  if curr_span is not None and hasattr(curr_span, "parent"):
152
- return curr_span.parent is None or get_current().get("root_span_id") == curr_span.parent.span_id
183
+ return curr_span.parent is None
153
184
  except Exception as e:
154
185
  logger.warning(f"Error finding root span: {e}")
186
+
187
+ def is_non_workflow_root_span(self, curr_span: Span, to_wrap) -> bool:
188
+ return SpanHandler.is_root_span(curr_span) and to_wrap.get("span_type") != "workflow"
189
+
190
+ def is_workflow_span_active(self):
191
+ return get_value(WORKFLOW_TYPE_KEY) is not None
192
+
193
+ @staticmethod
194
+ def attach_workflow_type(to_wrap=None, context=None):
195
+ token = None
196
+ if to_wrap:
197
+ if to_wrap.get('span_type') == "workflow":
198
+ token = attach(set_value(WORKFLOW_TYPE_KEY,
199
+ SpanHandler.get_workflow_type(to_wrap), context))
200
+ else:
201
+ token = attach(set_value(WORKFLOW_TYPE_KEY, WORKFLOW_TYPE_GENERIC, context))
202
+ return token
203
+
204
+ @staticmethod
205
+ def detach_workflow_type(token):
206
+ if token:
207
+ return detach(token)
208
+
209
+ class NonFrameworkSpanHandler(SpanHandler):
210
+
211
+ # If the language framework is being executed, then skip generating direct openAI spans
212
+ def skip_span(self, to_wrap, wrapped, instance, args, kwargs) -> bool:
213
+ return get_value(WORKFLOW_TYPE_KEY) in WORKFLOW_TYPE_MAP.values()