netra-sdk 0.1.26__py3-none-any.whl → 0.1.29__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.
- netra/__init__.py +89 -0
- netra/config.py +18 -0
- netra/decorators.py +15 -0
- netra/session_manager.py +13 -2
- netra/version.py +1 -1
- {netra_sdk-0.1.26.dist-info → netra_sdk-0.1.29.dist-info}/METADATA +17 -2
- {netra_sdk-0.1.26.dist-info → netra_sdk-0.1.29.dist-info}/RECORD +9 -9
- {netra_sdk-0.1.26.dist-info → netra_sdk-0.1.29.dist-info}/LICENCE +0 -0
- {netra_sdk-0.1.26.dist-info → netra_sdk-0.1.29.dist-info}/WHEEL +0 -0
netra/__init__.py
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import atexit
|
|
1
2
|
import logging
|
|
2
3
|
import threading
|
|
3
4
|
from typing import Any, Dict, Optional, Set
|
|
4
5
|
|
|
6
|
+
from opentelemetry import context as context_api
|
|
7
|
+
from opentelemetry import trace
|
|
8
|
+
from opentelemetry.trace import SpanKind
|
|
9
|
+
|
|
5
10
|
from netra.instrumentation.instruments import InstrumentSet, NetraInstruments
|
|
6
11
|
|
|
7
12
|
from .config import Config
|
|
@@ -12,7 +17,10 @@ from .session_manager import SessionManager
|
|
|
12
17
|
from .span_wrapper import ActionModel, SpanWrapper, UsageModel
|
|
13
18
|
from .tracer import Tracer
|
|
14
19
|
|
|
20
|
+
# Package-level logger. Attach NullHandler by default so library does not emit logs
|
|
21
|
+
# unless explicitly enabled by the user via debug_mode.
|
|
15
22
|
logger = logging.getLogger(__name__)
|
|
23
|
+
logger.addHandler(logging.NullHandler())
|
|
16
24
|
|
|
17
25
|
|
|
18
26
|
class Netra:
|
|
@@ -24,6 +32,8 @@ class Netra:
|
|
|
24
32
|
_initialized = False
|
|
25
33
|
# Use RLock so the thread that already owns the lock can re-acquire it safely
|
|
26
34
|
_init_lock = threading.RLock()
|
|
35
|
+
_root_span = None
|
|
36
|
+
_root_ctx_token = None
|
|
27
37
|
|
|
28
38
|
@classmethod
|
|
29
39
|
def is_initialized(cls) -> bool:
|
|
@@ -42,6 +52,8 @@ class Netra:
|
|
|
42
52
|
headers: Optional[str] = None,
|
|
43
53
|
disable_batch: Optional[bool] = None,
|
|
44
54
|
trace_content: Optional[bool] = None,
|
|
55
|
+
debug_mode: Optional[bool] = None,
|
|
56
|
+
enable_root_span: Optional[bool] = None,
|
|
45
57
|
resource_attributes: Optional[Dict[str, Any]] = None,
|
|
46
58
|
environment: Optional[str] = None,
|
|
47
59
|
instruments: Optional[Set[NetraInstruments]] = None,
|
|
@@ -61,10 +73,32 @@ class Netra:
|
|
|
61
73
|
headers=headers,
|
|
62
74
|
disable_batch=disable_batch,
|
|
63
75
|
trace_content=trace_content,
|
|
76
|
+
debug_mode=debug_mode,
|
|
77
|
+
enable_root_span=enable_root_span,
|
|
64
78
|
resource_attributes=resource_attributes,
|
|
65
79
|
environment=environment,
|
|
66
80
|
)
|
|
67
81
|
|
|
82
|
+
# Configure package logging based on debug mode
|
|
83
|
+
pkg_logger = logging.getLogger("netra")
|
|
84
|
+
# Prevent propagating to root to avoid duplicate logs
|
|
85
|
+
pkg_logger.propagate = False
|
|
86
|
+
# Clear existing handlers to avoid duplicates across repeated init attempts
|
|
87
|
+
pkg_logger.handlers.clear()
|
|
88
|
+
if cfg.debug_mode:
|
|
89
|
+
pkg_logger.setLevel(logging.DEBUG)
|
|
90
|
+
handler = logging.StreamHandler()
|
|
91
|
+
formatter = logging.Formatter(
|
|
92
|
+
fmt="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
|
|
93
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
94
|
+
)
|
|
95
|
+
handler.setFormatter(formatter)
|
|
96
|
+
pkg_logger.addHandler(handler)
|
|
97
|
+
else:
|
|
98
|
+
# Silence SDK logs entirely unless debug is enabled
|
|
99
|
+
pkg_logger.setLevel(logging.CRITICAL)
|
|
100
|
+
pkg_logger.addHandler(logging.NullHandler())
|
|
101
|
+
|
|
68
102
|
# Initialize tracer (OTLP exporter, span processor, resource)
|
|
69
103
|
Tracer(cfg)
|
|
70
104
|
|
|
@@ -81,6 +115,61 @@ class Netra:
|
|
|
81
115
|
cls._initialized = True
|
|
82
116
|
logger.info("Netra successfully initialized.")
|
|
83
117
|
|
|
118
|
+
# Create and attach a long-lived root span if enabled
|
|
119
|
+
if cfg.enable_root_span:
|
|
120
|
+
tracer = trace.get_tracer("netra.root.span")
|
|
121
|
+
root_name = f"{Config.LIBRARY_NAME}.root.span"
|
|
122
|
+
root_span = tracer.start_span(root_name, kind=SpanKind.INTERNAL)
|
|
123
|
+
# Add useful attributes
|
|
124
|
+
if cfg.app_name:
|
|
125
|
+
root_span.set_attribute("service.name", cfg.app_name)
|
|
126
|
+
root_span.set_attribute("netra.environment", cfg.environment)
|
|
127
|
+
root_span.set_attribute("netra.library.version", Config.LIBRARY_VERSION)
|
|
128
|
+
|
|
129
|
+
# Attach span to current context so subsequent spans become its children
|
|
130
|
+
ctx = trace.set_span_in_context(root_span)
|
|
131
|
+
token = context_api.attach(ctx)
|
|
132
|
+
|
|
133
|
+
# Save for potential shutdown/cleanup and session tracking
|
|
134
|
+
cls._root_span = root_span
|
|
135
|
+
cls._root_ctx_token = token
|
|
136
|
+
try:
|
|
137
|
+
SessionManager.set_current_span(root_span)
|
|
138
|
+
except Exception:
|
|
139
|
+
pass
|
|
140
|
+
logger.info("Netra root span created and attached to context.")
|
|
141
|
+
|
|
142
|
+
# Ensure cleanup at process exit
|
|
143
|
+
atexit.register(cls.shutdown)
|
|
144
|
+
|
|
145
|
+
@classmethod
|
|
146
|
+
def shutdown(cls) -> None:
|
|
147
|
+
"""Optional cleanup to end the root span and detach context."""
|
|
148
|
+
with cls._init_lock:
|
|
149
|
+
if cls._root_ctx_token is not None:
|
|
150
|
+
try:
|
|
151
|
+
context_api.detach(cls._root_ctx_token)
|
|
152
|
+
except Exception:
|
|
153
|
+
pass
|
|
154
|
+
finally:
|
|
155
|
+
cls._root_ctx_token = None
|
|
156
|
+
if cls._root_span is not None:
|
|
157
|
+
try:
|
|
158
|
+
cls._root_span.end()
|
|
159
|
+
except Exception:
|
|
160
|
+
pass
|
|
161
|
+
finally:
|
|
162
|
+
cls._root_span = None
|
|
163
|
+
# Try to flush and shutdown the tracer provider to ensure export
|
|
164
|
+
try:
|
|
165
|
+
provider = trace.get_tracer_provider()
|
|
166
|
+
if hasattr(provider, "force_flush"):
|
|
167
|
+
provider.force_flush()
|
|
168
|
+
if hasattr(provider, "shutdown"):
|
|
169
|
+
provider.shutdown()
|
|
170
|
+
except Exception:
|
|
171
|
+
pass
|
|
172
|
+
|
|
84
173
|
@classmethod
|
|
85
174
|
def set_session_id(cls, session_id: str) -> None:
|
|
86
175
|
"""
|
netra/config.py
CHANGED
|
@@ -16,6 +16,8 @@ class Config:
|
|
|
16
16
|
- headers: Additional headers (W3C Correlation-Context format)
|
|
17
17
|
- disable_batch: Whether to disable batch span processor (bool)
|
|
18
18
|
- trace_content: Whether to capture prompt/completion content (bool)
|
|
19
|
+
- debug_mode: Whether to enable SDK logging; default False (bool)
|
|
20
|
+
- enable_root_span: Whether to create a process root span; default False (bool)
|
|
19
21
|
- resource_attributes: Custom resource attributes dict (e.g., {'env': 'prod', 'version': '1.0.0'})
|
|
20
22
|
"""
|
|
21
23
|
|
|
@@ -30,6 +32,8 @@ class Config:
|
|
|
30
32
|
headers: Optional[str] = None,
|
|
31
33
|
disable_batch: Optional[bool] = None,
|
|
32
34
|
trace_content: Optional[bool] = None,
|
|
35
|
+
debug_mode: Optional[bool] = None,
|
|
36
|
+
enable_root_span: Optional[bool] = None,
|
|
33
37
|
resource_attributes: Optional[Dict[str, Any]] = None,
|
|
34
38
|
environment: Optional[str] = None,
|
|
35
39
|
):
|
|
@@ -91,12 +95,26 @@ class Config:
|
|
|
91
95
|
else:
|
|
92
96
|
os.environ["TRACELOOP_TRACE_CONTENT"] = "true"
|
|
93
97
|
|
|
98
|
+
# Debug mode: enable SDK logging only when True. Default False.
|
|
99
|
+
if debug_mode is not None:
|
|
100
|
+
self.debug_mode = debug_mode
|
|
101
|
+
else:
|
|
102
|
+
env_dbg = os.getenv("NETRA_DEBUG")
|
|
103
|
+
self.debug_mode = True if (env_dbg is not None and env_dbg.lower() in ("1", "true")) else False
|
|
104
|
+
|
|
94
105
|
# 7. Environment: param override, else env
|
|
95
106
|
if environment is not None:
|
|
96
107
|
self.environment = environment
|
|
97
108
|
else:
|
|
98
109
|
self.environment = os.getenv("NETRA_ENV", "local")
|
|
99
110
|
|
|
111
|
+
# Enable a long-lived root span for the process? Default False.
|
|
112
|
+
if enable_root_span is not None:
|
|
113
|
+
self.enable_root_span = enable_root_span
|
|
114
|
+
else:
|
|
115
|
+
env_root = os.getenv("NETRA_ENABLE_ROOT_SPAN")
|
|
116
|
+
self.enable_root_span = True if (env_root is not None and env_root.lower() in ("1", "true")) else False
|
|
117
|
+
|
|
100
118
|
# Resource attributes: param override, else parse JSON from env, else empty dict
|
|
101
119
|
if resource_attributes is not None:
|
|
102
120
|
self.resource_attributes = resource_attributes
|
netra/decorators.py
CHANGED
|
@@ -176,3 +176,18 @@ def task(
|
|
|
176
176
|
if target is not None:
|
|
177
177
|
return decorator(target)
|
|
178
178
|
return decorator
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def span(
|
|
182
|
+
target: Union[Callable[P, R], C, None] = None, *, name: Optional[str] = None
|
|
183
|
+
) -> Union[Callable[P, R], C, Callable[[Callable[P, R]], Callable[P, R]]]:
|
|
184
|
+
def decorator(obj: Union[Callable[P, R], C]) -> Union[Callable[P, R], C]:
|
|
185
|
+
if inspect.isclass(obj):
|
|
186
|
+
return _wrap_class_methods(cast(C, obj), "span", name)
|
|
187
|
+
else:
|
|
188
|
+
# When obj is a function, it should be type Callable[P, R]
|
|
189
|
+
return _create_function_wrapper(cast(Callable[P, R], obj), "span", name)
|
|
190
|
+
|
|
191
|
+
if target is not None:
|
|
192
|
+
return decorator(target)
|
|
193
|
+
return decorator
|
netra/session_manager.py
CHANGED
|
@@ -26,6 +26,7 @@ class SessionManager:
|
|
|
26
26
|
_workflow_stack: List[str] = []
|
|
27
27
|
_task_stack: List[str] = []
|
|
28
28
|
_agent_stack: List[str] = []
|
|
29
|
+
_span_stack: List[str] = []
|
|
29
30
|
|
|
30
31
|
@classmethod
|
|
31
32
|
def set_current_span(cls, span: Optional[trace.Span]) -> None:
|
|
@@ -53,7 +54,7 @@ class SessionManager:
|
|
|
53
54
|
Push an entity onto the appropriate entity stack.
|
|
54
55
|
|
|
55
56
|
Args:
|
|
56
|
-
entity_type: Type of entity (workflow, task, agent)
|
|
57
|
+
entity_type: Type of entity (workflow, task, agent, span)
|
|
57
58
|
entity_name: Name of the entity
|
|
58
59
|
"""
|
|
59
60
|
if entity_type == "workflow":
|
|
@@ -62,6 +63,8 @@ class SessionManager:
|
|
|
62
63
|
cls._task_stack.append(entity_name)
|
|
63
64
|
elif entity_type == "agent":
|
|
64
65
|
cls._agent_stack.append(entity_name)
|
|
66
|
+
elif entity_type == "span":
|
|
67
|
+
cls._span_stack.append(entity_name)
|
|
65
68
|
|
|
66
69
|
@classmethod
|
|
67
70
|
def pop_entity(cls, entity_type: str) -> Optional[str]:
|
|
@@ -69,7 +72,7 @@ class SessionManager:
|
|
|
69
72
|
Pop the most recent entity from the specified entity stack.
|
|
70
73
|
|
|
71
74
|
Args:
|
|
72
|
-
entity_type: Type of entity (workflow, task, agent)
|
|
75
|
+
entity_type: Type of entity (workflow, task, agent, span)
|
|
73
76
|
|
|
74
77
|
Returns:
|
|
75
78
|
Entity name or None if stack is empty
|
|
@@ -80,6 +83,8 @@ class SessionManager:
|
|
|
80
83
|
return cls._task_stack.pop()
|
|
81
84
|
elif entity_type == "agent" and cls._agent_stack:
|
|
82
85
|
return cls._agent_stack.pop()
|
|
86
|
+
elif entity_type == "span" and cls._span_stack:
|
|
87
|
+
return cls._span_stack.pop()
|
|
83
88
|
return None
|
|
84
89
|
|
|
85
90
|
@classmethod
|
|
@@ -104,6 +109,10 @@ class SessionManager:
|
|
|
104
109
|
if cls._agent_stack:
|
|
105
110
|
attributes[f"{Config.LIBRARY_NAME}.agent.name"] = cls._agent_stack[-1]
|
|
106
111
|
|
|
112
|
+
# Add current span if exists
|
|
113
|
+
if cls._span_stack:
|
|
114
|
+
attributes[f"{Config.LIBRARY_NAME}.span.name"] = cls._span_stack[-1]
|
|
115
|
+
|
|
107
116
|
return attributes
|
|
108
117
|
|
|
109
118
|
@classmethod
|
|
@@ -112,6 +121,7 @@ class SessionManager:
|
|
|
112
121
|
cls._workflow_stack.clear()
|
|
113
122
|
cls._task_stack.clear()
|
|
114
123
|
cls._agent_stack.clear()
|
|
124
|
+
cls._span_stack.clear()
|
|
115
125
|
|
|
116
126
|
@classmethod
|
|
117
127
|
def get_stack_info(cls) -> Dict[str, List[str]]:
|
|
@@ -125,6 +135,7 @@ class SessionManager:
|
|
|
125
135
|
"workflows": cls._workflow_stack.copy(),
|
|
126
136
|
"tasks": cls._task_stack.copy(),
|
|
127
137
|
"agents": cls._agent_stack.copy(),
|
|
138
|
+
"spans": cls._span_stack.copy(),
|
|
128
139
|
}
|
|
129
140
|
|
|
130
141
|
@staticmethod
|
netra/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.1.
|
|
1
|
+
__version__ = "0.1.29"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: netra-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.29
|
|
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
|
|
@@ -173,7 +173,7 @@ Netra.init(
|
|
|
173
173
|
Use decorators to automatically trace your functions and classes:
|
|
174
174
|
|
|
175
175
|
```python
|
|
176
|
-
from netra.decorators import workflow, agent, task
|
|
176
|
+
from netra.decorators import workflow, agent, task, span
|
|
177
177
|
|
|
178
178
|
@workflow
|
|
179
179
|
def data_processing_workflow(data):
|
|
@@ -191,6 +191,11 @@ def data_validation_task(data):
|
|
|
191
191
|
"""Task for validating input data"""
|
|
192
192
|
return validate_schema(data)
|
|
193
193
|
|
|
194
|
+
@span
|
|
195
|
+
def data_processing_span(data):
|
|
196
|
+
"""Span for processing data"""
|
|
197
|
+
return process_data(data)
|
|
198
|
+
|
|
194
199
|
# Works with async functions too
|
|
195
200
|
@workflow(name="Async Data Pipeline")
|
|
196
201
|
async def async_workflow(data):
|
|
@@ -205,6 +210,16 @@ class CustomerSupportAgent:
|
|
|
205
210
|
|
|
206
211
|
def escalate_issue(self, issue):
|
|
207
212
|
return self.forward_to_human(issue)
|
|
213
|
+
|
|
214
|
+
@task
|
|
215
|
+
async def async_task(data):
|
|
216
|
+
"""Task for processing data"""
|
|
217
|
+
return await process_data_async(data)
|
|
218
|
+
|
|
219
|
+
@span
|
|
220
|
+
async def async_span(data):
|
|
221
|
+
"""Span for processing data"""
|
|
222
|
+
return await process_data_async(data)
|
|
208
223
|
```
|
|
209
224
|
|
|
210
225
|
## 🔍 Supported Instrumentations
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
netra/__init__.py,sha256=
|
|
1
|
+
netra/__init__.py,sha256=EIMbq0ycEIolmyk7uGXkmgvydfWArNk-soBIEQijsiY,9612
|
|
2
2
|
netra/anonymizer/__init__.py,sha256=KeGPPZqKVZbtkbirEKYTYhj6aZHlakjdQhD7QHqBRio,133
|
|
3
3
|
netra/anonymizer/anonymizer.py,sha256=IcrYkdwWrFauGWUeAW-0RwrSUM8VSZCFNtoywZhvIqU,3778
|
|
4
4
|
netra/anonymizer/base.py,sha256=ytPxHCUD2OXlEY6fNTuMmwImNdIjgj294I41FIgoXpU,5946
|
|
5
5
|
netra/anonymizer/fp_anonymizer.py,sha256=_6svIYmE0eejdIMkhKBUWCNjGtGimtrGtbLvPSOp8W4,6493
|
|
6
|
-
netra/config.py,sha256=
|
|
7
|
-
netra/decorators.py,sha256=
|
|
6
|
+
netra/config.py,sha256=zmTssfUExG8viSMKN9Yfd8i--7S4oUQT8IyvwAI4C2U,5950
|
|
7
|
+
netra/decorators.py,sha256=TqORBs0Xv_CZHOAwrWkpvscMj4FgOJsgo1CRYoG6C7Y,7435
|
|
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
|
|
@@ -40,11 +40,11 @@ netra/pii.py,sha256=Rn4SjgTJW_aw9LcbjLuMqF3fKd9b1ndlYt1CaK51Ge0,33125
|
|
|
40
40
|
netra/processors/__init__.py,sha256=wfnSskRBtMT90hO7LqFJoEW374LgoH_gnTxhynqtByI,109
|
|
41
41
|
netra/processors/session_span_processor.py,sha256=qcsBl-LnILWefsftI8NQhXDGb94OWPc8LvzhVA0JS_c,2432
|
|
42
42
|
netra/scanner.py,sha256=kyDpeZiscCPb6pjuhS-sfsVj-dviBFRepdUWh0sLoEY,11554
|
|
43
|
-
netra/session_manager.py,sha256=
|
|
43
|
+
netra/session_manager.py,sha256=Ks8A6B9RYe0tV8PeWYuN0M-UMZqa47uquuw6D7C1vCE,6701
|
|
44
44
|
netra/span_wrapper.py,sha256=ec2WLYTRLZ02WSSCYEsMn1PgUGVji9rFyq_CRCV9rog,7388
|
|
45
45
|
netra/tracer.py,sha256=In5QPVLz_6BxrolWpav9EuR9_hirD2UUIlyY75QUaKk,3450
|
|
46
|
-
netra/version.py,sha256=
|
|
47
|
-
netra_sdk-0.1.
|
|
48
|
-
netra_sdk-0.1.
|
|
49
|
-
netra_sdk-0.1.
|
|
50
|
-
netra_sdk-0.1.
|
|
46
|
+
netra/version.py,sha256=A-lFHZ4YpCrWZ6nw3tlt_yurFJ00mInm3gR6hz51Eww,23
|
|
47
|
+
netra_sdk-0.1.29.dist-info/LICENCE,sha256=8B_UoZ-BAl0AqiHAHUETCgd3I2B9yYJ1WEQtVb_qFMA,11359
|
|
48
|
+
netra_sdk-0.1.29.dist-info/METADATA,sha256=XKGT69ygEEA6hbSinO1heKikakeIngiMqotgwQbFyeQ,28151
|
|
49
|
+
netra_sdk-0.1.29.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
50
|
+
netra_sdk-0.1.29.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|