netra-sdk 0.1.6__py3-none-any.whl → 0.1.9__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 netra-sdk might be problematic. Click here for more details.

@@ -16,10 +16,18 @@ logging.basicConfig(level=logging.INFO)
16
16
  logger = logging.getLogger(__name__)
17
17
 
18
18
 
19
+ class ActionModel(BaseModel): # type: ignore[misc]
20
+ action: str
21
+ action_type: str
22
+ success: bool
23
+ affected_records: Optional[List[Dict[str, str]]] = None
24
+ metadata: Optional[Dict[str, str]] = None
25
+
26
+
19
27
  class UsageModel(BaseModel): # type: ignore[misc]
20
28
  model: str
21
- type: str
22
- unit_used: Optional[int] = None
29
+ usage_type: str
30
+ units_used: Optional[int] = None
23
31
  cost_in_usd: Optional[float] = None
24
32
 
25
33
 
@@ -28,27 +36,25 @@ class ATTRIBUTE:
28
36
  MODEL = "model"
29
37
  PROMPT = "prompt"
30
38
  NEGATIVE_PROMPT = "negative_prompt"
31
- HEIGHT = "height"
32
- WIDTH = "width"
33
- OUTPUT_TYPE = "output_type"
34
39
  USAGE = "usage"
35
40
  STATUS = "status"
36
41
  DURATION_MS = "duration_ms"
37
42
  ERROR_MESSAGE = "error_message"
43
+ ACTION = "action"
38
44
 
39
45
 
40
- class Session:
46
+ class SpanWrapper:
41
47
  """
42
48
  Context manager for tracking observability data for external API calls.
43
49
 
44
50
  Usage:
45
- with combat.start_session("video_gen_task") as session:
46
- session.set_prompt("A cat playing piano").set_image_height("1024")
51
+ with combat.start_span("video_gen_task") as span:
52
+ span.set_prompt("A cat playing piano").set_image_height("1024")
47
53
 
48
54
  # External API call
49
55
  result = external_api.generate_video(...)
50
56
 
51
- session.set_usage(usage_data)
57
+ span.set_usage(usage_data)
52
58
  """
53
59
 
54
60
  def __init__(self, name: str, attributes: Optional[Dict[str, str]] = None, module_name: str = "combat_sdk"):
@@ -65,8 +71,8 @@ class Session:
65
71
  self.span: Optional[trace.Span] = None
66
72
  self.context_token: Optional[Any] = None
67
73
 
68
- def __enter__(self) -> "Session":
69
- """Start the session, begin time tracking, and create OpenTelemetry span."""
74
+ def __enter__(self) -> "SpanWrapper":
75
+ """Start the span wrapper, begin time tracking, and create OpenTelemetry span."""
70
76
  self.start_time = time.time()
71
77
 
72
78
  # Create OpenTelemetry span
@@ -76,11 +82,11 @@ class Session:
76
82
  ctx = set_span_in_context(self.span)
77
83
  self.context_token = context_api.attach(ctx)
78
84
 
79
- logger.info(f"Started session: {self.name}")
85
+ logger.info(f"Started span wrapper: {self.name}")
80
86
  return self
81
87
 
82
88
  def __exit__(self, exc_type: Optional[type], exc_val: Optional[Exception], exc_tb: Any) -> Literal[False]:
83
- """End the session, calculate duration, handle errors, and close OpenTelemetry span."""
89
+ """End the span wrapper, calculate duration, handle errors, and close OpenTelemetry span."""
84
90
  self.end_time = time.time()
85
91
  duration_ms = (self.end_time - self.start_time) * 1000 if self.start_time is not None else None
86
92
 
@@ -101,7 +107,7 @@ class Session:
101
107
  self.span.set_status(Status(StatusCode.ERROR, self.error_message))
102
108
  if exc_val is not None:
103
109
  self.span.record_exception(exc_val)
104
- logger.error(f"Session {self.name} failed: {self.error_message}")
110
+ logger.error(f"Span wrapper {self.name} failed: {self.error_message}")
105
111
 
106
112
  self.set_attribute(f"{Config.LIBRARY_NAME}.{ATTRIBUTE.STATUS}", self.status)
107
113
 
@@ -117,15 +123,15 @@ class Session:
117
123
  context_api.detach(self.context_token)
118
124
 
119
125
  logger.info(
120
- f"Ended session: {self.name} (Status: {self.status}, Duration: {duration_ms:.2f}ms)"
126
+ f"Ended span wrapper: {self.name} (Status: {self.status}, Duration: {duration_ms:.2f}ms)"
121
127
  if duration_ms is not None
122
- else f"Ended session: {self.name} (Status: {self.status})"
128
+ else f"Ended span wrapper: {self.name} (Status: {self.status})"
123
129
  )
124
130
 
125
131
  # Don't suppress exceptions
126
132
  return False
127
133
 
128
- def set_attribute(self, key: str, value: str) -> "Session":
134
+ def set_attribute(self, key: str, value: str) -> "SpanWrapper":
129
135
  """Set a single attribute and return self for method chaining."""
130
136
  self.attributes[key] = value
131
137
  # Also set on the span if it exists
@@ -133,41 +139,35 @@ class Session:
133
139
  self.span.set_attribute(key, value)
134
140
  return self
135
141
 
136
- def set_prompt(self, prompt: str) -> "Session":
142
+ def set_prompt(self, prompt: str) -> "SpanWrapper":
137
143
  """Set the input prompt."""
138
144
  return self.set_attribute(f"{Config.LIBRARY_NAME}.{ATTRIBUTE.PROMPT}", prompt)
139
145
 
140
- def set_negative_prompt(self, negative_prompt: str) -> "Session":
146
+ def set_negative_prompt(self, negative_prompt: str) -> "SpanWrapper":
141
147
  """Set the negative prompt."""
142
148
  return self.set_attribute(f"{Config.LIBRARY_NAME}.{ATTRIBUTE.NEGATIVE_PROMPT}", negative_prompt)
143
149
 
144
- def set_height(self, height: str) -> "Session":
145
- """Set the height."""
146
- return self.set_attribute(f"{Config.LIBRARY_NAME}.{ATTRIBUTE.HEIGHT}", height)
147
-
148
- def set_width(self, width: str) -> "Session":
149
- """Set the width."""
150
- return self.set_attribute(f"{Config.LIBRARY_NAME}.{ATTRIBUTE.WIDTH}", width)
151
-
152
- def set_output_type(self, output_type: str) -> "Session":
153
- """Set the output type."""
154
- return self.set_attribute(f"{Config.LIBRARY_NAME}.{ATTRIBUTE.OUTPUT_TYPE}", output_type)
155
-
156
- def set_usage(self, usage: List[UsageModel]) -> "Session":
150
+ def set_usage(self, usage: List[UsageModel]) -> "SpanWrapper":
157
151
  """Set the usage data as a JSON string."""
158
152
  usage_dict = [u.model_dump() for u in usage]
159
153
  usage_json = json.dumps(usage_dict)
160
154
  return self.set_attribute(f"{Config.LIBRARY_NAME}.{ATTRIBUTE.USAGE}", usage_json)
161
155
 
162
- def set_model(self, model: str) -> "Session":
156
+ def set_action(self, action: List[ActionModel]) -> "SpanWrapper":
157
+ """Set the action data as a JSON string."""
158
+ action_dict = [a.model_dump() for a in action]
159
+ action_json = json.dumps(action_dict)
160
+ return self.set_attribute(f"{Config.LIBRARY_NAME}.{ATTRIBUTE.ACTION}", action_json)
161
+
162
+ def set_model(self, model: str) -> "SpanWrapper":
163
163
  """Set the model used."""
164
164
  return self.set_attribute(f"{Config.LIBRARY_NAME}.{ATTRIBUTE.MODEL}", model)
165
165
 
166
- def set_llm_system(self, system: str) -> "Session":
166
+ def set_llm_system(self, system: str) -> "SpanWrapper":
167
167
  """Set the LLM system used."""
168
168
  return self.set_attribute(f"{Config.LIBRARY_NAME}.{ATTRIBUTE.LLM_SYSTEM}", system)
169
169
 
170
- def set_error(self, error_message: str) -> "Session":
170
+ def set_error(self, error_message: str) -> "SpanWrapper":
171
171
  """Manually set an error message."""
172
172
  self.status = "error"
173
173
  self.error_message = error_message
@@ -175,14 +175,14 @@ class Session:
175
175
  self.span.set_status(Status(StatusCode.ERROR, error_message))
176
176
  return self.set_attribute(f"{Config.LIBRARY_NAME}.{ATTRIBUTE.ERROR_MESSAGE}", error_message)
177
177
 
178
- def set_success(self) -> "Session":
179
- """Manually mark the session as successful."""
178
+ def set_success(self) -> "SpanWrapper":
179
+ """Manually mark the span wrapper as successful."""
180
180
  self.status = "success"
181
181
  if self.span:
182
182
  self.span.set_status(Status(StatusCode.OK))
183
183
  return self
184
184
 
185
- def add_event(self, name: str, attributes: Optional[Dict[str, str]] = None) -> "Session":
185
+ def add_event(self, name: str, attributes: Optional[Dict[str, str]] = None) -> "SpanWrapper":
186
186
  """Add an event to the span."""
187
187
  if self.span:
188
188
  self.span.add_event(name, attributes or {})
netra/tracer.py CHANGED
@@ -66,10 +66,9 @@ class Tracer:
66
66
  headers=self.cfg.headers,
67
67
  )
68
68
  # Add span processors for session span processing and data aggregation processing
69
- from netra.processors import ErrorDetectionProcessor, SessionSpanProcessor
69
+ from netra.processors import SessionSpanProcessor
70
70
 
71
71
  provider.add_span_processor(SessionSpanProcessor())
72
- provider.add_span_processor(ErrorDetectionProcessor())
73
72
 
74
73
  # Install appropriate span processor
75
74
  if self.cfg.disable_batch:
netra/version.py CHANGED
@@ -1 +1,2 @@
1
- __version__ = "0.1.6"
1
+ __version__ = "0.1.9"
2
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: netra-sdk
3
- Version: 0.1.6
3
+ Version: 0.1.9
4
4
  Summary: A Python SDK for AI application observability that provides OpenTelemetry-based monitoring, tracing, and PII protection for LLM and vector database applications. Enables easy instrumentation, session tracking, and privacy-focused data collection for AI systems in production environments.
5
5
  License: Apache-2.0
6
6
  Keywords: netra,tracing,observability,sdk,ai,llm,vector,database
@@ -402,46 +402,96 @@ Netra.set_custom_event(event_name="conversion", attributes={
402
402
  "value": 99.99
403
403
  })
404
404
  ```
405
- ## 🔄 Custom Session Tracking
405
+ ## 🔄 Custom Span Tracking
406
406
 
407
- Use the custom session tracking utility to track external API calls with detailed observability:
407
+ Use the custom span tracking utility to track external API calls with detailed observability:
408
408
 
409
409
  ```python
410
- from netra import Netra, Session
411
- from netra.session import UsageModel
412
-
413
- # Start a new session
414
- with Netra.start_session("image_generation") as session:
415
- # Set session attributes
416
- session.set_prompt("A beautiful sunset over mountains")
417
- session.set_negative_prompt("blurry, low quality")
418
- session.set_height("1024")
419
- session.set_width("1024")
420
- session.set_model("dall-e-3")
421
- session.set_llm_system("openai")
410
+ from netra import Netra, UsageModel
411
+
412
+ # Start a new span
413
+ with Netra.start_span("image_generation") as span:
414
+ # Set span attributes
415
+ span.set_prompt("A beautiful sunset over mountains")
416
+ span.set_negative_prompt("blurry, low quality")
417
+ span.set_model("dall-e-3")
418
+ span.set_llm_system("openai")
422
419
 
423
420
  # Set usage data with UsageModel
424
421
  usage_data = [
425
422
  UsageModel(
426
423
  model="dall-e-3",
427
- type="image_generation",
428
- unit_used=1,
424
+ usage_type="image_generation",
425
+ units_used=1,
429
426
  cost_in_usd=0.02
430
427
  )
431
428
  ]
432
- session.set_usage(usage_data)
429
+ span.set_usage(usage_data)
433
430
 
434
431
  # Your API calls here
435
432
  # ...
436
433
 
437
434
  # Set custom attributes
438
- session.set_attribute("custom_key", "custom_value")
435
+ span.set_attribute("custom_key", "custom_value")
439
436
 
440
437
  # Add events
441
- session.add_event("generation_started", {"step": 1, "status": "processing"})
442
- session.add_event("processing_completed", {"step": "rendering"})
438
+ span.add_event("generation_started", {"step": "1", "status": "processing"})
439
+ span.add_event("processing_completed", {"step": "rendering"})
440
+
441
+ # Get the current active open telemetry span
442
+ current_span = span.get_current_span()
443
+
444
+ # Track database operations and other actions
445
+ action = ActionModel(
446
+ action="DB",
447
+ action_type="INSERT",
448
+ affected_records=[
449
+ {"record_id": "user_123", "record_type": "user"},
450
+ {"record_id": "profile_456", "record_type": "profile"}
451
+ ],
452
+ metadata={
453
+ "table": "users",
454
+ "operation_id": "tx_789",
455
+ "duration_ms": "45"
456
+ },
457
+ success=True
458
+ )
459
+ span.set_action([action])
460
+
461
+ # Record API calls
462
+ api_action = ActionModel(
463
+ action="API",
464
+ action_type="CALL",
465
+ metadata={
466
+ "endpoint": "/api/v1/process",
467
+ "method": "POST",
468
+ "status_code": 200,
469
+ "duration_ms": "120"
470
+ },
471
+ success=True
472
+ )
473
+ span.set_action([api_action])
474
+ ```
475
+
476
+ ### Action Tracking Schema
477
+
478
+ Action tracking follows this schema:
443
479
 
444
- # Session automatically captures duration, status, and any errors
480
+ ```python
481
+ [
482
+ {
483
+ "action": str, # Type of action (e.g., "DB", "API", "CACHE")
484
+ "action_type": str, # Action subtype (e.g., "INSERT", "SELECT", "CALL")
485
+ "affected_records": [ # Optional: List of records affected
486
+ {
487
+ "record_id": str, # ID of the affected record
488
+ "record_type": str # Type of the record
489
+ }
490
+ ],
491
+ "metadata": Dict[str, str], # Additional metadata as key-value pairs
492
+ "success": bool # Whether the action succeeded
493
+ }
494
+ ]
445
495
  ```
446
496
 
447
497
  ## 🔧 Advanced Configuration
@@ -1,42 +1,46 @@
1
- netra/__init__.py,sha256=0qOHztSxtkSdA6-6gOT8BAqEMUnFOpFAhz9Sk2hTKRE,4665
1
+ netra/__init__.py,sha256=0w42KiANH31q0sXFL2hLxRjueepfo_jbx2oMDc5miFo,4733
2
2
  netra/anonymizer/__init__.py,sha256=KeGPPZqKVZbtkbirEKYTYhj6aZHlakjdQhD7QHqBRio,133
3
3
  netra/anonymizer/anonymizer.py,sha256=1VeYAsFpF_tYDlqJF-Q82-ZXGOR4YWBqrKUsRw3qOrA,3539
4
4
  netra/anonymizer/base.py,sha256=ytPxHCUD2OXlEY6fNTuMmwImNdIjgj294I41FIgoXpU,5946
5
5
  netra/anonymizer/fp_anonymizer.py,sha256=_6svIYmE0eejdIMkhKBUWCNjGtGimtrGtbLvPSOp8W4,6493
6
- netra/config.py,sha256=6aUG-BWWDOyptnTlEjOpB41n9xLmuo-XBAgs5GE5Tbg,4761
6
+ netra/config.py,sha256=S9GsCvwtakrmryAaV-AhyVB_wAQ6tjwPLLZQemLgXko,5006
7
7
  netra/decorators.py,sha256=V_WpZ2IgW2Y7B_WnSXmKUGGhkM5Cra2TwONddmJpPaI,6837
8
8
  netra/exceptions/__init__.py,sha256=uDgcBxmC4WhdS7HRYQk_TtJyxH1s1o6wZmcsnSHLAcM,174
9
9
  netra/exceptions/injection.py,sha256=ke4eUXRYUFJkMZgdSyPPkPt5PdxToTI6xLEBI0hTWUQ,1332
10
10
  netra/exceptions/pii.py,sha256=MT4p_x-zH3VtYudTSxw1Z9qQZADJDspq64WrYqSWlZc,2438
11
11
  netra/input_scanner.py,sha256=bzP3s7YudGHQrIbUgQGrcIBEJ6CmOewzuYNSu75cVXM,4988
12
- netra/instrumentation/__init__.py,sha256=s-sXykQZ4CKUHLqHRR7buOrkN9hXGTZpNALRZkdIHB0,38757
12
+ netra/instrumentation/__init__.py,sha256=Mz7BlRp4zgKAD3oLBGfBzh5naVVgAP0sLw4ISDydhLw,39567
13
13
  netra/instrumentation/aiohttp/__init__.py,sha256=M1kuF0R3gKY5rlbhEC1AR13UWHelmfokluL2yFysKWc,14398
14
14
  netra/instrumentation/aiohttp/version.py,sha256=Zy-0Aukx-HS_Mo3NKPWg-hlUoWKDzS0w58gLoVtJec8,24
15
15
  netra/instrumentation/cohere/__init__.py,sha256=3XwmCAZwZiMkHdNN3YvcBOLsNCx80ymbU31TyMzv1IY,17685
16
16
  netra/instrumentation/cohere/version.py,sha256=eFXRvO5AgP2DDj5tMt7hkuzi30NOLEqZ9dJpPaHFZLs,23
17
+ netra/instrumentation/fastapi/__init__.py,sha256=v955lLezcI-3toA60RVkTYqlstu-QjMOAbLkM9uHHU4,16611
18
+ netra/instrumentation/fastapi/version.py,sha256=FX3pdiCFGDsISNiq2rT0sQFwNLZ5IGKhiVknXkSE3O0,24
17
19
  netra/instrumentation/google_genai/__init__.py,sha256=470x3o5_NDQHRNT4o-IWnSZw1JjPnAoFSZ-mpaWvAuE,17894
18
20
  netra/instrumentation/google_genai/config.py,sha256=XCyo3mk30qkvqyCqeTrKwROahu0gcOEwmbDLOo53J5k,121
19
21
  netra/instrumentation/google_genai/utils.py,sha256=2OeSN5jUaMKF4x5zWiW65R1LB_a44Roi00isv2vxFME,921
20
22
  netra/instrumentation/google_genai/version.py,sha256=Hww1duZrC8kYK7ThBSQVyz0HNOb0ys_o8Pln-wVQ1hI,23
21
23
  netra/instrumentation/httpx/__init__.py,sha256=w1su_eQP_w5ZJHq0Lf-4miF5zM4OOW0ItmRp0wi85Ew,19388
22
24
  netra/instrumentation/httpx/version.py,sha256=ZRQKbgDaGz_yuLk-cUKuk6ZBKCSRKZC8nQd041NRNXk,23
23
- netra/instrumentation/instruments.py,sha256=M_-4N1YML-Lc1Jb2dIzoHF9sCmsFz4euUvm8a-VKROM,4247
25
+ netra/instrumentation/instruments.py,sha256=_25TCDiBsgqP_fzL4OgwWx2UKD8EnzvLn0WA8t3lB_U,4269
24
26
  netra/instrumentation/mistralai/__init__.py,sha256=RE0b-rS6iXdoynJMFKHL9s97eYo5HghrJa013fR4ZhI,18910
25
27
  netra/instrumentation/mistralai/config.py,sha256=XCyo3mk30qkvqyCqeTrKwROahu0gcOEwmbDLOo53J5k,121
26
28
  netra/instrumentation/mistralai/utils.py,sha256=nhdIer5gJFxuGwg8FCT222hggDHeMQDhJctnDSwLqcc,894
27
29
  netra/instrumentation/mistralai/version.py,sha256=d6593s-XBNvVxri9lr2qLUDZQ3Zk3-VXHEwdb4pj8qA,22
30
+ netra/instrumentation/openai/__init__.py,sha256=HztqLMw8Tf30-Ydqr4N7FcvAwj-5cnGZNqI-S3wIZ_4,5143
31
+ netra/instrumentation/openai/version.py,sha256=_J-N1qG50GykJDM356BSQf0E8LoLbB8AaC3RKho494A,23
32
+ netra/instrumentation/openai/wrappers.py,sha256=hXUgWKAs2_LCKIBnScoIJt_AkHhdQKnZWk7D94UjPGU,20685
28
33
  netra/instrumentation/weaviate/__init__.py,sha256=EOlpWxobOLHYKqo_kMct_7nu26x1hr8qkeG5_h99wtg,4330
29
34
  netra/instrumentation/weaviate/version.py,sha256=PiCZHjonujPbnIn0KmD3Yl68hrjPRG_oKe5vJF3mmG8,24
30
35
  netra/pii.py,sha256=S7GnVzoNJEzKiUWnqN9bOCKPeNLsriztgB2E6Rx-yJU,27023
31
- netra/processors/__init__.py,sha256=G16VumYTpgV4jsWrKNFSgm6xMQAsZ2Rrux25UVeo5YQ,215
32
- netra/processors/error_detection_processor.py,sha256=TtSZoJ7BCMHlVaXWYfqLHSZ6uIx43tdqYb7AFXpt0BA,2898
36
+ netra/processors/__init__.py,sha256=wfnSskRBtMT90hO7LqFJoEW374LgoH_gnTxhynqtByI,109
33
37
  netra/processors/session_span_processor.py,sha256=qcsBl-LnILWefsftI8NQhXDGb94OWPc8LvzhVA0JS_c,2432
34
38
  netra/scanner.py,sha256=wqjMZnEbVvrGMiUSI352grUyHpkk94oBfHfMiXPhpGU,3866
35
- netra/session.py,sha256=o1wXrPzMauqj_3P-iNBHVlcIR7zcKcbsmkrcHjQMKuY,7263
36
39
  netra/session_manager.py,sha256=EVcnWcSj4NdkH--HmqHx0mmzivQiM4GCyFLu6lwi33M,6252
37
- netra/tracer.py,sha256=9jAKdIHXbaZ6WV_p8I1syQiMdqXVCXMhpEhCBsbbci8,3538
38
- netra/version.py,sha256=n3oM6B_EMz93NsTI18NNZd-jKFcUPzUkbIKj5VFK5ok,22
39
- netra_sdk-0.1.6.dist-info/LICENCE,sha256=8B_UoZ-BAl0AqiHAHUETCgd3I2B9yYJ1WEQtVb_qFMA,11359
40
- netra_sdk-0.1.6.dist-info/METADATA,sha256=5iMVB2PTJpnGBVB-JSsBF9AS5OzGUT2heIdfRoOXR1s,23730
41
- netra_sdk-0.1.6.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
42
- netra_sdk-0.1.6.dist-info/RECORD,,
40
+ netra/span_wrapper.py,sha256=MMFuQTqEmQ33pj6qUeIx7EbJJoEz_hzW-4dt1Y2N7s8,7286
41
+ netra/tracer.py,sha256=In5QPVLz_6BxrolWpav9EuR9_hirD2UUIlyY75QUaKk,3450
42
+ netra/version.py,sha256=_sUAMIoa3JJyDFRRMIhTB30E34m3PK89cv561Stxyb4,23
43
+ netra_sdk-0.1.9.dist-info/LICENCE,sha256=8B_UoZ-BAl0AqiHAHUETCgd3I2B9yYJ1WEQtVb_qFMA,11359
44
+ netra_sdk-0.1.9.dist-info/METADATA,sha256=jTsIP9rcBdVgL0x4sBtmEQL4NLWxnl5yHwteWhdiUdQ,25100
45
+ netra_sdk-0.1.9.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
46
+ netra_sdk-0.1.9.dist-info/RECORD,,
@@ -1,84 +0,0 @@
1
- import logging
2
- from typing import Any, Optional, Union
3
-
4
- import httpx
5
- from opentelemetry.sdk.trace import SpanProcessor
6
- from opentelemetry.trace import Context, Span, Status, StatusCode
7
-
8
- from netra import Netra
9
-
10
- logger = logging.getLogger(__name__)
11
-
12
-
13
- class ErrorDetectionProcessor(SpanProcessor): # type: ignore[misc]
14
- """
15
- OpenTelemetry span processor that monitors for error attributes in spans and creates custom events.
16
- """
17
-
18
- def __init__(self) -> None:
19
- pass
20
-
21
- def on_start(self, span: Span, parent_context: Optional[Context] = None) -> None:
22
- """Called when a span starts."""
23
- span_id = self._get_span_id(span)
24
- if not span_id:
25
- return
26
-
27
- # Wrap span methods to capture data
28
- self._wrap_span_methods(span, span_id)
29
-
30
- def on_end(self, span: Span) -> None:
31
- """Called when a span ends."""
32
-
33
- def force_flush(self, timeout_millis: int = 30000) -> bool:
34
- """Force flush any pending data."""
35
- return True
36
-
37
- def shutdown(self) -> bool:
38
- """Shutdown the processor."""
39
- return True
40
-
41
- def _get_span_id(self, span: Span) -> Optional[str]:
42
- """Get a unique identifier for the span."""
43
- try:
44
- span_context = span.get_span_context()
45
- return f"{span_context.trace_id:032x}-{span_context.span_id:016x}"
46
- except Exception:
47
- return None
48
-
49
- def _status_code_processing(self, status_code: int) -> None:
50
- if httpx.codes.is_error(status_code):
51
- event_attributes = {"has_error": True, "status_code": status_code}
52
- Netra.set_custom_event(event_name="error_detected", attributes=event_attributes)
53
-
54
- def _wrap_span_methods(self, span: Span, span_id: str) -> Any:
55
- """Wrap span methods to capture attributes and events."""
56
- # Wrap set_attribute
57
- original_set_attribute = span.set_attribute
58
-
59
- def wrapped_set_attribute(key: str, value: Any) -> Any:
60
- # Status code processing
61
- if key == "http.status_code":
62
- self._status_code_processing(value)
63
-
64
- return original_set_attribute(key, value)
65
-
66
- # Wrap set_status
67
- original_set_status = span.set_status
68
-
69
- def wrapped_set_status(status: Union[Status, StatusCode]) -> Any:
70
- # Check if status code is ERROR
71
- if isinstance(status, Status):
72
- status_code = status.status_code
73
- elif isinstance(status, StatusCode):
74
- status_code = status
75
- if status_code == StatusCode.ERROR:
76
- event_attributes = {
77
- "has_error": True,
78
- }
79
- Netra.set_custom_event(event_name="error_detected", attributes=event_attributes)
80
-
81
- return original_set_status(status)
82
-
83
- span.set_attribute = wrapped_set_attribute
84
- span.set_status = wrapped_set_status