sf-veritas 0.10.3__cp314-cp314-manylinux_2_28_x86_64.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 sf-veritas might be problematic. Click here for more details.

Files changed (132) hide show
  1. sf_veritas/__init__.py +20 -0
  2. sf_veritas/_sffastlog.c +889 -0
  3. sf_veritas/_sffastlog.cpython-314-x86_64-linux-gnu.so +0 -0
  4. sf_veritas/_sffastnet.c +924 -0
  5. sf_veritas/_sffastnet.cpython-314-x86_64-linux-gnu.so +0 -0
  6. sf_veritas/_sffastnetworkrequest.c +730 -0
  7. sf_veritas/_sffastnetworkrequest.cpython-314-x86_64-linux-gnu.so +0 -0
  8. sf_veritas/_sffuncspan.c +2155 -0
  9. sf_veritas/_sffuncspan.cpython-314-x86_64-linux-gnu.so +0 -0
  10. sf_veritas/_sffuncspan_config.c +617 -0
  11. sf_veritas/_sffuncspan_config.cpython-314-x86_64-linux-gnu.so +0 -0
  12. sf_veritas/_sfheadercheck.c +341 -0
  13. sf_veritas/_sfheadercheck.cpython-314-x86_64-linux-gnu.so +0 -0
  14. sf_veritas/_sfnetworkhop.c +1451 -0
  15. sf_veritas/_sfnetworkhop.cpython-314-x86_64-linux-gnu.so +0 -0
  16. sf_veritas/_sfservice.c +1175 -0
  17. sf_veritas/_sfservice.cpython-314-x86_64-linux-gnu.so +0 -0
  18. sf_veritas/_sfteepreload.c +5167 -0
  19. sf_veritas/app_config.py +49 -0
  20. sf_veritas/cli.py +336 -0
  21. sf_veritas/constants.py +10 -0
  22. sf_veritas/custom_excepthook.py +304 -0
  23. sf_veritas/custom_log_handler.py +129 -0
  24. sf_veritas/custom_output_wrapper.py +144 -0
  25. sf_veritas/custom_print.py +146 -0
  26. sf_veritas/django_app.py +5 -0
  27. sf_veritas/env_vars.py +186 -0
  28. sf_veritas/exception_handling_middleware.py +18 -0
  29. sf_veritas/exception_metaclass.py +69 -0
  30. sf_veritas/fast_frame_info.py +116 -0
  31. sf_veritas/fast_network_hop.py +293 -0
  32. sf_veritas/frame_tools.py +112 -0
  33. sf_veritas/funcspan_config_loader.py +556 -0
  34. sf_veritas/function_span_profiler.py +1174 -0
  35. sf_veritas/import_hook.py +62 -0
  36. sf_veritas/infra_details/__init__.py +3 -0
  37. sf_veritas/infra_details/get_infra_details.py +24 -0
  38. sf_veritas/infra_details/kubernetes/__init__.py +3 -0
  39. sf_veritas/infra_details/kubernetes/get_cluster_name.py +147 -0
  40. sf_veritas/infra_details/kubernetes/get_details.py +7 -0
  41. sf_veritas/infra_details/running_on/__init__.py +17 -0
  42. sf_veritas/infra_details/running_on/kubernetes.py +11 -0
  43. sf_veritas/interceptors.py +497 -0
  44. sf_veritas/libsfnettee.so +0 -0
  45. sf_veritas/local_env_detect.py +118 -0
  46. sf_veritas/package_metadata.py +6 -0
  47. sf_veritas/patches/__init__.py +0 -0
  48. sf_veritas/patches/concurrent_futures.py +19 -0
  49. sf_veritas/patches/constants.py +1 -0
  50. sf_veritas/patches/exceptions.py +82 -0
  51. sf_veritas/patches/multiprocessing.py +32 -0
  52. sf_veritas/patches/network_libraries/__init__.py +76 -0
  53. sf_veritas/patches/network_libraries/aiohttp.py +281 -0
  54. sf_veritas/patches/network_libraries/curl_cffi.py +363 -0
  55. sf_veritas/patches/network_libraries/http_client.py +419 -0
  56. sf_veritas/patches/network_libraries/httpcore.py +515 -0
  57. sf_veritas/patches/network_libraries/httplib2.py +204 -0
  58. sf_veritas/patches/network_libraries/httpx.py +515 -0
  59. sf_veritas/patches/network_libraries/niquests.py +211 -0
  60. sf_veritas/patches/network_libraries/pycurl.py +385 -0
  61. sf_veritas/patches/network_libraries/requests.py +633 -0
  62. sf_veritas/patches/network_libraries/tornado.py +341 -0
  63. sf_veritas/patches/network_libraries/treq.py +270 -0
  64. sf_veritas/patches/network_libraries/urllib_request.py +468 -0
  65. sf_veritas/patches/network_libraries/utils.py +398 -0
  66. sf_veritas/patches/os.py +17 -0
  67. sf_veritas/patches/threading.py +218 -0
  68. sf_veritas/patches/web_frameworks/__init__.py +54 -0
  69. sf_veritas/patches/web_frameworks/aiohttp.py +793 -0
  70. sf_veritas/patches/web_frameworks/async_websocket_consumer.py +317 -0
  71. sf_veritas/patches/web_frameworks/blacksheep.py +527 -0
  72. sf_veritas/patches/web_frameworks/bottle.py +502 -0
  73. sf_veritas/patches/web_frameworks/cherrypy.py +678 -0
  74. sf_veritas/patches/web_frameworks/cors_utils.py +122 -0
  75. sf_veritas/patches/web_frameworks/django.py +944 -0
  76. sf_veritas/patches/web_frameworks/eve.py +395 -0
  77. sf_veritas/patches/web_frameworks/falcon.py +926 -0
  78. sf_veritas/patches/web_frameworks/fastapi.py +724 -0
  79. sf_veritas/patches/web_frameworks/flask.py +520 -0
  80. sf_veritas/patches/web_frameworks/klein.py +501 -0
  81. sf_veritas/patches/web_frameworks/litestar.py +551 -0
  82. sf_veritas/patches/web_frameworks/pyramid.py +428 -0
  83. sf_veritas/patches/web_frameworks/quart.py +824 -0
  84. sf_veritas/patches/web_frameworks/robyn.py +697 -0
  85. sf_veritas/patches/web_frameworks/sanic.py +857 -0
  86. sf_veritas/patches/web_frameworks/starlette.py +723 -0
  87. sf_veritas/patches/web_frameworks/strawberry.py +813 -0
  88. sf_veritas/patches/web_frameworks/tornado.py +481 -0
  89. sf_veritas/patches/web_frameworks/utils.py +91 -0
  90. sf_veritas/print_override.py +13 -0
  91. sf_veritas/regular_data_transmitter.py +409 -0
  92. sf_veritas/request_interceptor.py +401 -0
  93. sf_veritas/request_utils.py +550 -0
  94. sf_veritas/server_status.py +1 -0
  95. sf_veritas/shutdown_flag.py +11 -0
  96. sf_veritas/subprocess_startup.py +3 -0
  97. sf_veritas/test_cli.py +145 -0
  98. sf_veritas/thread_local.py +970 -0
  99. sf_veritas/timeutil.py +114 -0
  100. sf_veritas/transmit_exception_to_sailfish.py +28 -0
  101. sf_veritas/transmitter.py +132 -0
  102. sf_veritas/types.py +47 -0
  103. sf_veritas/unified_interceptor.py +1580 -0
  104. sf_veritas/utils.py +39 -0
  105. sf_veritas-0.10.3.dist-info/METADATA +97 -0
  106. sf_veritas-0.10.3.dist-info/RECORD +132 -0
  107. sf_veritas-0.10.3.dist-info/WHEEL +5 -0
  108. sf_veritas-0.10.3.dist-info/entry_points.txt +2 -0
  109. sf_veritas-0.10.3.dist-info/top_level.txt +1 -0
  110. sf_veritas.libs/libbrotlicommon-6ce2a53c.so.1.0.6 +0 -0
  111. sf_veritas.libs/libbrotlidec-811d1be3.so.1.0.6 +0 -0
  112. sf_veritas.libs/libcom_err-730ca923.so.2.1 +0 -0
  113. sf_veritas.libs/libcrypt-52aca757.so.1.1.0 +0 -0
  114. sf_veritas.libs/libcrypto-bdaed0ea.so.1.1.1k +0 -0
  115. sf_veritas.libs/libcurl-eaa3cf66.so.4.5.0 +0 -0
  116. sf_veritas.libs/libgssapi_krb5-323bbd21.so.2.2 +0 -0
  117. sf_veritas.libs/libidn2-2f4a5893.so.0.3.6 +0 -0
  118. sf_veritas.libs/libk5crypto-9a74ff38.so.3.1 +0 -0
  119. sf_veritas.libs/libkeyutils-2777d33d.so.1.6 +0 -0
  120. sf_veritas.libs/libkrb5-a55300e8.so.3.3 +0 -0
  121. sf_veritas.libs/libkrb5support-e6594cfc.so.0.1 +0 -0
  122. sf_veritas.libs/liblber-2-d20824ef.4.so.2.10.9 +0 -0
  123. sf_veritas.libs/libldap-2-cea2a960.4.so.2.10.9 +0 -0
  124. sf_veritas.libs/libnghttp2-39367a22.so.14.17.0 +0 -0
  125. sf_veritas.libs/libpcre2-8-516f4c9d.so.0.7.1 +0 -0
  126. sf_veritas.libs/libpsl-99becdd3.so.5.3.1 +0 -0
  127. sf_veritas.libs/libsasl2-7de4d792.so.3.0.0 +0 -0
  128. sf_veritas.libs/libselinux-d0805dcb.so.1 +0 -0
  129. sf_veritas.libs/libssh-c11d285b.so.4.8.7 +0 -0
  130. sf_veritas.libs/libssl-60250281.so.1.1.1k +0 -0
  131. sf_veritas.libs/libunistring-05abdd40.so.2.1.0 +0 -0
  132. sf_veritas.libs/libuuid-95b83d40.so.1.3.0 +0 -0
@@ -0,0 +1,497 @@
1
+ import json
2
+ import logging
3
+ import os
4
+ import re
5
+ import threading
6
+ import time
7
+ import uuid
8
+ from typing import Any, Dict, List, Optional
9
+
10
+ from . import app_config
11
+ from .env_vars import SF_DEBUG, SF_LOG_IGNORE_REGEX
12
+ from .package_metadata import PACKAGE_LIBRARY_TYPE, __version__
13
+ from .regular_data_transmitter import ServiceIdentifier
14
+ from .request_utils import non_blocking_post
15
+ from .thread_local import ( # reentrancy_guard, activate_reentrancy_guards_logging_preactive,
16
+ activate_reentrancy_guards_logging,
17
+ get_or_set_sf_trace_id,
18
+ )
19
+ from .timeutil import TimeSync
20
+ from .types import CustomJSONEncoderForFrameInfo, FrameInfo
21
+ from .utils import serialize_json_with_exclusions, strtobool
22
+
23
+ # Precompile once (was re.match(pattern,..) per log)
24
+ # Loaded from SF_LOG_IGNORE_REGEX environment variable (default: suppress /healthz and /graphql/ 2xx)
25
+ _IGNORE_RE = re.compile(SF_LOG_IGNORE_REGEX)
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+
30
+ class OutputInterceptor(object):
31
+ def __init__(self, api_key: str = None):
32
+ self.api_key = api_key or app_config._sailfish_api_key
33
+ self.endpoint = app_config._sailfish_graphql_endpoint
34
+ self.operation_name: Optional[str] = ""
35
+ self.query_type = "mutation"
36
+ self.service_identifier = ServiceIdentifier()
37
+
38
+ @property
39
+ def query_name(self) -> str:
40
+ return (
41
+ self.operation_name[0].lower() + self.operation_name[1:]
42
+ if self.operation_name
43
+ else ""
44
+ )
45
+
46
+ def get_default_variables(self, session_id: Optional[str] = None):
47
+ trace_id = session_id
48
+ if not session_id:
49
+ _, trace_id = get_or_set_sf_trace_id(session_id)
50
+ timestamp_ms = TimeSync.get_instance().get_utc_time_in_ms()
51
+ return {
52
+ "apiKey": self.api_key,
53
+ "serviceUuid": app_config._service_uuid,
54
+ "library": PACKAGE_LIBRARY_TYPE,
55
+ "sessionId": trace_id,
56
+ "timestampMs": str(timestamp_ms),
57
+ "version": __version__,
58
+ }
59
+
60
+ def get_variables(
61
+ self,
62
+ additional_variables: Optional[Dict[str, Any]] = None,
63
+ session_id: Optional[str] = None,
64
+ ) -> Dict[str, Any]:
65
+ additional_variables = additional_variables or {}
66
+ return {**additional_variables, **self.get_default_variables(session_id)}
67
+
68
+ def check_if_contents_should_be_ignored(
69
+ self, contents
70
+ ): # pylint: disable=unused-argument
71
+ return False
72
+
73
+ def _send_app_identifier(self, session_id: str) -> None:
74
+ if SF_DEBUG:
75
+ print("_send_app_identifier...SENDING DATA...args=", set(), log=False)
76
+ self.service_identifier.do_send(set())
77
+
78
+ def do_send(self, args, session_id: str) -> None:
79
+ self._send_app_identifier(session_id)
80
+ if SF_DEBUG:
81
+ print(f"[[OutputInterceptor.do_send]] session_id={session_id}", log=False)
82
+ try:
83
+ self.send(*args)
84
+ except RuntimeError:
85
+ return
86
+
87
+
88
+ # sf_veritas/interceptors.py (excerpt: LogInterceptor)
89
+ import time
90
+ from typing import Callable, Tuple
91
+
92
+ from . import app_config
93
+ from .env_vars import SF_DEBUG
94
+ from .request_utils import non_blocking_post_deferred # Python fallback
95
+ from .thread_local import get_reentrancy_guard_logging_preactive
96
+
97
+ # Try native fast path (compiled C extension)
98
+ try:
99
+ from . import _sffastlog
100
+
101
+ _FAST_OK = True
102
+ except Exception:
103
+ _sffastlog = None
104
+ _FAST_OK = False
105
+
106
+ # GraphQL mutation (camelCase variables) — keep identical to your server schema
107
+ _COLLECT_LOGS_OP = "CollectLogs"
108
+ _COLLECT_LOGS_MUTATION = """
109
+ mutation CollectLogs(
110
+ $apiKey: String!,
111
+ $serviceUuid: String!,
112
+ $sessionId: String!,
113
+ $level: String!,
114
+ $contents: String!,
115
+ $reentrancyGuardPreactive: Boolean!,
116
+ $library: String!,
117
+ $timestampMs: String!,
118
+ $version: String!
119
+ ) {
120
+ collectLogs(
121
+ apiKey: $apiKey,
122
+ serviceUuid: $serviceUuid,
123
+ sessionId: $sessionId,
124
+ level: $level,
125
+ contents: $contents,
126
+ reentrancyGuardPreactive: $reentrancyGuardPreactive,
127
+ library: $library,
128
+ timestampMs: $timestampMs,
129
+ version: $version
130
+ )
131
+ }
132
+ """.strip()
133
+
134
+ # ---------- Prints (GraphQL identical to your current schema) ----------
135
+ _COLLECT_PRINT_OP = "CollectPrintStatements"
136
+ _COLLECT_PRINT_MUTATION = """
137
+ mutation CollectPrintStatements(
138
+ $apiKey: String!,
139
+ $serviceUuid: String!,
140
+ $sessionId: String!,
141
+ $contents: String!,
142
+ $reentrancyGuardPreactive: Boolean!,
143
+ $library: String!,
144
+ $timestampMs: String!,
145
+ $version: String!
146
+ ) {
147
+ collectPrintStatements(
148
+ apiKey: $apiKey,
149
+ serviceUuid: $serviceUuid,
150
+ sessionId: $sessionId,
151
+ contents: $contents,
152
+ reentrancyGuardPreactive: $reentrancyGuardPreactive,
153
+ library: $library,
154
+ timestampMs: $timestampMs,
155
+ version: $version
156
+ )
157
+ }
158
+ """.strip()
159
+
160
+
161
+ class LogInterceptor:
162
+ """
163
+ Uses native _sffastlog if present; otherwise falls back to Python deferred sender.
164
+ """
165
+
166
+ def __init__(self, api_key: str):
167
+ self.api_key = api_key
168
+ # Use app_config instead of os.environ to avoid KeyError
169
+ self.endpoint = getattr(app_config, "_sailfish_graphql_endpoint", None)
170
+ self.service_uuid = (
171
+ getattr(app_config, "_service_uuid", None)
172
+ or getattr(app_config, "service_uuid", None)
173
+ or "unknown"
174
+ )
175
+ self.library = getattr(app_config, "library", "sailfish-python")
176
+ self.version = getattr(app_config, "version", "0.0.0")
177
+
178
+ if _FAST_OK and self.endpoint:
179
+ try:
180
+ http2 = 1 if os.getenv("SF_NBPOST_HTTP2", "0") == "1" else 0
181
+ if SF_DEBUG:
182
+ print(
183
+ f"[[LogInterceptor.__init__]] Calling _sffastlog.init() with url={self.endpoint}"
184
+ )
185
+ ok = _sffastlog.init(
186
+ url=self.endpoint,
187
+ query=_COLLECT_LOGS_MUTATION,
188
+ api_key=self.api_key,
189
+ service_uuid=str(self.service_uuid),
190
+ library=str(self.library),
191
+ version=str(self.version),
192
+ http2=http2,
193
+ )
194
+ if ok and SF_DEBUG:
195
+ print("[_sffastlog] initialized (libcurl sender for logs)")
196
+ elif SF_DEBUG:
197
+ print(f"[_sffastlog] init returned {ok}")
198
+ except Exception as e:
199
+ if SF_DEBUG:
200
+ print(f"[_sffastlog] init failed; falling back: {e}")
201
+
202
+ def check_if_contents_should_be_ignored(self, contents: str) -> bool:
203
+ """
204
+ Check if log contents should be ignored (not sent to Sailfish).
205
+ Uses SF_LOG_IGNORE_REGEX environment variable (default: suppress /healthz and /graphql/ 2xx).
206
+
207
+ Returns:
208
+ True if the log should be ignored, False otherwise
209
+ """
210
+ return _IGNORE_RE.match(contents or "") is not None
211
+
212
+ def do_send(self, payload: Tuple[str, str, str], trace_id: str):
213
+ """
214
+ payload: (log_level, log_entry, session_id)
215
+ """
216
+ level, contents, session_id = payload
217
+ preactive = bool(get_reentrancy_guard_logging_preactive())
218
+
219
+ if SF_DEBUG:
220
+ print(
221
+ f"[[LogInterceptor.do_send]] level={level}, session_id={session_id}, _FAST_OK={_FAST_OK}",
222
+ log=False,
223
+ )
224
+
225
+ if _FAST_OK:
226
+ try:
227
+ if SF_DEBUG:
228
+ print(
229
+ f"[[LogInterceptor.do_send]] Calling _sffastlog.log()",
230
+ log=False,
231
+ )
232
+ _sffastlog.log(
233
+ level=level or "UNKNOWN",
234
+ contents=contents,
235
+ session_id=str(session_id),
236
+ preactive=preactive,
237
+ )
238
+ if SF_DEBUG:
239
+ print(
240
+ f"[[LogInterceptor.do_send]] _sffastlog.log() succeeded",
241
+ log=False,
242
+ )
243
+ return
244
+ except Exception as e:
245
+ if SF_DEBUG:
246
+ print(f"[_sffastlog] log failed; fallback path: {e}", log=False)
247
+
248
+ # --- Python fallback (deferred) ---
249
+ ts_ms = time.time_ns() // 1_000_000
250
+ endpoint = self.endpoint
251
+ op = _COLLECT_LOGS_OP
252
+ query = _COLLECT_LOGS_MUTATION
253
+ api_key = self.api_key
254
+ service_uuid = self.service_uuid
255
+ library = self.library
256
+ version = self.version
257
+
258
+ def _builder():
259
+ vars = {
260
+ "apiKey": api_key,
261
+ "serviceUuid": str(service_uuid),
262
+ "sessionId": str(session_id),
263
+ "level": level or "UNKNOWN",
264
+ "contents": contents,
265
+ "reentrancyGuardPreactive": preactive,
266
+ "library": str(library),
267
+ "timestampMs": str(ts_ms),
268
+ "version": str(version),
269
+ }
270
+ return endpoint, op, query, vars
271
+
272
+ non_blocking_post_deferred(_builder)
273
+
274
+ def shutdown(self):
275
+ if _FAST_OK:
276
+ try:
277
+ _sffastlog.shutdown()
278
+ except Exception:
279
+ pass
280
+
281
+
282
+ # ---------------- Prints (NEW native fast path) ----------------
283
+ class PrintInterceptor(OutputInterceptor):
284
+ def __init__(self, api_key: str = None):
285
+ if api_key is None:
286
+ api_key = app_config._sailfish_api_key
287
+ super().__init__(api_key)
288
+ self.operation_name = _COLLECT_PRINT_OP
289
+
290
+ # Cache the query string
291
+ self._QUERY = _COLLECT_PRINT_MUTATION
292
+
293
+ # Native fast path for print, if available
294
+ self._fast_print_ok = False
295
+ if _FAST_OK:
296
+ try:
297
+ http2 = 1 if os.getenv("SF_NBPOST_HTTP2", "0") == "1" else 0
298
+ ok = _sffastlog.init_print(
299
+ url=self.endpoint,
300
+ query=self._QUERY,
301
+ api_key=self.api_key,
302
+ service_uuid=str(app_config._service_uuid),
303
+ library=PACKAGE_LIBRARY_TYPE,
304
+ version=__version__,
305
+ http2=http2,
306
+ )
307
+ self._fast_print_ok = bool(ok)
308
+ print("[_sffastlog] initialized (prints)") # , log=False)
309
+ if self._fast_print_ok and SF_DEBUG:
310
+ print("[_sffastlog] initialized (prints)") # , log=False)
311
+ except Exception as e:
312
+ if SF_DEBUG:
313
+ print(
314
+ "[_sffastlog] init_print failed; fallback:", e
315
+ ) # , log=False)
316
+
317
+ def send(self, contents: str, session_id: str):
318
+ # Drop obvious noise early (cheap)
319
+ if _IGNORE_RE.match(contents or ""):
320
+ return
321
+
322
+ preactive = False # printing path uses preactive only if you need it later
323
+ if self._fast_print_ok:
324
+ try:
325
+ _sffastlog.print_( # exposed as print_ to avoid name clash
326
+ contents=contents,
327
+ session_id=str(session_id),
328
+ preactive=preactive,
329
+ )
330
+ return
331
+ except Exception as e:
332
+ if SF_DEBUG:
333
+ print("[_sffastlog] print_ failed; fallback:", e, log=False)
334
+
335
+ # Python fallback: fast minimal dict and post
336
+ d = self.get_default_variables(session_id)
337
+ variables = {
338
+ "apiKey": d["apiKey"],
339
+ "serviceUuid": d["serviceUuid"],
340
+ "sessionId": d["sessionId"],
341
+ "library": d["library"],
342
+ "timestampMs": d["timestampMs"],
343
+ "version": d["version"],
344
+ "contents": contents,
345
+ "reentrancyGuardPreactive": False,
346
+ }
347
+ non_blocking_post(self.endpoint, self.operation_name, self._QUERY, variables)
348
+
349
+
350
+ _COLLECT_EXCEPTION_OP = "CollectExceptions"
351
+ _COLLECT_EXCEPTION_MUTATION = """
352
+ mutation CollectExceptions(
353
+ $apiKey: String!,
354
+ $serviceUuid: String!,
355
+ $sessionId: String!,
356
+ $exceptionMessage: String!,
357
+ $wasCaught: Boolean!,
358
+ $traceJson: String!,
359
+ $reentrancyGuardPreactive: Boolean!,
360
+ $library: String!,
361
+ $timestampMs: String!,
362
+ $version: String!,
363
+ $isFromLocalService: Boolean!
364
+ ) {
365
+ collectExceptions(
366
+ apiKey: $apiKey,
367
+ serviceUuid: $serviceUuid,
368
+ sessionId: $sessionId,
369
+ exceptionMessage: $exceptionMessage,
370
+ wasCaught: $wasCaught,
371
+ traceJson: $traceJson,
372
+ reentrancyGuardPreactive: $reentrancyGuardPreactive,
373
+ library: $library,
374
+ timestampMs: $timestampMs,
375
+ version: $version,
376
+ isFromLocalService: $isFromLocalService
377
+ )
378
+ }
379
+ """.strip()
380
+
381
+
382
+ class ExceptionInterceptor(OutputInterceptor):
383
+ def __init__(self, api_key: str = app_config._sailfish_api_key):
384
+ super().__init__(api_key)
385
+ self.operation_name = _COLLECT_EXCEPTION_OP
386
+ self._QUERY = _COLLECT_EXCEPTION_MUTATION
387
+
388
+ # Native fast path for exceptions, if available
389
+ self._fast_exception_ok = False
390
+ if _FAST_OK:
391
+ try:
392
+ http2 = 1 if os.getenv("SF_NBPOST_HTTP2", "0") == "1" else 0
393
+ ok = _sffastlog.init_exception(
394
+ url=self.endpoint,
395
+ query=self._QUERY,
396
+ api_key=self.api_key,
397
+ service_uuid=str(app_config._service_uuid),
398
+ library=PACKAGE_LIBRARY_TYPE,
399
+ version=__version__,
400
+ http2=http2,
401
+ )
402
+ self._fast_exception_ok = bool(ok)
403
+ if self._fast_exception_ok and SF_DEBUG:
404
+ print("[_sffastlog] initialized (exceptions)", log=False)
405
+ except Exception as e:
406
+ if SF_DEBUG:
407
+ print("[_sffastlog] init_exception failed; fallback:", e, log=False)
408
+
409
+ def send(
410
+ self,
411
+ exception_message: str,
412
+ trace: List[FrameInfo],
413
+ session_id: str,
414
+ was_caught: bool = True,
415
+ is_from_local_service: bool = False,
416
+ ):
417
+ trace_json = json.dumps(trace, cls=CustomJSONEncoderForFrameInfo)
418
+
419
+ if self._fast_exception_ok:
420
+ try:
421
+ _sffastlog.exception(
422
+ exception_message=exception_message,
423
+ trace_json=trace_json,
424
+ session_id=str(session_id),
425
+ was_caught=was_caught,
426
+ is_from_local_service=is_from_local_service,
427
+ )
428
+ return
429
+ except Exception as e:
430
+ if SF_DEBUG:
431
+ print("[_sffastlog] exception failed; fallback:", e, log=False)
432
+
433
+ # Python fallback
434
+ query = f"""
435
+ {self.query_type} {self.operation_name}($apiKey: String!, $serviceUuid: String!, $sessionId: String!, $exceptionMessage: String!, $wasCaught: Boolean!, $traceJson: String!, $reentrancyGuardPreactive: Boolean!, $library: String!, $timestampMs: String!, $version: String!, $isFromLocalService: Boolean!) {{
436
+ {self.query_name}(apiKey: $apiKey, serviceUuid: $serviceUuid, sessionId: $sessionId, exceptionMessage: $exceptionMessage, wasCaught: $wasCaught, traceJson: $traceJson, reentrancyGuardPreactive: $reentrancyGuardPreactive, library: $library, timestampMs: $timestampMs, version: $version, isFromLocalService: $isFromLocalService)
437
+ }}
438
+ """
439
+
440
+ if SF_DEBUG:
441
+ print("SENDING EXCEPTION...", log=False)
442
+ non_blocking_post(
443
+ self.endpoint,
444
+ self.operation_name,
445
+ query,
446
+ self.get_variables(
447
+ {
448
+ "apiKey": self.api_key,
449
+ "exceptionMessage": exception_message,
450
+ "traceJson": trace_json,
451
+ "reentrancyGuardPreactive": False,
452
+ "wasCaught": was_caught,
453
+ "isFromLocalService": is_from_local_service,
454
+ },
455
+ session_id,
456
+ ),
457
+ )
458
+
459
+
460
+ class CollectMetadataTransmitter(OutputInterceptor):
461
+ def __init__(self, api_key: str = app_config._sailfish_api_key):
462
+ super().__init__(api_key)
463
+ self.operation_name = "CollectMetadata"
464
+
465
+ def send(
466
+ self,
467
+ user_id: str,
468
+ traits: Optional[Dict[str, Any]],
469
+ traits_json: Optional[str],
470
+ override: bool,
471
+ session_id: str,
472
+ ):
473
+ if traits is None and traits_json is None:
474
+ raise Exception(
475
+ 'Must pass in either traits or traits_json to "add_or_update_traits"'
476
+ )
477
+ query = f"""
478
+ {self.query_type} {self.operation_name}($apiKey: String!, $serviceUuid: String!, $sessionId: String!, $userId: String!, $traitsJson: String!, $excludedFields: [String!]!, $library: String!, $timestampMs: String!, $version: String!, $override: Boolean!) {{
479
+ {self.query_name}(apiKey: $apiKey, serviceUuid: $serviceUuid, sessionId: $sessionId, userId: $userId, traitsJson: $traitsJson, excludedFields: $excludedFields, library: $library, timestampMs: $timestampMs, version: $version, override: $override)
480
+ }}
481
+ """
482
+
483
+ excluded_fields = []
484
+ if traits_json is None:
485
+ traits_json, excluded_fields = serialize_json_with_exclusions(traits)
486
+
487
+ variables = self.get_variables(
488
+ {
489
+ "userId": user_id,
490
+ "traitsJson": traits_json,
491
+ "excludedFields": excluded_fields,
492
+ "override": override,
493
+ },
494
+ session_id,
495
+ )
496
+
497
+ non_blocking_post(self.endpoint, self.operation_name, query, variables)
Binary file
@@ -0,0 +1,118 @@
1
+ from __future__ import annotations
2
+ import os, sys, socket, urllib.request, urllib.error
3
+
4
+ DEFAULT_TIMEOUT_S = 0.15
5
+
6
+ def _quick_http(url: str, headers: dict[str, str] | None = None, timeout: float = DEFAULT_TIMEOUT_S) -> tuple[int | None, str]:
7
+ req = urllib.request.Request(url, headers=headers or {}, method="GET")
8
+ try:
9
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
10
+ return resp.getcode(), "ok"
11
+ except urllib.error.HTTPError as e:
12
+ return e.code, "http_error"
13
+ except Exception as e:
14
+ return None, str(e)
15
+
16
+ def _is_cloud_instance() -> tuple[bool, str]:
17
+ try:
18
+ import urllib.request as _u
19
+ tok_req = _u.Request(
20
+ "http://169.254.169.254/latest/api/token",
21
+ headers={"X-aws-ec2-metadata-token-ttl-seconds": "60"},
22
+ method="PUT",
23
+ )
24
+ with _u.urlopen(tok_req, timeout=DEFAULT_TIMEOUT_S) as r:
25
+ if r.getcode() == 200:
26
+ return True, "aws-imdsv2"
27
+ except urllib.error.HTTPError as e:
28
+ if e.code in (401, 403, 404, 405):
29
+ return True, f"aws-imds({e.code})"
30
+ except Exception:
31
+ pass
32
+
33
+ code, _ = _quick_http("http://169.254.169.254/latest/meta-data/")
34
+ if code == 200:
35
+ return True, "aws-imdsv1"
36
+
37
+ code, _ = _quick_http(
38
+ "http://169.254.169.254/computeMetadata/v1/instance/id",
39
+ headers={"Metadata-Flavor": "Google"},
40
+ )
41
+ if code == 200:
42
+ return True, "gcp-metadata"
43
+
44
+ code, _ = _quick_http(
45
+ "http://169.254.169.254/metadata/instance?api-version=2021-02-01",
46
+ headers={"Metadata": "true"},
47
+ )
48
+ if code == 200:
49
+ return True, "azure-imds"
50
+
51
+ return False, "no-cloud-metadata"
52
+
53
+ def _resolves_host_docker_internal() -> bool:
54
+ try:
55
+ socket.gethostbyname("host.docker.internal")
56
+ return True
57
+ except Exception:
58
+ return False
59
+
60
+ # ---- globals to hold state ----
61
+ SF_IS_LOCAL_ENV: bool | None = None
62
+ SF_LOCAL_ENV_REASON: str | None = None
63
+
64
+
65
+ def _detect() -> tuple[bool, str]:
66
+ """Detect environment once. Raise nothing; always return a tuple."""
67
+ try:
68
+ if any(os.getenv(k) for k in (
69
+ "CI", "GITHUB_ACTIONS", "GITLAB_CI", "CIRCLECI",
70
+ "BUILDkite", "TEAMCITY_VERSION", "JENKINS_URL", "DRONE"
71
+ )):
72
+ return (False, "ci-env-detected")
73
+
74
+ on_cloud, cloud_reason = _is_cloud_instance()
75
+ if on_cloud:
76
+ return (False, cloud_reason)
77
+
78
+ if sys.platform in ("darwin", "win32"):
79
+ return (True, f"desktop-os:{sys.platform}")
80
+ try:
81
+ if "microsoft" in os.uname().release.lower() \
82
+ or "microsoft" in open("/proc/version", "rt", errors="ignore").read().lower():
83
+ return (True, "wsl-kernel")
84
+ except OSError:
85
+ pass
86
+
87
+ if _resolves_host_docker_internal():
88
+ return (True, "docker-desktop-dns")
89
+
90
+ return (True, "no-cloud-metadata-and-no-ci")
91
+
92
+ except Exception as e:
93
+ # fallback: treat as local if detection fails
94
+ return (True, f"detect-error:{type(e).__name__}")
95
+
96
+
97
+ def set_sf_is_local_flag() -> None:
98
+ """
99
+ Run detection once and store results in global variables.
100
+ Call this at app startup. Never raises.
101
+ """
102
+ global SF_IS_LOCAL_ENV, SF_LOCAL_ENV_REASON
103
+ try:
104
+ SF_IS_LOCAL_ENV, SF_LOCAL_ENV_REASON = _detect()
105
+ except Exception as e:
106
+ # absolute fallback, so setup never fails
107
+ SF_IS_LOCAL_ENV, SF_LOCAL_ENV_REASON = True, f"setup-error:{type(e).__name__}"
108
+
109
+
110
+ def sf_is_local_dev_environment() -> tuple[bool, str]:
111
+ """
112
+ Return cached values if sf_set_is_local_flag() has been called,
113
+ otherwise run detection on the fly. Never raises.
114
+ """
115
+ global SF_IS_LOCAL_ENV, SF_LOCAL_ENV_REASON
116
+ if SF_IS_LOCAL_ENV is None or SF_LOCAL_ENV_REASON is None:
117
+ set_sf_is_local_flag()
118
+ return SF_IS_LOCAL_ENV, SF_LOCAL_ENV_REASON
@@ -0,0 +1,6 @@
1
+ import importlib.metadata
2
+
3
+ PACKAGE_LIBRARY_TYPE = "PYTHON"
4
+ PACKAGE_NAME = "sf-veritas"
5
+
6
+ __version__ = importlib.metadata.version(PACKAGE_NAME)
File without changes
@@ -0,0 +1,19 @@
1
+ from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
2
+ from ..thread_local import get_context, set_context
3
+
4
+ _original_submit = ThreadPoolExecutor.submit
5
+
6
+
7
+ def patched_submit(self, fn, *args, **kwargs):
8
+ current_context = get_context()
9
+
10
+ def wrapped_fn(*fn_args, **fn_kwargs):
11
+ set_context(current_context)
12
+ fn(*fn_args, **fn_kwargs)
13
+
14
+ return _original_submit(self, wrapped_fn, *args, **kwargs)
15
+
16
+
17
+ def patch_concurrent_futures():
18
+ ThreadPoolExecutor.submit = patched_submit
19
+ ProcessPoolExecutor.submit = patched_submit
@@ -0,0 +1 @@
1
+ supported_network_verbs = ("get", "post", "put", "patch", "delete", "head", "options")