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,545 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
import logging
|
|
5
|
+
import types
|
|
6
|
+
from timeit import default_timer
|
|
7
|
+
from typing import Any, Callable, Collection, Dict, Optional, Union
|
|
8
|
+
from urllib.parse import urlparse
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
from opentelemetry.instrumentation._semconv import (
|
|
12
|
+
HTTP_DURATION_HISTOGRAM_BUCKETS_NEW,
|
|
13
|
+
HTTP_DURATION_HISTOGRAM_BUCKETS_OLD,
|
|
14
|
+
_client_duration_attrs_new,
|
|
15
|
+
_client_duration_attrs_old,
|
|
16
|
+
_filter_semconv_duration_attrs,
|
|
17
|
+
_get_schema_url,
|
|
18
|
+
_OpenTelemetrySemanticConventionStability,
|
|
19
|
+
_OpenTelemetryStabilitySignalType,
|
|
20
|
+
_report_new,
|
|
21
|
+
_report_old,
|
|
22
|
+
_set_http_host_client,
|
|
23
|
+
_set_http_method,
|
|
24
|
+
_set_http_net_peer_name_client,
|
|
25
|
+
_set_http_network_protocol_version,
|
|
26
|
+
_set_http_peer_port_client,
|
|
27
|
+
_set_http_scheme,
|
|
28
|
+
_set_http_url,
|
|
29
|
+
_set_status,
|
|
30
|
+
_StabilityMode,
|
|
31
|
+
)
|
|
32
|
+
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
|
33
|
+
from opentelemetry.instrumentation.utils import (
|
|
34
|
+
is_http_instrumentation_enabled,
|
|
35
|
+
suppress_http_instrumentation,
|
|
36
|
+
)
|
|
37
|
+
from opentelemetry.metrics import Histogram, get_meter
|
|
38
|
+
from opentelemetry.propagate import inject
|
|
39
|
+
from opentelemetry.semconv.attributes.error_attributes import ERROR_TYPE
|
|
40
|
+
from opentelemetry.semconv.attributes.network_attributes import (
|
|
41
|
+
NETWORK_PEER_ADDRESS,
|
|
42
|
+
NETWORK_PEER_PORT,
|
|
43
|
+
)
|
|
44
|
+
from opentelemetry.semconv.metrics import MetricInstruments
|
|
45
|
+
from opentelemetry.semconv.metrics.http_metrics import (
|
|
46
|
+
HTTP_CLIENT_REQUEST_DURATION,
|
|
47
|
+
)
|
|
48
|
+
from opentelemetry.trace import SpanKind, Tracer, get_tracer
|
|
49
|
+
from opentelemetry.trace.span import Span
|
|
50
|
+
from opentelemetry.util.http import (
|
|
51
|
+
ExcludeList,
|
|
52
|
+
get_excluded_urls,
|
|
53
|
+
parse_excluded_urls,
|
|
54
|
+
remove_url_credentials,
|
|
55
|
+
sanitize_method,
|
|
56
|
+
)
|
|
57
|
+
from opentelemetry.util.http.httplib import set_ip_on_next_http_connection
|
|
58
|
+
|
|
59
|
+
from netra.instrumentation.httpx.version import __version__
|
|
60
|
+
|
|
61
|
+
logger = logging.getLogger(__name__)
|
|
62
|
+
|
|
63
|
+
# Package info for httpx instrumentation
|
|
64
|
+
_instruments = ("httpx >= 0.18.0",)
|
|
65
|
+
|
|
66
|
+
_excluded_urls_from_env = get_excluded_urls("HTTPX")
|
|
67
|
+
|
|
68
|
+
_RequestHookT = Optional[Callable[[Span, httpx.Request], None]]
|
|
69
|
+
_ResponseHookT = Optional[Callable[[Span, httpx.Request, httpx.Response], None]]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _set_http_status_code_attribute(
|
|
73
|
+
span: Span,
|
|
74
|
+
status_code: Union[int, str],
|
|
75
|
+
metric_attributes: Optional[Dict[str, Any]] = None,
|
|
76
|
+
sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT,
|
|
77
|
+
) -> None:
|
|
78
|
+
status_code_str = str(status_code)
|
|
79
|
+
try:
|
|
80
|
+
status_code_int = int(status_code)
|
|
81
|
+
except ValueError:
|
|
82
|
+
status_code_int = -1
|
|
83
|
+
if metric_attributes is None:
|
|
84
|
+
metric_attributes = {}
|
|
85
|
+
_set_status(
|
|
86
|
+
span,
|
|
87
|
+
metric_attributes,
|
|
88
|
+
status_code_int,
|
|
89
|
+
status_code_str,
|
|
90
|
+
server_span=False,
|
|
91
|
+
sem_conv_opt_in_mode=sem_conv_opt_in_mode,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _instrument(
|
|
96
|
+
tracer: Tracer,
|
|
97
|
+
duration_histogram_old: Optional[Histogram],
|
|
98
|
+
duration_histogram_new: Optional[Histogram],
|
|
99
|
+
request_hook: _RequestHookT = None,
|
|
100
|
+
response_hook: _ResponseHookT = None,
|
|
101
|
+
excluded_urls: Optional[ExcludeList] = None,
|
|
102
|
+
sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT,
|
|
103
|
+
) -> None:
|
|
104
|
+
"""Enables tracing of all httpx calls that go through
|
|
105
|
+
:code:`httpx.Client.send` and :code:`httpx.AsyncClient.send`."""
|
|
106
|
+
|
|
107
|
+
# Instrument sync client
|
|
108
|
+
wrapped_send = httpx.Client.send
|
|
109
|
+
|
|
110
|
+
@functools.wraps(wrapped_send)
|
|
111
|
+
def instrumented_send(self: httpx.Client, request: httpx.Request, **kwargs: Any) -> httpx.Response:
|
|
112
|
+
if excluded_urls and excluded_urls.url_disabled(str(request.url)):
|
|
113
|
+
return wrapped_send(self, request, **kwargs)
|
|
114
|
+
|
|
115
|
+
if not is_http_instrumentation_enabled():
|
|
116
|
+
return wrapped_send(self, request, **kwargs)
|
|
117
|
+
|
|
118
|
+
return _trace_request(
|
|
119
|
+
tracer,
|
|
120
|
+
duration_histogram_old,
|
|
121
|
+
duration_histogram_new,
|
|
122
|
+
request,
|
|
123
|
+
wrapped_send,
|
|
124
|
+
self,
|
|
125
|
+
request_hook,
|
|
126
|
+
response_hook,
|
|
127
|
+
sem_conv_opt_in_mode,
|
|
128
|
+
**kwargs,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# Type ignore for dynamic attribute assignment
|
|
132
|
+
instrumented_send.opentelemetry_instrumentation_httpx_applied = True # type: ignore
|
|
133
|
+
httpx.Client.send = instrumented_send
|
|
134
|
+
|
|
135
|
+
# Instrument async client
|
|
136
|
+
wrapped_async_send = httpx.AsyncClient.send
|
|
137
|
+
|
|
138
|
+
@functools.wraps(wrapped_async_send)
|
|
139
|
+
async def instrumented_async_send(self: httpx.AsyncClient, request: httpx.Request, **kwargs: Any) -> httpx.Response:
|
|
140
|
+
if excluded_urls and excluded_urls.url_disabled(str(request.url)):
|
|
141
|
+
return await wrapped_async_send(self, request, **kwargs)
|
|
142
|
+
|
|
143
|
+
if not is_http_instrumentation_enabled():
|
|
144
|
+
return await wrapped_async_send(self, request, **kwargs)
|
|
145
|
+
|
|
146
|
+
return await _trace_async_request(
|
|
147
|
+
tracer,
|
|
148
|
+
duration_histogram_old,
|
|
149
|
+
duration_histogram_new,
|
|
150
|
+
request,
|
|
151
|
+
wrapped_async_send,
|
|
152
|
+
self,
|
|
153
|
+
request_hook,
|
|
154
|
+
response_hook,
|
|
155
|
+
sem_conv_opt_in_mode,
|
|
156
|
+
**kwargs,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# Type ignore for dynamic attribute assignment
|
|
160
|
+
instrumented_async_send.opentelemetry_instrumentation_httpx_applied = True # type: ignore
|
|
161
|
+
httpx.AsyncClient.send = instrumented_async_send
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _trace_request(
|
|
165
|
+
tracer: Tracer,
|
|
166
|
+
duration_histogram_old: Optional[Histogram],
|
|
167
|
+
duration_histogram_new: Optional[Histogram],
|
|
168
|
+
request: httpx.Request,
|
|
169
|
+
send_func: Callable[..., httpx.Response],
|
|
170
|
+
client: httpx.Client,
|
|
171
|
+
request_hook: _RequestHookT,
|
|
172
|
+
response_hook: _ResponseHookT,
|
|
173
|
+
sem_conv_opt_in_mode: _StabilityMode,
|
|
174
|
+
**kwargs: Any,
|
|
175
|
+
) -> httpx.Response:
|
|
176
|
+
"""Trace a synchronous HTTP request."""
|
|
177
|
+
method = request.method
|
|
178
|
+
span_name = get_default_span_name(method)
|
|
179
|
+
url = remove_url_credentials(str(request.url))
|
|
180
|
+
|
|
181
|
+
span_attributes: Dict[str, Any] = {}
|
|
182
|
+
_set_http_method(
|
|
183
|
+
span_attributes,
|
|
184
|
+
method,
|
|
185
|
+
sanitize_method(method),
|
|
186
|
+
sem_conv_opt_in_mode,
|
|
187
|
+
)
|
|
188
|
+
_set_http_url(span_attributes, url, sem_conv_opt_in_mode)
|
|
189
|
+
|
|
190
|
+
metric_labels: Dict[str, Any] = {}
|
|
191
|
+
_set_http_method(
|
|
192
|
+
metric_labels,
|
|
193
|
+
method,
|
|
194
|
+
sanitize_method(method),
|
|
195
|
+
sem_conv_opt_in_mode,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
parsed_url = urlparse(url)
|
|
200
|
+
if parsed_url.scheme:
|
|
201
|
+
if _report_old(sem_conv_opt_in_mode):
|
|
202
|
+
_set_http_scheme(metric_labels, parsed_url.scheme, sem_conv_opt_in_mode)
|
|
203
|
+
if parsed_url.hostname:
|
|
204
|
+
_set_http_host_client(metric_labels, parsed_url.hostname, sem_conv_opt_in_mode)
|
|
205
|
+
_set_http_net_peer_name_client(metric_labels, parsed_url.hostname, sem_conv_opt_in_mode)
|
|
206
|
+
if _report_new(sem_conv_opt_in_mode):
|
|
207
|
+
_set_http_host_client(
|
|
208
|
+
span_attributes,
|
|
209
|
+
parsed_url.hostname,
|
|
210
|
+
sem_conv_opt_in_mode,
|
|
211
|
+
)
|
|
212
|
+
span_attributes[NETWORK_PEER_ADDRESS] = parsed_url.hostname
|
|
213
|
+
if parsed_url.port:
|
|
214
|
+
_set_http_peer_port_client(metric_labels, parsed_url.port, sem_conv_opt_in_mode)
|
|
215
|
+
if _report_new(sem_conv_opt_in_mode):
|
|
216
|
+
_set_http_peer_port_client(span_attributes, parsed_url.port, sem_conv_opt_in_mode)
|
|
217
|
+
span_attributes[NETWORK_PEER_PORT] = parsed_url.port
|
|
218
|
+
except ValueError as error:
|
|
219
|
+
logger.error(error)
|
|
220
|
+
|
|
221
|
+
with (
|
|
222
|
+
tracer.start_as_current_span(span_name, kind=SpanKind.CLIENT, attributes=span_attributes) as span,
|
|
223
|
+
set_ip_on_next_http_connection(span),
|
|
224
|
+
):
|
|
225
|
+
exception = None
|
|
226
|
+
if callable(request_hook):
|
|
227
|
+
request_hook(span, request)
|
|
228
|
+
|
|
229
|
+
headers = dict(request.headers)
|
|
230
|
+
inject(headers)
|
|
231
|
+
request.headers.update(headers)
|
|
232
|
+
|
|
233
|
+
with suppress_http_instrumentation():
|
|
234
|
+
start_time = default_timer()
|
|
235
|
+
try:
|
|
236
|
+
result = send_func(client, request, **kwargs)
|
|
237
|
+
except Exception as exc:
|
|
238
|
+
exception = exc
|
|
239
|
+
result = getattr(exc, "response", None)
|
|
240
|
+
finally:
|
|
241
|
+
elapsed_time = max(default_timer() - start_time, 0)
|
|
242
|
+
|
|
243
|
+
if isinstance(result, httpx.Response):
|
|
244
|
+
span_attributes_response: Dict[str, Any] = {}
|
|
245
|
+
_set_http_status_code_attribute(
|
|
246
|
+
span,
|
|
247
|
+
result.status_code,
|
|
248
|
+
metric_labels,
|
|
249
|
+
sem_conv_opt_in_mode,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
if hasattr(result, "http_version"):
|
|
253
|
+
version_text = result.http_version
|
|
254
|
+
_set_http_network_protocol_version(metric_labels, version_text, sem_conv_opt_in_mode)
|
|
255
|
+
if _report_new(sem_conv_opt_in_mode):
|
|
256
|
+
_set_http_network_protocol_version(
|
|
257
|
+
span_attributes_response,
|
|
258
|
+
version_text,
|
|
259
|
+
sem_conv_opt_in_mode,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
for key, val in span_attributes_response.items():
|
|
263
|
+
span.set_attribute(key, val)
|
|
264
|
+
|
|
265
|
+
if callable(response_hook):
|
|
266
|
+
response_hook(span, request, result)
|
|
267
|
+
|
|
268
|
+
if exception is not None and _report_new(sem_conv_opt_in_mode):
|
|
269
|
+
span.set_attribute(ERROR_TYPE, type(exception).__qualname__)
|
|
270
|
+
metric_labels[ERROR_TYPE] = type(exception).__qualname__
|
|
271
|
+
|
|
272
|
+
_record_duration_metrics(
|
|
273
|
+
duration_histogram_old,
|
|
274
|
+
duration_histogram_new,
|
|
275
|
+
elapsed_time,
|
|
276
|
+
metric_labels,
|
|
277
|
+
sem_conv_opt_in_mode,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
if exception is not None:
|
|
281
|
+
raise exception.with_traceback(exception.__traceback__)
|
|
282
|
+
|
|
283
|
+
return result
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
async def _trace_async_request(
|
|
287
|
+
tracer: Tracer,
|
|
288
|
+
duration_histogram_old: Optional[Histogram],
|
|
289
|
+
duration_histogram_new: Optional[Histogram],
|
|
290
|
+
request: httpx.Request,
|
|
291
|
+
send_func: Callable[..., Any],
|
|
292
|
+
client: httpx.AsyncClient,
|
|
293
|
+
request_hook: _RequestHookT,
|
|
294
|
+
response_hook: _ResponseHookT,
|
|
295
|
+
sem_conv_opt_in_mode: _StabilityMode,
|
|
296
|
+
**kwargs: Any,
|
|
297
|
+
) -> httpx.Response:
|
|
298
|
+
"""Trace an asynchronous HTTP request."""
|
|
299
|
+
method = request.method
|
|
300
|
+
span_name = get_default_span_name(method)
|
|
301
|
+
url = remove_url_credentials(str(request.url))
|
|
302
|
+
|
|
303
|
+
span_attributes: Dict[str, Any] = {}
|
|
304
|
+
_set_http_method(
|
|
305
|
+
span_attributes,
|
|
306
|
+
method,
|
|
307
|
+
sanitize_method(method),
|
|
308
|
+
sem_conv_opt_in_mode,
|
|
309
|
+
)
|
|
310
|
+
_set_http_url(span_attributes, url, sem_conv_opt_in_mode)
|
|
311
|
+
|
|
312
|
+
metric_labels: Dict[str, Any] = {}
|
|
313
|
+
_set_http_method(
|
|
314
|
+
metric_labels,
|
|
315
|
+
method,
|
|
316
|
+
sanitize_method(method),
|
|
317
|
+
sem_conv_opt_in_mode,
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
try:
|
|
321
|
+
parsed_url = urlparse(url)
|
|
322
|
+
if parsed_url.scheme:
|
|
323
|
+
if _report_old(sem_conv_opt_in_mode):
|
|
324
|
+
_set_http_scheme(metric_labels, parsed_url.scheme, sem_conv_opt_in_mode)
|
|
325
|
+
if parsed_url.hostname:
|
|
326
|
+
_set_http_host_client(metric_labels, parsed_url.hostname, sem_conv_opt_in_mode)
|
|
327
|
+
_set_http_net_peer_name_client(metric_labels, parsed_url.hostname, sem_conv_opt_in_mode)
|
|
328
|
+
if _report_new(sem_conv_opt_in_mode):
|
|
329
|
+
_set_http_host_client(
|
|
330
|
+
span_attributes,
|
|
331
|
+
parsed_url.hostname,
|
|
332
|
+
sem_conv_opt_in_mode,
|
|
333
|
+
)
|
|
334
|
+
span_attributes[NETWORK_PEER_ADDRESS] = parsed_url.hostname
|
|
335
|
+
if parsed_url.port:
|
|
336
|
+
_set_http_peer_port_client(metric_labels, parsed_url.port, sem_conv_opt_in_mode)
|
|
337
|
+
if _report_new(sem_conv_opt_in_mode):
|
|
338
|
+
_set_http_peer_port_client(span_attributes, parsed_url.port, sem_conv_opt_in_mode)
|
|
339
|
+
span_attributes[NETWORK_PEER_PORT] = parsed_url.port
|
|
340
|
+
except ValueError as error:
|
|
341
|
+
logger.error(error)
|
|
342
|
+
|
|
343
|
+
with (
|
|
344
|
+
tracer.start_as_current_span(span_name, kind=SpanKind.CLIENT, attributes=span_attributes) as span,
|
|
345
|
+
set_ip_on_next_http_connection(span),
|
|
346
|
+
):
|
|
347
|
+
exception = None
|
|
348
|
+
if callable(request_hook):
|
|
349
|
+
request_hook(span, request)
|
|
350
|
+
|
|
351
|
+
headers = dict(request.headers)
|
|
352
|
+
inject(headers)
|
|
353
|
+
request.headers.update(headers)
|
|
354
|
+
|
|
355
|
+
with suppress_http_instrumentation():
|
|
356
|
+
start_time = default_timer()
|
|
357
|
+
try:
|
|
358
|
+
result = await send_func(client, request, **kwargs)
|
|
359
|
+
except Exception as exc:
|
|
360
|
+
exception = exc
|
|
361
|
+
result = getattr(exc, "response", None)
|
|
362
|
+
finally:
|
|
363
|
+
elapsed_time = max(default_timer() - start_time, 0)
|
|
364
|
+
|
|
365
|
+
if isinstance(result, httpx.Response):
|
|
366
|
+
span_attributes_response: Dict[str, Any] = {}
|
|
367
|
+
_set_http_status_code_attribute(
|
|
368
|
+
span,
|
|
369
|
+
result.status_code,
|
|
370
|
+
metric_labels,
|
|
371
|
+
sem_conv_opt_in_mode,
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
if hasattr(result, "http_version"):
|
|
375
|
+
version_text = result.http_version
|
|
376
|
+
_set_http_network_protocol_version(metric_labels, version_text, sem_conv_opt_in_mode)
|
|
377
|
+
if _report_new(sem_conv_opt_in_mode):
|
|
378
|
+
_set_http_network_protocol_version(
|
|
379
|
+
span_attributes_response,
|
|
380
|
+
version_text,
|
|
381
|
+
sem_conv_opt_in_mode,
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
for key, val in span_attributes_response.items():
|
|
385
|
+
span.set_attribute(key, val)
|
|
386
|
+
|
|
387
|
+
if callable(response_hook):
|
|
388
|
+
response_hook(span, request, result)
|
|
389
|
+
|
|
390
|
+
if exception is not None and _report_new(sem_conv_opt_in_mode):
|
|
391
|
+
span.set_attribute(ERROR_TYPE, type(exception).__qualname__)
|
|
392
|
+
metric_labels[ERROR_TYPE] = type(exception).__qualname__
|
|
393
|
+
|
|
394
|
+
_record_duration_metrics(
|
|
395
|
+
duration_histogram_old,
|
|
396
|
+
duration_histogram_new,
|
|
397
|
+
elapsed_time,
|
|
398
|
+
metric_labels,
|
|
399
|
+
sem_conv_opt_in_mode,
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
if exception is not None:
|
|
403
|
+
raise exception.with_traceback(exception.__traceback__)
|
|
404
|
+
|
|
405
|
+
return result
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def _record_duration_metrics(
|
|
409
|
+
duration_histogram_old: Optional[Histogram],
|
|
410
|
+
duration_histogram_new: Optional[Histogram],
|
|
411
|
+
elapsed_time: float,
|
|
412
|
+
metric_labels: Dict[str, Any],
|
|
413
|
+
sem_conv_opt_in_mode: _StabilityMode,
|
|
414
|
+
) -> None:
|
|
415
|
+
"""Record duration metrics for HTTP requests."""
|
|
416
|
+
if duration_histogram_old is not None:
|
|
417
|
+
duration_attrs_old = _filter_semconv_duration_attrs(
|
|
418
|
+
metric_labels,
|
|
419
|
+
_client_duration_attrs_old,
|
|
420
|
+
_client_duration_attrs_new,
|
|
421
|
+
_StabilityMode.DEFAULT,
|
|
422
|
+
)
|
|
423
|
+
duration_histogram_old.record(
|
|
424
|
+
max(round(elapsed_time * 1000), 0),
|
|
425
|
+
attributes=duration_attrs_old,
|
|
426
|
+
)
|
|
427
|
+
if duration_histogram_new is not None:
|
|
428
|
+
duration_attrs_new = _filter_semconv_duration_attrs(
|
|
429
|
+
metric_labels,
|
|
430
|
+
_client_duration_attrs_old,
|
|
431
|
+
_client_duration_attrs_new,
|
|
432
|
+
_StabilityMode.HTTP,
|
|
433
|
+
)
|
|
434
|
+
duration_histogram_new.record(elapsed_time, attributes=duration_attrs_new)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def _uninstrument() -> None:
|
|
438
|
+
"""Disables instrumentation of :code:`httpx` through this module.
|
|
439
|
+
|
|
440
|
+
Note that this only works if no other module also patches httpx."""
|
|
441
|
+
_uninstrument_from(httpx.Client)
|
|
442
|
+
_uninstrument_from(httpx.AsyncClient)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def _uninstrument_from(instr_root: Union[type, object], restore_as_bound_func: bool = False) -> None:
|
|
446
|
+
for instr_func_name in ("send",):
|
|
447
|
+
instr_func = getattr(instr_root, instr_func_name)
|
|
448
|
+
if not getattr(
|
|
449
|
+
instr_func,
|
|
450
|
+
"opentelemetry_instrumentation_httpx_applied",
|
|
451
|
+
False,
|
|
452
|
+
):
|
|
453
|
+
continue
|
|
454
|
+
|
|
455
|
+
original = instr_func.__wrapped__
|
|
456
|
+
if restore_as_bound_func:
|
|
457
|
+
original = types.MethodType(original, instr_root)
|
|
458
|
+
setattr(instr_root, instr_func_name, original)
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def get_default_span_name(method: str) -> str:
|
|
462
|
+
"""
|
|
463
|
+
Default implementation for name_callback, returns HTTP {method_name}.
|
|
464
|
+
https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/http/#name
|
|
465
|
+
|
|
466
|
+
Args:
|
|
467
|
+
method: string representing HTTP method
|
|
468
|
+
Returns:
|
|
469
|
+
span name
|
|
470
|
+
"""
|
|
471
|
+
method = sanitize_method(method.strip())
|
|
472
|
+
if method == "_OTHER":
|
|
473
|
+
return "HTTP"
|
|
474
|
+
return method
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
class HTTPXInstrumentor(BaseInstrumentor): # type: ignore
|
|
478
|
+
"""An instrumentor for httpx
|
|
479
|
+
See `BaseInstrumentor`
|
|
480
|
+
"""
|
|
481
|
+
|
|
482
|
+
def instrumentation_dependencies(self) -> Collection[str]:
|
|
483
|
+
return _instruments
|
|
484
|
+
|
|
485
|
+
def _instrument(self, **kwargs: Any) -> None:
|
|
486
|
+
"""Instruments httpx module
|
|
487
|
+
|
|
488
|
+
Args:
|
|
489
|
+
**kwargs: Optional arguments
|
|
490
|
+
``tracer_provider``: a TracerProvider, defaults to global
|
|
491
|
+
``request_hook``: An optional callback that is invoked right after a span is created.
|
|
492
|
+
``response_hook``: An optional callback which is invoked right before the span is finished processing a response.
|
|
493
|
+
``excluded_urls``: A string containing a comma-delimited list of regexes used to exclude URLs from tracking
|
|
494
|
+
``duration_histogram_boundaries``: A list of float values representing the explicit bucket boundaries for the duration histogram.
|
|
495
|
+
"""
|
|
496
|
+
semconv_opt_in_mode = _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
|
|
497
|
+
_OpenTelemetryStabilitySignalType.HTTP,
|
|
498
|
+
)
|
|
499
|
+
schema_url = _get_schema_url(semconv_opt_in_mode)
|
|
500
|
+
tracer_provider = kwargs.get("tracer_provider")
|
|
501
|
+
tracer = get_tracer(
|
|
502
|
+
__name__,
|
|
503
|
+
__version__,
|
|
504
|
+
tracer_provider,
|
|
505
|
+
schema_url=schema_url,
|
|
506
|
+
)
|
|
507
|
+
excluded_urls = kwargs.get("excluded_urls")
|
|
508
|
+
meter_provider = kwargs.get("meter_provider")
|
|
509
|
+
duration_histogram_boundaries = kwargs.get("duration_histogram_boundaries")
|
|
510
|
+
meter = get_meter(
|
|
511
|
+
__name__,
|
|
512
|
+
__version__,
|
|
513
|
+
meter_provider,
|
|
514
|
+
schema_url=schema_url,
|
|
515
|
+
)
|
|
516
|
+
duration_histogram_old = None
|
|
517
|
+
if _report_old(semconv_opt_in_mode):
|
|
518
|
+
duration_histogram_old = meter.create_histogram(
|
|
519
|
+
name=MetricInstruments.HTTP_CLIENT_DURATION,
|
|
520
|
+
unit="ms",
|
|
521
|
+
description="measures the duration of the outbound HTTP request",
|
|
522
|
+
explicit_bucket_boundaries_advisory=duration_histogram_boundaries
|
|
523
|
+
or HTTP_DURATION_HISTOGRAM_BUCKETS_OLD,
|
|
524
|
+
)
|
|
525
|
+
duration_histogram_new = None
|
|
526
|
+
if _report_new(semconv_opt_in_mode):
|
|
527
|
+
duration_histogram_new = meter.create_histogram(
|
|
528
|
+
name=HTTP_CLIENT_REQUEST_DURATION,
|
|
529
|
+
unit="s",
|
|
530
|
+
description="Duration of HTTP client requests.",
|
|
531
|
+
explicit_bucket_boundaries_advisory=duration_histogram_boundaries
|
|
532
|
+
or HTTP_DURATION_HISTOGRAM_BUCKETS_NEW,
|
|
533
|
+
)
|
|
534
|
+
_instrument(
|
|
535
|
+
tracer,
|
|
536
|
+
duration_histogram_old,
|
|
537
|
+
duration_histogram_new,
|
|
538
|
+
request_hook=kwargs.get("request_hook"),
|
|
539
|
+
response_hook=kwargs.get("response_hook"),
|
|
540
|
+
excluded_urls=(_excluded_urls_from_env if excluded_urls is None else parse_excluded_urls(excluded_urls)),
|
|
541
|
+
sem_conv_opt_in_mode=semconv_opt_in_mode,
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
def _uninstrument(self, **kwargs: Any) -> None:
|
|
545
|
+
_uninstrument()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.28.1"
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from traceloop.sdk import Instruments
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CustomInstruments(Enum):
|
|
8
|
+
AIOHTTP = "aiohttp"
|
|
9
|
+
COHEREAI = "cohere_ai"
|
|
10
|
+
HTTPX = "httpx"
|
|
11
|
+
MISTRALAI = "mistral_ai"
|
|
12
|
+
QDRANTDB = "qdrant_db"
|
|
13
|
+
WEAVIATEDB = "weaviate_db"
|
|
14
|
+
GOOGLE_GENERATIVEAI = "google_genai"
|
|
15
|
+
FASTAPI = "fastapi"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class NetraInstruments(Enum):
|
|
19
|
+
"""Custom enum that stores the original enum class in an 'origin' attribute."""
|
|
20
|
+
|
|
21
|
+
def __new__(cls: Any, value: Any, origin: Any = None) -> Any:
|
|
22
|
+
member = object.__new__(cls)
|
|
23
|
+
member._value_ = value
|
|
24
|
+
member.origin = origin
|
|
25
|
+
return member
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
merged_members = {}
|
|
29
|
+
|
|
30
|
+
for member in Instruments:
|
|
31
|
+
merged_members[member.name] = (member.value, Instruments)
|
|
32
|
+
|
|
33
|
+
for member in CustomInstruments:
|
|
34
|
+
merged_members[member.name] = (member.value, CustomInstruments)
|
|
35
|
+
|
|
36
|
+
InstrumentSet = NetraInstruments("InstrumentSet", merged_members)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
#####################################################################################
|
|
40
|
+
"""
|
|
41
|
+
NetraInstruments follows the given structure. Refer this for usage within Netra SDK:
|
|
42
|
+
|
|
43
|
+
class InstrumentSet(Enum):
|
|
44
|
+
AIOHTTP = "aiohttp"
|
|
45
|
+
ALEPHALPHA = "alephalpha"
|
|
46
|
+
ANTHROPIC = "anthropic"
|
|
47
|
+
BEDROCK = "bedrock"
|
|
48
|
+
CHROMA = "chroma"
|
|
49
|
+
COHEREAI = "cohere_ai"
|
|
50
|
+
CREW = "crew"
|
|
51
|
+
FASTAPI = "fastapi"
|
|
52
|
+
GOOGLE_GENERATIVEAI = "google_genai"
|
|
53
|
+
GROQ = "groq"
|
|
54
|
+
HAYSTACK = "haystack"
|
|
55
|
+
HTTPX = "httpx"
|
|
56
|
+
LANCEDB = "lancedb"
|
|
57
|
+
LANGCHAIN = "langchain"
|
|
58
|
+
LLAMA_INDEX = "llama_index"
|
|
59
|
+
MARQO = "marqo"
|
|
60
|
+
MCP = "mcp"
|
|
61
|
+
MILVUS = "milvus"
|
|
62
|
+
MISTRALAI = "mistral_ai"
|
|
63
|
+
OLLAMA = "ollama"
|
|
64
|
+
OPENAI = "openai"
|
|
65
|
+
PINECONE = "pinecone"
|
|
66
|
+
PYMYSQL = "pymysql"
|
|
67
|
+
QDRANTDB = "qdrant_db"
|
|
68
|
+
REDIS = "redis"
|
|
69
|
+
REPLICATE = "replicate"
|
|
70
|
+
REQUESTS = "requests"
|
|
71
|
+
SAGEMAKER = "sagemaker"
|
|
72
|
+
TOGETHER = "together"
|
|
73
|
+
TRANSFORMERS = "transformers"
|
|
74
|
+
URLLIB3 = "urllib3"
|
|
75
|
+
VERTEXAI = "vertexai"
|
|
76
|
+
WATSONX = "watsonx"
|
|
77
|
+
WEAVIATEDB = "weaviate_db"
|
|
78
|
+
"""
|