netra-sdk 0.1.0__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 +148 -0
- netra/anonymizer/__init__.py +7 -0
- netra/anonymizer/anonymizer.py +79 -0
- netra/anonymizer/base.py +159 -0
- netra/anonymizer/fp_anonymizer.py +182 -0
- netra/config.py +111 -0
- netra/decorators.py +167 -0
- netra/exceptions/__init__.py +6 -0
- netra/exceptions/injection.py +33 -0
- netra/exceptions/pii.py +46 -0
- netra/input_scanner.py +142 -0
- netra/instrumentation/__init__.py +257 -0
- netra/instrumentation/aiohttp/__init__.py +378 -0
- netra/instrumentation/aiohttp/version.py +1 -0
- netra/instrumentation/cohere/__init__.py +446 -0
- netra/instrumentation/cohere/version.py +1 -0
- netra/instrumentation/google_genai/__init__.py +506 -0
- netra/instrumentation/google_genai/config.py +5 -0
- netra/instrumentation/google_genai/utils.py +31 -0
- netra/instrumentation/google_genai/version.py +1 -0
- netra/instrumentation/httpx/__init__.py +545 -0
- netra/instrumentation/httpx/version.py +1 -0
- netra/instrumentation/instruments.py +78 -0
- netra/instrumentation/mistralai/__init__.py +545 -0
- netra/instrumentation/mistralai/config.py +5 -0
- netra/instrumentation/mistralai/utils.py +30 -0
- netra/instrumentation/mistralai/version.py +1 -0
- netra/instrumentation/weaviate/__init__.py +121 -0
- netra/instrumentation/weaviate/version.py +1 -0
- netra/pii.py +757 -0
- netra/processors/__init__.py +4 -0
- netra/processors/session_span_processor.py +55 -0
- netra/processors/span_aggregation_processor.py +365 -0
- netra/scanner.py +104 -0
- netra/session.py +185 -0
- netra/session_manager.py +96 -0
- netra/tracer.py +99 -0
- netra/version.py +1 -0
- netra_sdk-0.1.0.dist-info/LICENCE +201 -0
- netra_sdk-0.1.0.dist-info/METADATA +573 -0
- netra_sdk-0.1.0.dist-info/RECORD +42 -0
- netra_sdk-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import functools
|
|
5
|
+
import logging
|
|
6
|
+
import types
|
|
7
|
+
from timeit import default_timer
|
|
8
|
+
from typing import Any, Awaitable, Callable, Collection, Dict, Optional, Union
|
|
9
|
+
from urllib.parse import urlparse
|
|
10
|
+
|
|
11
|
+
from aiohttp import ClientRequest, ClientResponse, ClientSession
|
|
12
|
+
from aiohttp.typedefs import URL
|
|
13
|
+
from opentelemetry.instrumentation._semconv import (
|
|
14
|
+
HTTP_DURATION_HISTOGRAM_BUCKETS_NEW,
|
|
15
|
+
HTTP_DURATION_HISTOGRAM_BUCKETS_OLD,
|
|
16
|
+
_client_duration_attrs_new,
|
|
17
|
+
_client_duration_attrs_old,
|
|
18
|
+
_filter_semconv_duration_attrs,
|
|
19
|
+
_get_schema_url,
|
|
20
|
+
_OpenTelemetrySemanticConventionStability,
|
|
21
|
+
_OpenTelemetryStabilitySignalType,
|
|
22
|
+
_report_new,
|
|
23
|
+
_report_old,
|
|
24
|
+
_set_http_host_client,
|
|
25
|
+
_set_http_method,
|
|
26
|
+
_set_http_net_peer_name_client,
|
|
27
|
+
_set_http_network_protocol_version,
|
|
28
|
+
_set_http_peer_port_client,
|
|
29
|
+
_set_http_scheme,
|
|
30
|
+
_set_http_url,
|
|
31
|
+
_set_status,
|
|
32
|
+
_StabilityMode,
|
|
33
|
+
)
|
|
34
|
+
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
|
35
|
+
from opentelemetry.instrumentation.utils import (
|
|
36
|
+
is_http_instrumentation_enabled,
|
|
37
|
+
suppress_http_instrumentation,
|
|
38
|
+
)
|
|
39
|
+
from opentelemetry.metrics import Histogram, get_meter
|
|
40
|
+
from opentelemetry.propagate import inject
|
|
41
|
+
from opentelemetry.semconv.attributes.error_attributes import ERROR_TYPE
|
|
42
|
+
from opentelemetry.semconv.attributes.network_attributes import (
|
|
43
|
+
NETWORK_PEER_ADDRESS,
|
|
44
|
+
NETWORK_PEER_PORT,
|
|
45
|
+
)
|
|
46
|
+
from opentelemetry.semconv.metrics import MetricInstruments
|
|
47
|
+
from opentelemetry.semconv.metrics.http_metrics import (
|
|
48
|
+
HTTP_CLIENT_REQUEST_DURATION,
|
|
49
|
+
)
|
|
50
|
+
from opentelemetry.trace import SpanKind, Tracer, get_tracer
|
|
51
|
+
from opentelemetry.trace.span import Span
|
|
52
|
+
from opentelemetry.util.http import (
|
|
53
|
+
ExcludeList,
|
|
54
|
+
get_excluded_urls,
|
|
55
|
+
parse_excluded_urls,
|
|
56
|
+
remove_url_credentials,
|
|
57
|
+
sanitize_method,
|
|
58
|
+
)
|
|
59
|
+
from opentelemetry.util.http.httplib import set_ip_on_next_http_connection
|
|
60
|
+
|
|
61
|
+
from netra.instrumentation.aiohttp.version import __version__
|
|
62
|
+
|
|
63
|
+
logger = logging.getLogger(__name__)
|
|
64
|
+
|
|
65
|
+
# Package info for aiohttp instrumentation
|
|
66
|
+
_instruments = ("aiohttp >= 3.0.0",)
|
|
67
|
+
|
|
68
|
+
_excluded_urls_from_env = get_excluded_urls("AIOHTTP_CLIENT")
|
|
69
|
+
|
|
70
|
+
_RequestHookT = Optional[Callable[[Span, ClientRequest], Awaitable[None]]]
|
|
71
|
+
_ResponseHookT = Optional[Callable[[Span, ClientRequest, ClientResponse], Awaitable[None]]]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _set_http_status_code_attribute(
|
|
75
|
+
span: Span,
|
|
76
|
+
status_code: Union[int, str],
|
|
77
|
+
metric_attributes: Optional[Dict[str, Any]] = None,
|
|
78
|
+
sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT,
|
|
79
|
+
) -> None:
|
|
80
|
+
status_code_str = str(status_code)
|
|
81
|
+
try:
|
|
82
|
+
status_code_int = int(status_code)
|
|
83
|
+
except ValueError:
|
|
84
|
+
status_code_int = -1
|
|
85
|
+
if metric_attributes is None:
|
|
86
|
+
metric_attributes = {}
|
|
87
|
+
_set_status(
|
|
88
|
+
span,
|
|
89
|
+
metric_attributes,
|
|
90
|
+
status_code_int,
|
|
91
|
+
status_code_str,
|
|
92
|
+
server_span=False,
|
|
93
|
+
sem_conv_opt_in_mode=sem_conv_opt_in_mode,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _instrument(
|
|
98
|
+
tracer: Tracer,
|
|
99
|
+
duration_histogram_old: Optional[Histogram],
|
|
100
|
+
duration_histogram_new: Optional[Histogram],
|
|
101
|
+
request_hook: _RequestHookT = None,
|
|
102
|
+
response_hook: _ResponseHookT = None,
|
|
103
|
+
excluded_urls: Optional[ExcludeList] = None,
|
|
104
|
+
sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT,
|
|
105
|
+
) -> None:
|
|
106
|
+
"""Enables tracing of all aiohttp client requests."""
|
|
107
|
+
|
|
108
|
+
wrapped_request = ClientSession._request
|
|
109
|
+
|
|
110
|
+
@functools.wraps(wrapped_request)
|
|
111
|
+
async def instrumented_request(self: ClientSession, method: str, url: Any, **kwargs: Any) -> ClientResponse:
|
|
112
|
+
if excluded_urls and excluded_urls.url_disabled(str(url)):
|
|
113
|
+
return await wrapped_request(self, method, url, **kwargs)
|
|
114
|
+
|
|
115
|
+
if not is_http_instrumentation_enabled():
|
|
116
|
+
return await wrapped_request(self, method, url, **kwargs)
|
|
117
|
+
|
|
118
|
+
span_name = get_default_span_name(method)
|
|
119
|
+
|
|
120
|
+
url_str = str(url)
|
|
121
|
+
url = remove_url_credentials(url_str)
|
|
122
|
+
|
|
123
|
+
span_attributes: Dict[str, Any] = {}
|
|
124
|
+
_set_http_method(
|
|
125
|
+
span_attributes,
|
|
126
|
+
method,
|
|
127
|
+
sanitize_method(method),
|
|
128
|
+
sem_conv_opt_in_mode,
|
|
129
|
+
)
|
|
130
|
+
_set_http_url(span_attributes, url, sem_conv_opt_in_mode)
|
|
131
|
+
|
|
132
|
+
metric_labels: Dict[str, Any] = {}
|
|
133
|
+
_set_http_method(
|
|
134
|
+
metric_labels,
|
|
135
|
+
method,
|
|
136
|
+
sanitize_method(method),
|
|
137
|
+
sem_conv_opt_in_mode,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
parsed_url = urlparse(url)
|
|
142
|
+
if parsed_url.scheme:
|
|
143
|
+
if _report_old(sem_conv_opt_in_mode):
|
|
144
|
+
_set_http_scheme(metric_labels, parsed_url.scheme, sem_conv_opt_in_mode)
|
|
145
|
+
if parsed_url.hostname:
|
|
146
|
+
_set_http_host_client(metric_labels, parsed_url.hostname, sem_conv_opt_in_mode)
|
|
147
|
+
_set_http_net_peer_name_client(metric_labels, parsed_url.hostname, sem_conv_opt_in_mode)
|
|
148
|
+
if _report_new(sem_conv_opt_in_mode):
|
|
149
|
+
_set_http_host_client(
|
|
150
|
+
span_attributes,
|
|
151
|
+
parsed_url.hostname,
|
|
152
|
+
sem_conv_opt_in_mode,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
span_attributes[NETWORK_PEER_ADDRESS] = parsed_url.hostname
|
|
156
|
+
if parsed_url.port:
|
|
157
|
+
_set_http_peer_port_client(metric_labels, parsed_url.port, sem_conv_opt_in_mode)
|
|
158
|
+
if _report_new(sem_conv_opt_in_mode):
|
|
159
|
+
_set_http_peer_port_client(span_attributes, parsed_url.port, sem_conv_opt_in_mode)
|
|
160
|
+
span_attributes[NETWORK_PEER_PORT] = parsed_url.port
|
|
161
|
+
except ValueError as error:
|
|
162
|
+
logger.error(error)
|
|
163
|
+
|
|
164
|
+
with (
|
|
165
|
+
tracer.start_as_current_span(span_name, kind=SpanKind.CLIENT, attributes=span_attributes) as span,
|
|
166
|
+
set_ip_on_next_http_connection(span),
|
|
167
|
+
):
|
|
168
|
+
exception = None
|
|
169
|
+
response = None
|
|
170
|
+
|
|
171
|
+
headers = kwargs.get("headers", {})
|
|
172
|
+
if headers is None:
|
|
173
|
+
headers = {}
|
|
174
|
+
if hasattr(headers, "items"):
|
|
175
|
+
headers_dict = dict(headers.items())
|
|
176
|
+
else:
|
|
177
|
+
headers_dict = dict(headers)
|
|
178
|
+
|
|
179
|
+
inject(headers_dict)
|
|
180
|
+
kwargs["headers"] = headers_dict
|
|
181
|
+
|
|
182
|
+
with suppress_http_instrumentation():
|
|
183
|
+
start_time = default_timer()
|
|
184
|
+
try:
|
|
185
|
+
response = await wrapped_request(self, method, url_str, **kwargs) # *** PROCEED
|
|
186
|
+
|
|
187
|
+
# Create a ClientRequest object for hooks
|
|
188
|
+
|
|
189
|
+
request_obj = ClientRequest(
|
|
190
|
+
method=method,
|
|
191
|
+
url=URL(url_str),
|
|
192
|
+
headers=headers_dict,
|
|
193
|
+
data=kwargs.get("data"),
|
|
194
|
+
params=kwargs.get("params"),
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
if callable(request_hook):
|
|
198
|
+
await request_hook(span, request_obj)
|
|
199
|
+
|
|
200
|
+
except Exception as exc: # pylint: disable=W0703
|
|
201
|
+
exception = exc
|
|
202
|
+
response = getattr(exc, "response", None)
|
|
203
|
+
finally:
|
|
204
|
+
elapsed_time = max(default_timer() - start_time, 0)
|
|
205
|
+
|
|
206
|
+
if response is not None:
|
|
207
|
+
span_attributes_response: Dict[str, Any] = {}
|
|
208
|
+
_set_http_status_code_attribute(
|
|
209
|
+
span,
|
|
210
|
+
response.status,
|
|
211
|
+
metric_labels,
|
|
212
|
+
sem_conv_opt_in_mode,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# Set HTTP version if available
|
|
216
|
+
if hasattr(response, "version") and response.version:
|
|
217
|
+
version_text = f"{response.version.major}.{response.version.minor}"
|
|
218
|
+
_set_http_network_protocol_version(metric_labels, version_text, sem_conv_opt_in_mode)
|
|
219
|
+
if _report_new(sem_conv_opt_in_mode):
|
|
220
|
+
_set_http_network_protocol_version(
|
|
221
|
+
span_attributes_response,
|
|
222
|
+
version_text,
|
|
223
|
+
sem_conv_opt_in_mode,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
for key, val in span_attributes_response.items():
|
|
227
|
+
span.set_attribute(key, val)
|
|
228
|
+
|
|
229
|
+
if callable(response_hook) and "request_obj" in locals():
|
|
230
|
+
await response_hook(span, request_obj, response)
|
|
231
|
+
|
|
232
|
+
if exception is not None and _report_new(sem_conv_opt_in_mode):
|
|
233
|
+
span.set_attribute(ERROR_TYPE, type(exception).__qualname__)
|
|
234
|
+
metric_labels[ERROR_TYPE] = type(exception).__qualname__
|
|
235
|
+
|
|
236
|
+
if duration_histogram_old is not None:
|
|
237
|
+
duration_attrs_old = _filter_semconv_duration_attrs(
|
|
238
|
+
metric_labels,
|
|
239
|
+
_client_duration_attrs_old,
|
|
240
|
+
_client_duration_attrs_new,
|
|
241
|
+
_StabilityMode.DEFAULT,
|
|
242
|
+
)
|
|
243
|
+
duration_histogram_old.record(
|
|
244
|
+
max(round(elapsed_time * 1000), 0),
|
|
245
|
+
attributes=duration_attrs_old,
|
|
246
|
+
)
|
|
247
|
+
if duration_histogram_new is not None:
|
|
248
|
+
duration_attrs_new = _filter_semconv_duration_attrs(
|
|
249
|
+
metric_labels,
|
|
250
|
+
_client_duration_attrs_old,
|
|
251
|
+
_client_duration_attrs_new,
|
|
252
|
+
_StabilityMode.HTTP,
|
|
253
|
+
)
|
|
254
|
+
duration_histogram_new.record(elapsed_time, attributes=duration_attrs_new)
|
|
255
|
+
|
|
256
|
+
if exception is not None:
|
|
257
|
+
raise exception.with_traceback(exception.__traceback__)
|
|
258
|
+
|
|
259
|
+
return response
|
|
260
|
+
|
|
261
|
+
# Store the attribute on the function object directly
|
|
262
|
+
setattr(instrumented_request, "opentelemetry_instrumentation_aiohttp_applied", True)
|
|
263
|
+
ClientSession._request = instrumented_request
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _uninstrument() -> None:
|
|
267
|
+
"""Disables instrumentation of :code:`aiohttp` client through this module.
|
|
268
|
+
|
|
269
|
+
Note that this only works if no other module also patches aiohttp."""
|
|
270
|
+
_uninstrument_from(ClientSession)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _uninstrument_from(instr_root: Any, restore_as_bound_func: bool = False) -> None:
|
|
274
|
+
for instr_func_name in ("_request",):
|
|
275
|
+
instr_func = getattr(instr_root, instr_func_name)
|
|
276
|
+
if not getattr(
|
|
277
|
+
instr_func,
|
|
278
|
+
"opentelemetry_instrumentation_aiohttp_applied",
|
|
279
|
+
False,
|
|
280
|
+
):
|
|
281
|
+
continue
|
|
282
|
+
|
|
283
|
+
original = instr_func.__wrapped__ # pylint:disable=no-member
|
|
284
|
+
if restore_as_bound_func:
|
|
285
|
+
original = types.MethodType(original, instr_root)
|
|
286
|
+
setattr(instr_root, instr_func_name, original)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def get_default_span_name(method: str) -> str:
|
|
290
|
+
"""
|
|
291
|
+
Default implementation for name_callback, returns HTTP {method_name}.
|
|
292
|
+
https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/http/#name
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
method: string representing HTTP method
|
|
296
|
+
Returns:
|
|
297
|
+
span name
|
|
298
|
+
"""
|
|
299
|
+
method = sanitize_method(method.strip())
|
|
300
|
+
if method == "_OTHER":
|
|
301
|
+
return "HTTP"
|
|
302
|
+
return method
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
class AioHttpClientInstrumentor(BaseInstrumentor): # type: ignore[misc]
|
|
306
|
+
"""An instrumentor for aiohttp client
|
|
307
|
+
See `BaseInstrumentor`
|
|
308
|
+
"""
|
|
309
|
+
|
|
310
|
+
def instrumentation_dependencies(self) -> Collection[str]:
|
|
311
|
+
return _instruments
|
|
312
|
+
|
|
313
|
+
def _instrument(self, **kwargs: Any) -> None:
|
|
314
|
+
"""Instruments aiohttp client module
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
**kwargs: Optional arguments
|
|
318
|
+
``tracer_provider``: a TracerProvider, defaults to global
|
|
319
|
+
``request_hook``: An optional async callback that is invoked right after a span is created.
|
|
320
|
+
``response_hook``: An optional async callback which is invoked right before the span is finished processing a response.
|
|
321
|
+
``excluded_urls``: A string containing a comma-delimited list of regexes used to exclude URLs from tracking
|
|
322
|
+
``duration_histogram_boundaries``: A list of float values representing the explicit bucket boundaries for the duration histogram.
|
|
323
|
+
"""
|
|
324
|
+
semconv_opt_in_mode = _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
|
|
325
|
+
_OpenTelemetryStabilitySignalType.HTTP,
|
|
326
|
+
)
|
|
327
|
+
schema_url = _get_schema_url(semconv_opt_in_mode)
|
|
328
|
+
tracer_provider = kwargs.get("tracer_provider")
|
|
329
|
+
tracer = get_tracer(
|
|
330
|
+
__name__,
|
|
331
|
+
__version__,
|
|
332
|
+
tracer_provider,
|
|
333
|
+
schema_url=schema_url,
|
|
334
|
+
)
|
|
335
|
+
excluded_urls = kwargs.get("excluded_urls")
|
|
336
|
+
meter_provider = kwargs.get("meter_provider")
|
|
337
|
+
duration_histogram_boundaries = kwargs.get("duration_histogram_boundaries")
|
|
338
|
+
meter = get_meter(
|
|
339
|
+
__name__,
|
|
340
|
+
__version__,
|
|
341
|
+
meter_provider,
|
|
342
|
+
schema_url=schema_url,
|
|
343
|
+
)
|
|
344
|
+
duration_histogram_old = None
|
|
345
|
+
if _report_old(semconv_opt_in_mode):
|
|
346
|
+
duration_histogram_old = meter.create_histogram(
|
|
347
|
+
name=MetricInstruments.HTTP_CLIENT_DURATION,
|
|
348
|
+
unit="ms",
|
|
349
|
+
description="measures the duration of the outbound HTTP request",
|
|
350
|
+
explicit_bucket_boundaries_advisory=duration_histogram_boundaries
|
|
351
|
+
or HTTP_DURATION_HISTOGRAM_BUCKETS_OLD,
|
|
352
|
+
)
|
|
353
|
+
duration_histogram_new = None
|
|
354
|
+
if _report_new(semconv_opt_in_mode):
|
|
355
|
+
duration_histogram_new = meter.create_histogram(
|
|
356
|
+
name=HTTP_CLIENT_REQUEST_DURATION,
|
|
357
|
+
unit="s",
|
|
358
|
+
description="Duration of HTTP client requests.",
|
|
359
|
+
explicit_bucket_boundaries_advisory=duration_histogram_boundaries
|
|
360
|
+
or HTTP_DURATION_HISTOGRAM_BUCKETS_NEW,
|
|
361
|
+
)
|
|
362
|
+
_instrument(
|
|
363
|
+
tracer,
|
|
364
|
+
duration_histogram_old,
|
|
365
|
+
duration_histogram_new,
|
|
366
|
+
request_hook=kwargs.get("request_hook"),
|
|
367
|
+
response_hook=kwargs.get("response_hook"),
|
|
368
|
+
excluded_urls=(_excluded_urls_from_env if excluded_urls is None else parse_excluded_urls(excluded_urls)),
|
|
369
|
+
sem_conv_opt_in_mode=semconv_opt_in_mode,
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
def _uninstrument(self, **kwargs: Any) -> None:
|
|
373
|
+
_uninstrument()
|
|
374
|
+
|
|
375
|
+
@staticmethod
|
|
376
|
+
def uninstrument_session(session: ClientSession) -> None:
|
|
377
|
+
"""Disables instrumentation on the session object."""
|
|
378
|
+
_uninstrument_from(session, restore_as_bound_func=True)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "3.12.13"
|