monocle-apptrace 0.3.0b2__py3-none-any.whl → 0.3.0b3__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 (74) hide show
  1. monocle_apptrace/exporters/aws/s3_exporter.py +1 -1
  2. monocle_apptrace/exporters/aws/s3_exporter_opendal.py +126 -0
  3. monocle_apptrace/exporters/azure/blob_exporter_opendal.py +147 -0
  4. monocle_apptrace/exporters/monocle_exporters.py +38 -20
  5. monocle_apptrace/instrumentation/__init__.py +0 -0
  6. monocle_apptrace/instrumentation/common/__init__.py +0 -0
  7. monocle_apptrace/{constants.py → instrumentation/common/constants.py} +13 -0
  8. monocle_apptrace/instrumentation/common/instrumentor.py +208 -0
  9. monocle_apptrace/instrumentation/common/span_handler.py +154 -0
  10. monocle_apptrace/instrumentation/common/utils.py +171 -0
  11. monocle_apptrace/instrumentation/common/wrapper.py +69 -0
  12. monocle_apptrace/instrumentation/common/wrapper_method.py +45 -0
  13. monocle_apptrace/instrumentation/metamodel/__init__.py +0 -0
  14. monocle_apptrace/instrumentation/metamodel/botocore/__init__.py +0 -0
  15. monocle_apptrace/instrumentation/metamodel/botocore/_helper.py +126 -0
  16. monocle_apptrace/instrumentation/metamodel/botocore/entities/__init__.py +0 -0
  17. monocle_apptrace/instrumentation/metamodel/botocore/entities/inference.py +65 -0
  18. monocle_apptrace/instrumentation/metamodel/botocore/methods.py +16 -0
  19. monocle_apptrace/instrumentation/metamodel/haystack/__init__.py +0 -0
  20. monocle_apptrace/instrumentation/metamodel/haystack/_helper.py +127 -0
  21. monocle_apptrace/instrumentation/metamodel/haystack/entities/__init__.py +0 -0
  22. monocle_apptrace/instrumentation/metamodel/haystack/entities/inference.py +76 -0
  23. monocle_apptrace/instrumentation/metamodel/haystack/entities/retrieval.py +61 -0
  24. monocle_apptrace/instrumentation/metamodel/haystack/methods.py +42 -0
  25. monocle_apptrace/instrumentation/metamodel/langchain/__init__.py +0 -0
  26. monocle_apptrace/instrumentation/metamodel/langchain/_helper.py +121 -0
  27. monocle_apptrace/instrumentation/metamodel/langchain/entities/__init__.py +0 -0
  28. monocle_apptrace/instrumentation/metamodel/langchain/entities/inference.py +71 -0
  29. monocle_apptrace/instrumentation/metamodel/langchain/entities/retrieval.py +58 -0
  30. monocle_apptrace/instrumentation/metamodel/langchain/methods.py +105 -0
  31. monocle_apptrace/instrumentation/metamodel/llamaindex/__init__.py +0 -0
  32. monocle_apptrace/instrumentation/metamodel/llamaindex/_helper.py +154 -0
  33. monocle_apptrace/instrumentation/metamodel/llamaindex/entities/__init__.py +0 -0
  34. monocle_apptrace/instrumentation/metamodel/llamaindex/entities/inference.py +71 -0
  35. monocle_apptrace/instrumentation/metamodel/llamaindex/entities/retrieval.py +57 -0
  36. monocle_apptrace/{metamodel/maps/llamaindex_methods.json → instrumentation/metamodel/llamaindex/methods.py} +28 -31
  37. {monocle_apptrace-0.3.0b2.dist-info → monocle_apptrace-0.3.0b3.dist-info}/METADATA +14 -1
  38. monocle_apptrace-0.3.0b3.dist-info/RECORD +48 -0
  39. monocle_apptrace/botocore/__init__.py +0 -9
  40. monocle_apptrace/haystack/__init__.py +0 -9
  41. monocle_apptrace/haystack/wrap_pipeline.py +0 -63
  42. monocle_apptrace/instrumentor.py +0 -121
  43. monocle_apptrace/langchain/__init__.py +0 -9
  44. monocle_apptrace/llamaindex/__init__.py +0 -16
  45. monocle_apptrace/message_processing.py +0 -80
  46. monocle_apptrace/metamodel/README.md +0 -47
  47. monocle_apptrace/metamodel/entities/README.md +0 -77
  48. monocle_apptrace/metamodel/entities/app_hosting_types.json +0 -29
  49. monocle_apptrace/metamodel/entities/entities.json +0 -49
  50. monocle_apptrace/metamodel/entities/inference_types.json +0 -33
  51. monocle_apptrace/metamodel/entities/model_types.json +0 -41
  52. monocle_apptrace/metamodel/entities/vector_store_types.json +0 -25
  53. monocle_apptrace/metamodel/entities/workflow_types.json +0 -22
  54. monocle_apptrace/metamodel/maps/attributes/inference/botocore_entities.json +0 -27
  55. monocle_apptrace/metamodel/maps/attributes/inference/haystack_entities.json +0 -57
  56. monocle_apptrace/metamodel/maps/attributes/inference/langchain_entities.json +0 -57
  57. monocle_apptrace/metamodel/maps/attributes/inference/llamaindex_entities.json +0 -57
  58. monocle_apptrace/metamodel/maps/attributes/retrieval/haystack_entities.json +0 -31
  59. monocle_apptrace/metamodel/maps/attributes/retrieval/langchain_entities.json +0 -31
  60. monocle_apptrace/metamodel/maps/attributes/retrieval/llamaindex_entities.json +0 -31
  61. monocle_apptrace/metamodel/maps/botocore_methods.json +0 -13
  62. monocle_apptrace/metamodel/maps/haystack_methods.json +0 -45
  63. monocle_apptrace/metamodel/maps/langchain_methods.json +0 -129
  64. monocle_apptrace/metamodel/spans/README.md +0 -121
  65. monocle_apptrace/metamodel/spans/span_example.json +0 -140
  66. monocle_apptrace/metamodel/spans/span_format.json +0 -55
  67. monocle_apptrace/metamodel/spans/span_types.json +0 -16
  68. monocle_apptrace/utils.py +0 -252
  69. monocle_apptrace/wrap_common.py +0 -511
  70. monocle_apptrace/wrapper.py +0 -27
  71. monocle_apptrace-0.3.0b2.dist-info/RECORD +0 -48
  72. {monocle_apptrace-0.3.0b2.dist-info → monocle_apptrace-0.3.0b3.dist-info}/WHEEL +0 -0
  73. {monocle_apptrace-0.3.0b2.dist-info → monocle_apptrace-0.3.0b3.dist-info}/licenses/LICENSE +0 -0
  74. {monocle_apptrace-0.3.0b2.dist-info → monocle_apptrace-0.3.0b3.dist-info}/licenses/NOTICE +0 -0
@@ -25,7 +25,7 @@ class S3SpanExporter(SpanExporterBase):
25
25
  super().__init__()
26
26
  # Use environment variables if credentials are not provided
27
27
  DEFAULT_FILE_PREFIX = "monocle_trace_"
28
- DEFAULT_TIME_FORMAT = "%Y-%m-%d_%H.%M.%S"
28
+ DEFAULT_TIME_FORMAT = "%Y-%m-%d__%H.%M.%S"
29
29
  self.max_batch_size = 500
30
30
  self.export_interval = 1
31
31
  self.s3_client = boto3.client(
@@ -0,0 +1,126 @@
1
+ import os
2
+ import time
3
+ import datetime
4
+ import logging
5
+ import asyncio
6
+ from typing import Sequence
7
+ from opentelemetry.sdk.trace import ReadableSpan
8
+ from opentelemetry.sdk.trace.export import SpanExportResult
9
+ from monocle_apptrace.exporters.base_exporter import SpanExporterBase
10
+ from opendal import Operator
11
+ from opendal.exceptions import PermissionDenied, ConfigInvalid, Unexpected
12
+
13
+
14
+ import json
15
+
16
+ logger = logging.getLogger(__name__)
17
+ class OpenDALS3Exporter(SpanExporterBase):
18
+ def __init__(self, bucket_name=None, region_name=None):
19
+ super().__init__()
20
+ DEFAULT_FILE_PREFIX = "monocle_trace_"
21
+ DEFAULT_TIME_FORMAT = "%Y-%m-%d__%H.%M.%S"
22
+ self.max_batch_size = 500
23
+ self.export_interval = 1
24
+ self.file_prefix = DEFAULT_FILE_PREFIX
25
+ self.time_format = DEFAULT_TIME_FORMAT
26
+ self.export_queue = []
27
+ self.last_export_time = time.time()
28
+ self.bucket_name = bucket_name or os.getenv("MONOCLE_S3_BUCKET_NAME", "default-bucket")
29
+
30
+ # Initialize OpenDAL S3 operator
31
+ self.op = Operator(
32
+ "s3",
33
+ root = "/",
34
+ region=os.getenv("AWS_REGION", region_name),
35
+ bucket=self.bucket_name,
36
+ access_key_id=os.getenv("AWS_ACCESS_KEY_ID"),
37
+ secret_access_key=os.getenv("AWS_SECRET_ACCESS_KEY"),
38
+ )
39
+
40
+
41
+ def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
42
+ """Synchronous export method that internally handles async logic."""
43
+ try:
44
+ # Run the asynchronous export logic in an event loop
45
+ asyncio.run(self.__export_async(spans))
46
+ return SpanExportResult.SUCCESS
47
+ except Exception as e:
48
+ logger.error(f"Error exporting spans: {e}")
49
+ return SpanExportResult.FAILURE
50
+
51
+ async def __export_async(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
52
+ try:
53
+ # Add spans to the export queue
54
+ for span in spans:
55
+ self.export_queue.append(span)
56
+ if len(self.export_queue) >= self.max_batch_size:
57
+ await self.__export_spans()
58
+
59
+ # Check if it's time to force a flush
60
+ current_time = time.time()
61
+ if current_time - self.last_export_time >= self.export_interval:
62
+ await self.__export_spans()
63
+ self.last_export_time = current_time
64
+
65
+ return SpanExportResult.SUCCESS
66
+ except Exception as e:
67
+ logger.error(f"Error exporting spans: {e}")
68
+ return SpanExportResult.FAILURE
69
+
70
+ def __serialize_spans(self, spans: Sequence[ReadableSpan]) -> str:
71
+ try:
72
+ # Serialize spans to JSON or any other format you prefer
73
+ valid_json_list = []
74
+ for span in spans:
75
+ try:
76
+ valid_json_list.append(span.to_json(indent=0).replace("\n", ""))
77
+ except json.JSONDecodeError as e:
78
+ logger.warning(f"Invalid JSON format in span data: {span.context.span_id}. Error: {e}")
79
+ continue
80
+ return "\n".join(valid_json_list) + "\n"
81
+ except Exception as e:
82
+ logger.warning(f"Error serializing spans: {e}")
83
+
84
+ async def __export_spans(self):
85
+ if not self.export_queue:
86
+ return
87
+ # Take a batch of spans from the queue
88
+ batch_to_export = self.export_queue[:self.max_batch_size]
89
+ serialized_data = self.__serialize_spans(batch_to_export)
90
+ self.export_queue = self.export_queue[self.max_batch_size:]
91
+ try:
92
+ self.__upload_to_s3(serialized_data)
93
+ except Exception as e:
94
+ logger.error(f"Failed to upload span batch: {e}")
95
+
96
+ @SpanExporterBase.retry_with_backoff(exceptions=(Unexpected))
97
+ def __upload_to_s3(self, span_data_batch: str):
98
+
99
+ current_time = datetime.datetime.now().strftime(self.time_format)
100
+ file_name = f"{self.file_prefix}{current_time}.ndjson"
101
+ try:
102
+ # Attempt to write the span data batch to S3
103
+ self.op.write(file_name, span_data_batch.encode("utf-8"))
104
+ logger.info(f"Span batch uploaded to S3 as {file_name}.")
105
+
106
+ except PermissionDenied as e:
107
+ # S3 bucket is forbidden.
108
+ logger.error(f"Access to bucket {self.bucket_name} is forbidden (403).")
109
+ raise PermissionError(f"Access to bucket {self.bucket_name} is forbidden.")
110
+
111
+ except ConfigInvalid as e:
112
+ # Bucket does not exist.
113
+ if "404" in str(e):
114
+ logger.error("Bucket does not exist. Please check the bucket name and region.")
115
+ raise Exception(f"Bucket does not exist. Error: {e}")
116
+ else:
117
+ logger.error(f"Unexpected error when accessing bucket {self.bucket_name}: {e}")
118
+ raise e
119
+
120
+
121
+ async def force_flush(self, timeout_millis: int = 30000) -> bool:
122
+ await self.__export_spans()
123
+ return True
124
+
125
+ def shutdown(self) -> None:
126
+ logger.info("S3SpanExporter has been shut down.")
@@ -0,0 +1,147 @@
1
+ import os
2
+ import time
3
+ import datetime
4
+ import logging
5
+ import asyncio
6
+ from opentelemetry.sdk.trace import ReadableSpan
7
+ from opentelemetry.sdk.trace.export import SpanExportResult
8
+ from typing import Sequence
9
+ from opendal import Operator
10
+ from monocle_apptrace.exporters.base_exporter import SpanExporterBase
11
+ from opendal.exceptions import Unexpected, PermissionDenied, NotFound
12
+ import json
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ class OpenDALAzureExporter(SpanExporterBase):
17
+ def __init__(self, connection_string=None, container_name=None):
18
+ super().__init__()
19
+ DEFAULT_FILE_PREFIX = "monocle_trace_"
20
+ DEFAULT_TIME_FORMAT = "%Y-%m-%d_%H.%M.%S"
21
+ self.max_batch_size = 500
22
+ self.export_interval = 1
23
+ self.container_name = container_name
24
+
25
+ # Default values
26
+ self.file_prefix = DEFAULT_FILE_PREFIX
27
+ self.time_format = DEFAULT_TIME_FORMAT
28
+
29
+ # Validate input
30
+ if not connection_string:
31
+ connection_string = os.getenv('MONOCLE_BLOB_CONNECTION_STRING')
32
+ if not connection_string:
33
+ raise ValueError("Azure Storage connection string is not provided or set in environment variables.")
34
+
35
+ if not container_name:
36
+ container_name = os.getenv('MONOCLE_BLOB_CONTAINER_NAME', 'default-container')
37
+ endpoint, account_name , account_key = self.parse_connection_string(connection_string)
38
+
39
+ if not account_name or not account_key:
40
+ raise ValueError("AccountName or AccountKey missing in the connection string.")
41
+
42
+ try:
43
+ # Initialize OpenDAL operator with explicit credentials
44
+ self.operator = Operator(
45
+ "azblob",
46
+ endpoint=endpoint,
47
+ account_name=account_name,
48
+ account_key=account_key,
49
+ container=container_name
50
+ )
51
+ except Exception as e:
52
+ raise RuntimeError(f"Failed to initialize OpenDAL operator: {e}")
53
+
54
+
55
+ def parse_connection_string(self,connection_string):
56
+ connection_params = dict(item.split('=', 1) for item in connection_string.split(';') if '=' in item)
57
+
58
+ account_name = connection_params.get('AccountName')
59
+ account_key = connection_params.get('AccountKey')
60
+ endpoint_suffix = connection_params.get('EndpointSuffix')
61
+
62
+ if not all([account_name, account_key, endpoint_suffix]):
63
+ raise ValueError("Invalid connection string. Ensure it contains AccountName, AccountKey, and EndpointSuffix.")
64
+
65
+ endpoint = f"https://{account_name}.blob.{endpoint_suffix}"
66
+ return endpoint, account_name, account_key
67
+
68
+
69
+ def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
70
+ """Synchronous export method that internally handles async logic."""
71
+ try:
72
+ # Run the asynchronous export logic in an event loop
73
+ asyncio.run(self._export_async(spans))
74
+ return SpanExportResult.SUCCESS
75
+ except Exception as e:
76
+ logger.error(f"Error exporting spans: {e}")
77
+ return SpanExportResult.FAILURE
78
+
79
+ async def _export_async(self, spans: Sequence[ReadableSpan]):
80
+ """The actual async export logic is run here."""
81
+ # Add spans to the export queue
82
+ for span in spans:
83
+ self.export_queue.append(span)
84
+ if len(self.export_queue) >= self.max_batch_size:
85
+ await self.__export_spans()
86
+
87
+ # Force a flush if the interval has passed
88
+ current_time = time.time()
89
+ if current_time - self.last_export_time >= self.export_interval:
90
+ await self.__export_spans()
91
+ self.last_export_time = current_time
92
+
93
+ def __serialize_spans(self, spans: Sequence[ReadableSpan]) -> str:
94
+ try:
95
+ valid_json_list = []
96
+ for span in spans:
97
+ try:
98
+ valid_json_list.append(span.to_json(indent=0).replace("\n", ""))
99
+ except json.JSONDecodeError as e:
100
+ logger.warning(f"Invalid JSON format in span data: {span.context.span_id}. Error: {e}")
101
+ continue
102
+
103
+ ndjson_data = "\n".join(valid_json_list) + "\n"
104
+ return ndjson_data
105
+ except Exception as e:
106
+ logger.warning(f"Error serializing spans: {e}")
107
+
108
+ async def __export_spans(self):
109
+ if len(self.export_queue) == 0:
110
+ return
111
+
112
+ batch_to_export = self.export_queue[:self.max_batch_size]
113
+ serialized_data = self.__serialize_spans(batch_to_export)
114
+ self.export_queue = self.export_queue[self.max_batch_size:]
115
+ try:
116
+ self.__upload_to_opendal(serialized_data)
117
+ except Exception as e:
118
+ logger.error(f"Failed to upload span batch: {e}")
119
+
120
+ @SpanExporterBase.retry_with_backoff(exceptions=(Unexpected,))
121
+ def __upload_to_opendal(self, span_data_batch: str):
122
+ current_time = datetime.datetime.now().strftime(self.time_format)
123
+ file_name = f"{self.file_prefix}{current_time}.ndjson"
124
+
125
+ try:
126
+ self.operator.write(file_name, span_data_batch.encode('utf-8'))
127
+ logger.info(f"Span batch uploaded to Azure Blob Storage as {file_name}.")
128
+ except PermissionDenied as e:
129
+ # Azure Container is forbidden.
130
+ logger.error(f"Access to container {self.container_name} is forbidden (403).")
131
+ raise PermissionError(f"Access to container {self.container_name} is forbidden.")
132
+
133
+ except NotFound as e:
134
+ # Container does not exist.
135
+ if "404" in str(e):
136
+ logger.error("Container does not exist. Please check the container name.")
137
+ raise Exception(f"Container does not exist. Error: {e}")
138
+ else:
139
+ logger.error(f"Unexpected NotFound error when accessing container {self.container_name}: {e}")
140
+ raise e
141
+
142
+ async def force_flush(self, timeout_millis: int = 30000) -> bool:
143
+ await self.__export_spans()
144
+ return True
145
+
146
+ def shutdown(self) -> None:
147
+ logger.info("OpenDALAzureExporter has been shut down.")
@@ -1,27 +1,45 @@
1
- from typing import Dict, Any
2
- import os, warnings
1
+ from typing import Dict, Any, List
2
+ import os
3
+ import warnings
3
4
  from importlib import import_module
4
5
  from opentelemetry.sdk.trace.export import SpanExporter, ConsoleSpanExporter
5
6
  from monocle_apptrace.exporters.file_exporter import FileSpanExporter
6
7
 
7
- monocle_exporters:Dict[str, Any] = {
8
+ monocle_exporters: Dict[str, Any] = {
8
9
  "s3": {"module": "monocle_apptrace.exporters.aws.s3_exporter", "class": "S3SpanExporter"},
9
- "blob": {"module":"monocle_apptrace.exporters.azure.blob_exporter", "class": "AzureBlobSpanExporter"},
10
- "okahu": {"module":"monocle_apptrace.exporters.okahu.okahu_exporter", "class": "OkahuSpanExporter"},
11
- "file": {"module":"monocle_apptrace.exporters.file_exporter", "class": "FileSpanExporter"}
10
+ "blob": {"module": "monocle_apptrace.exporters.azure.blob_exporter", "class": "AzureBlobSpanExporter"},
11
+ "okahu": {"module": "monocle_apptrace.exporters.okahu.okahu_exporter", "class": "OkahuSpanExporter"},
12
+ "file": {"module": "monocle_apptrace.exporters.file_exporter", "class": "FileSpanExporter"},
13
+ "memory": {"module": "opentelemetry.sdk.trace.export.in_memory_span_exporter", "class": "InMemorySpanExporter"},
14
+ "console": {"module": "opentelemetry.sdk.trace.export", "class": "ConsoleSpanExporter"}
12
15
  }
13
16
 
14
- def get_monocle_exporter() -> SpanExporter:
15
- exporter_name = os.environ.get("MONOCLE_EXPORTER", "file")
16
- try:
17
- exporter_class_path = monocle_exporters[exporter_name]
18
- except Exception as ex:
19
- warnings.warn(f"Unsupported Monocle span exporter setting {exporter_name}, using default FileSpanExporter.")
20
- return FileSpanExporter()
21
- try:
22
- exporter_module = import_module(exporter_class_path.get("module"))
23
- exporter_class = getattr(exporter_module, exporter_class_path.get("class"))
24
- return exporter_class()
25
- except Exception as ex:
26
- warnings.warn(f"Unable to set Monocle span exporter to {exporter_name}, error {ex}. Using ConsoleSpanExporter")
27
- return ConsoleSpanExporter()
17
+
18
+ def get_monocle_exporter() -> List[SpanExporter]:
19
+ # Retrieve the MONOCLE_EXPORTER environment variable and split it into a list
20
+ exporter_names = os.environ.get("MONOCLE_EXPORTER", "file").split(",")
21
+ exporters = []
22
+
23
+ for exporter_name in exporter_names:
24
+ exporter_name = exporter_name.strip()
25
+ try:
26
+ exporter_class_path = monocle_exporters[exporter_name]
27
+ except KeyError:
28
+ warnings.warn(f"Unsupported Monocle span exporter '{exporter_name}', skipping.")
29
+ continue
30
+ try:
31
+ exporter_module = import_module(exporter_class_path["module"])
32
+ exporter_class = getattr(exporter_module, exporter_class_path["class"])
33
+ exporters.append(exporter_class())
34
+ except Exception as ex:
35
+ warnings.warn(
36
+ f"Unable to initialize Monocle span exporter '{exporter_name}', error: {ex}. Using ConsoleSpanExporter as a fallback.")
37
+ exporters.append(ConsoleSpanExporter())
38
+ continue
39
+
40
+ # If no exporters were created, default to FileSpanExporter
41
+ if not exporters:
42
+ warnings.warn("No valid Monocle span exporters configured. Defaulting to FileSpanExporter.")
43
+ exporters.append(FileSpanExporter())
44
+
45
+ return exporters
File without changes
File without changes
@@ -9,6 +9,8 @@ AWS_LAMBDA_FUNCTION_IDENTIFIER_ENV_NAME = "AWS_LAMBDA_FUNCTION_NAME"
9
9
  AZURE_FUNCTION_IDENTIFIER_ENV_NAME = "WEBSITE_SITE_NAME"
10
10
  AZURE_APP_SERVICE_IDENTIFIER_ENV_NAME = "WEBSITE_DEPLOYMENT_ID"
11
11
  GITHUB_CODESPACE_IDENTIFIER_ENV_NAME = "GITHUB_REPOSITORY"
12
+
13
+
12
14
  # Azure naming reference can be found here
13
15
  # https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-abbreviations
14
16
  AZURE_FUNCTION_NAME = "azure.func"
@@ -34,3 +36,14 @@ service_name_map = {
34
36
  AWS_LAMBDA_SERVICE_NAME: AWS_LAMBDA_FUNCTION_IDENTIFIER_ENV_NAME,
35
37
  GITHUB_CODESPACE_SERVICE_NAME: GITHUB_CODESPACE_IDENTIFIER_ENV_NAME
36
38
  }
39
+
40
+ WORKFLOW_TYPE_KEY = "workflow_type"
41
+ DATA_INPUT_KEY = "data.input"
42
+ DATA_OUTPUT_KEY = "data.output"
43
+ PROMPT_INPUT_KEY = "data.input"
44
+ PROMPT_OUTPUT_KEY = "data.output"
45
+ QUERY = "input"
46
+ RESPONSE = "response"
47
+ SESSION_PROPERTIES_KEY = "session"
48
+ INFRA_SERVICE_KEY = "infra_service_name"
49
+ META_DATA = 'metadata'
@@ -0,0 +1,208 @@
1
+ import logging
2
+ from typing import Collection, Dict, List, Union
3
+ import random
4
+ import uuid
5
+ from opentelemetry import trace
6
+ from opentelemetry.context import attach, get_value, set_value, get_current, detach
7
+ from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
8
+ from opentelemetry.instrumentation.utils import unwrap
9
+ from opentelemetry.trace import SpanContext
10
+ from opentelemetry.sdk.trace import TracerProvider, Span, id_generator
11
+ from opentelemetry.sdk.resources import SERVICE_NAME, Resource
12
+ from opentelemetry.sdk.trace import Span, TracerProvider
13
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor, SpanProcessor
14
+ from opentelemetry.trace import get_tracer
15
+ from wrapt import wrap_function_wrapper
16
+ from opentelemetry.trace.propagation import set_span_in_context
17
+ from monocle_apptrace.exporters.monocle_exporters import get_monocle_exporter
18
+ from monocle_apptrace.instrumentation.common.span_handler import SpanHandler
19
+ from monocle_apptrace.instrumentation.common.wrapper_method import (
20
+ DEFAULT_METHODS_LIST,
21
+ WrapperMethod,
22
+ )
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+ SESSION_PROPERTIES_KEY = "session"
27
+
28
+ _instruments = ()
29
+
30
+ monocle_tracer_provider: TracerProvider = None
31
+
32
+ MONOCLE_INSTRUMENTOR = "monocle_apptrace"
33
+
34
+ class MonocleInstrumentor(BaseInstrumentor):
35
+ workflow_name: str = ""
36
+ user_wrapper_methods: list[Union[dict,WrapperMethod]] = []
37
+ instrumented_method_list: list[object] = []
38
+ handlers:Dict[str,SpanHandler] = {} # dict of handlers
39
+ union_with_default_methods: bool = False
40
+
41
+ def __init__(
42
+ self,
43
+ handlers,
44
+ user_wrapper_methods: list[Union[dict,WrapperMethod]] = None,
45
+ union_with_default_methods: bool = True
46
+ ) -> None:
47
+ self.user_wrapper_methods = user_wrapper_methods or []
48
+ self.handlers = handlers or {'default':SpanHandler()}
49
+ self.union_with_default_methods = union_with_default_methods
50
+ super().__init__()
51
+
52
+ def instrumentation_dependencies(self) -> Collection[str]:
53
+ return _instruments
54
+
55
+ def _instrument(self, **kwargs):
56
+ tracer_provider: TracerProvider = kwargs.get("tracer_provider")
57
+ global monocle_tracer_provider
58
+ monocle_tracer_provider = tracer_provider
59
+ tracer = get_tracer(instrumenting_module_name=MONOCLE_INSTRUMENTOR, tracer_provider=tracer_provider)
60
+
61
+ final_method_list = []
62
+ if self.union_with_default_methods is True:
63
+ final_method_list= final_method_list + DEFAULT_METHODS_LIST
64
+
65
+ for method in self.user_wrapper_methods:
66
+ if isinstance(method, dict):
67
+ final_method_list.append(method)
68
+ elif isinstance(method, WrapperMethod):
69
+ final_method_list.append(method.to_dict())
70
+
71
+ for method_config in final_method_list:
72
+ target_package = method_config.get("package", None)
73
+ target_object = method_config.get("object", None)
74
+ target_method = method_config.get("method", None)
75
+ wrapped_by = method_config.get("wrapper_method", None)
76
+ #get the requisite handler or default one
77
+ handler_key = method_config.get("span_handler",'default')
78
+ try:
79
+ handler = self.handlers.get(handler_key)
80
+ wrap_function_wrapper(
81
+ target_package,
82
+ f"{target_object}.{target_method}" if target_object else target_method,
83
+ wrapped_by(tracer, handler, method_config),
84
+ )
85
+ self.instrumented_method_list.append(method_config)
86
+ except ModuleNotFoundError as e:
87
+ logger.debug(f"ignoring module {e.name}")
88
+
89
+ except Exception as ex:
90
+ logger.error(f"""_instrument wrap exception: {str(ex)}
91
+ for package: {target_package},
92
+ object:{target_object},
93
+ method:{target_method}""")
94
+
95
+ def _uninstrument(self, **kwargs):
96
+ for wrapped_method in self.instrumented_method_list:
97
+ try:
98
+ wrap_package = wrapped_method.get("package")
99
+ wrap_object = wrapped_method.get("object")
100
+ wrap_method = wrapped_method.get("method")
101
+ unwrap(
102
+ f"{wrap_package}.{wrap_object}" if wrap_object else wrap_package,
103
+ wrap_method,
104
+ )
105
+ except Exception as ex:
106
+ logger.error(f"""_instrument unwrap exception: {str(ex)}
107
+ for package: {wrap_package},
108
+ object:{wrap_object},
109
+ method:{wrap_method}""")
110
+
111
+ def setup_monocle_telemetry(
112
+ workflow_name: str,
113
+ span_processors: List[SpanProcessor] = None,
114
+ span_handlers: Dict[str,SpanHandler] = None,
115
+ wrapper_methods: List[Union[dict,WrapperMethod]] = None,
116
+ union_with_default_methods: bool = True) -> None:
117
+ resource = Resource(attributes={
118
+ SERVICE_NAME: workflow_name
119
+ })
120
+ exporters = get_monocle_exporter()
121
+ span_processors = span_processors or [BatchSpanProcessor(exporter) for exporter in exporters]
122
+ trace_provider = TracerProvider(resource=resource)
123
+ attach(set_value("workflow_name", workflow_name))
124
+ tracer_provider_default = trace.get_tracer_provider()
125
+ provider_type = type(tracer_provider_default).__name__
126
+ is_proxy_provider = "Proxy" in provider_type
127
+ for processor in span_processors:
128
+ processor.on_start = on_processor_start
129
+ if not is_proxy_provider:
130
+ tracer_provider_default.add_span_processor(processor)
131
+ else:
132
+ trace_provider.add_span_processor(processor)
133
+ if is_proxy_provider:
134
+ trace.set_tracer_provider(trace_provider)
135
+ instrumentor = MonocleInstrumentor(user_wrapper_methods=wrapper_methods or [],
136
+ handlers=span_handlers, union_with_default_methods = union_with_default_methods)
137
+ # instrumentor.app_name = workflow_name
138
+ if not instrumentor.is_instrumented_by_opentelemetry:
139
+ instrumentor.instrument(trace_provider=trace_provider)
140
+
141
+ return instrumentor
142
+
143
+ def on_processor_start(span: Span, parent_context):
144
+ context_properties = get_value(SESSION_PROPERTIES_KEY)
145
+ if context_properties is not None:
146
+ for key, value in context_properties.items():
147
+ span.set_attribute(
148
+ f"{SESSION_PROPERTIES_KEY}.{key}", value
149
+ )
150
+
151
+ def set_context_properties(properties: dict) -> None:
152
+ attach(set_value(SESSION_PROPERTIES_KEY, properties))
153
+
154
+
155
+ def propagate_trace_id(traceId = "", use_trace_context = False):
156
+ 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
172
+ return token
173
+ except:
174
+ logger.warning("Failed to propagate trace id")
175
+ return
176
+
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:
183
+ try:
184
+ detach(token)
185
+ except:
186
+ logger.warning("Failed to stop propagating trace id")
187
+
188
+
189
+ def is_valid_trace_id_uuid(traceId: str) -> bool:
190
+ try:
191
+ uuid.UUID(traceId)
192
+ return True
193
+ except:
194
+ pass
195
+ return False
196
+
197
+
198
+ class FixedIdGenerator(id_generator.IdGenerator):
199
+ def __init__(
200
+ self,
201
+ trace_id: int) -> None:
202
+ self.trace_id = trace_id
203
+
204
+ def generate_span_id(self) -> int:
205
+ return random.getrandbits(64)
206
+
207
+ def generate_trace_id(self) -> int:
208
+ return self.trace_id