sf-veritas 0.10.3__cp311-cp311-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.
- sf_veritas/__init__.py +20 -0
- sf_veritas/_sffastlog.c +889 -0
- sf_veritas/_sffastlog.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnet.c +924 -0
- sf_veritas/_sffastnet.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnetworkrequest.c +730 -0
- sf_veritas/_sffastnetworkrequest.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan.c +2155 -0
- sf_veritas/_sffuncspan.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan_config.c +617 -0
- sf_veritas/_sffuncspan_config.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfheadercheck.c +341 -0
- sf_veritas/_sfheadercheck.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfnetworkhop.c +1451 -0
- sf_veritas/_sfnetworkhop.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfservice.c +1175 -0
- sf_veritas/_sfservice.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfteepreload.c +5167 -0
- sf_veritas/app_config.py +49 -0
- sf_veritas/cli.py +336 -0
- sf_veritas/constants.py +10 -0
- sf_veritas/custom_excepthook.py +304 -0
- sf_veritas/custom_log_handler.py +129 -0
- sf_veritas/custom_output_wrapper.py +144 -0
- sf_veritas/custom_print.py +146 -0
- sf_veritas/django_app.py +5 -0
- sf_veritas/env_vars.py +186 -0
- sf_veritas/exception_handling_middleware.py +18 -0
- sf_veritas/exception_metaclass.py +69 -0
- sf_veritas/fast_frame_info.py +116 -0
- sf_veritas/fast_network_hop.py +293 -0
- sf_veritas/frame_tools.py +112 -0
- sf_veritas/funcspan_config_loader.py +556 -0
- sf_veritas/function_span_profiler.py +1174 -0
- sf_veritas/import_hook.py +62 -0
- sf_veritas/infra_details/__init__.py +3 -0
- sf_veritas/infra_details/get_infra_details.py +24 -0
- sf_veritas/infra_details/kubernetes/__init__.py +3 -0
- sf_veritas/infra_details/kubernetes/get_cluster_name.py +147 -0
- sf_veritas/infra_details/kubernetes/get_details.py +7 -0
- sf_veritas/infra_details/running_on/__init__.py +17 -0
- sf_veritas/infra_details/running_on/kubernetes.py +11 -0
- sf_veritas/interceptors.py +497 -0
- sf_veritas/libsfnettee.so +0 -0
- sf_veritas/local_env_detect.py +118 -0
- sf_veritas/package_metadata.py +6 -0
- sf_veritas/patches/__init__.py +0 -0
- sf_veritas/patches/concurrent_futures.py +19 -0
- sf_veritas/patches/constants.py +1 -0
- sf_veritas/patches/exceptions.py +82 -0
- sf_veritas/patches/multiprocessing.py +32 -0
- sf_veritas/patches/network_libraries/__init__.py +76 -0
- sf_veritas/patches/network_libraries/aiohttp.py +281 -0
- sf_veritas/patches/network_libraries/curl_cffi.py +363 -0
- sf_veritas/patches/network_libraries/http_client.py +419 -0
- sf_veritas/patches/network_libraries/httpcore.py +515 -0
- sf_veritas/patches/network_libraries/httplib2.py +204 -0
- sf_veritas/patches/network_libraries/httpx.py +515 -0
- sf_veritas/patches/network_libraries/niquests.py +211 -0
- sf_veritas/patches/network_libraries/pycurl.py +385 -0
- sf_veritas/patches/network_libraries/requests.py +633 -0
- sf_veritas/patches/network_libraries/tornado.py +341 -0
- sf_veritas/patches/network_libraries/treq.py +270 -0
- sf_veritas/patches/network_libraries/urllib_request.py +468 -0
- sf_veritas/patches/network_libraries/utils.py +398 -0
- sf_veritas/patches/os.py +17 -0
- sf_veritas/patches/threading.py +218 -0
- sf_veritas/patches/web_frameworks/__init__.py +54 -0
- sf_veritas/patches/web_frameworks/aiohttp.py +793 -0
- sf_veritas/patches/web_frameworks/async_websocket_consumer.py +317 -0
- sf_veritas/patches/web_frameworks/blacksheep.py +527 -0
- sf_veritas/patches/web_frameworks/bottle.py +502 -0
- sf_veritas/patches/web_frameworks/cherrypy.py +678 -0
- sf_veritas/patches/web_frameworks/cors_utils.py +122 -0
- sf_veritas/patches/web_frameworks/django.py +944 -0
- sf_veritas/patches/web_frameworks/eve.py +395 -0
- sf_veritas/patches/web_frameworks/falcon.py +926 -0
- sf_veritas/patches/web_frameworks/fastapi.py +724 -0
- sf_veritas/patches/web_frameworks/flask.py +520 -0
- sf_veritas/patches/web_frameworks/klein.py +501 -0
- sf_veritas/patches/web_frameworks/litestar.py +551 -0
- sf_veritas/patches/web_frameworks/pyramid.py +428 -0
- sf_veritas/patches/web_frameworks/quart.py +824 -0
- sf_veritas/patches/web_frameworks/robyn.py +697 -0
- sf_veritas/patches/web_frameworks/sanic.py +857 -0
- sf_veritas/patches/web_frameworks/starlette.py +723 -0
- sf_veritas/patches/web_frameworks/strawberry.py +813 -0
- sf_veritas/patches/web_frameworks/tornado.py +481 -0
- sf_veritas/patches/web_frameworks/utils.py +91 -0
- sf_veritas/print_override.py +13 -0
- sf_veritas/regular_data_transmitter.py +409 -0
- sf_veritas/request_interceptor.py +401 -0
- sf_veritas/request_utils.py +550 -0
- sf_veritas/server_status.py +1 -0
- sf_veritas/shutdown_flag.py +11 -0
- sf_veritas/subprocess_startup.py +3 -0
- sf_veritas/test_cli.py +145 -0
- sf_veritas/thread_local.py +970 -0
- sf_veritas/timeutil.py +114 -0
- sf_veritas/transmit_exception_to_sailfish.py +28 -0
- sf_veritas/transmitter.py +132 -0
- sf_veritas/types.py +47 -0
- sf_veritas/unified_interceptor.py +1580 -0
- sf_veritas/utils.py +39 -0
- sf_veritas-0.10.3.dist-info/METADATA +97 -0
- sf_veritas-0.10.3.dist-info/RECORD +132 -0
- sf_veritas-0.10.3.dist-info/WHEEL +5 -0
- sf_veritas-0.10.3.dist-info/entry_points.txt +2 -0
- sf_veritas-0.10.3.dist-info/top_level.txt +1 -0
- sf_veritas.libs/libbrotlicommon-6ce2a53c.so.1.0.6 +0 -0
- sf_veritas.libs/libbrotlidec-811d1be3.so.1.0.6 +0 -0
- sf_veritas.libs/libcom_err-730ca923.so.2.1 +0 -0
- sf_veritas.libs/libcrypt-52aca757.so.1.1.0 +0 -0
- sf_veritas.libs/libcrypto-bdaed0ea.so.1.1.1k +0 -0
- sf_veritas.libs/libcurl-eaa3cf66.so.4.5.0 +0 -0
- sf_veritas.libs/libgssapi_krb5-323bbd21.so.2.2 +0 -0
- sf_veritas.libs/libidn2-2f4a5893.so.0.3.6 +0 -0
- sf_veritas.libs/libk5crypto-9a74ff38.so.3.1 +0 -0
- sf_veritas.libs/libkeyutils-2777d33d.so.1.6 +0 -0
- sf_veritas.libs/libkrb5-a55300e8.so.3.3 +0 -0
- sf_veritas.libs/libkrb5support-e6594cfc.so.0.1 +0 -0
- sf_veritas.libs/liblber-2-d20824ef.4.so.2.10.9 +0 -0
- sf_veritas.libs/libldap-2-cea2a960.4.so.2.10.9 +0 -0
- sf_veritas.libs/libnghttp2-39367a22.so.14.17.0 +0 -0
- sf_veritas.libs/libpcre2-8-516f4c9d.so.0.7.1 +0 -0
- sf_veritas.libs/libpsl-99becdd3.so.5.3.1 +0 -0
- sf_veritas.libs/libsasl2-7de4d792.so.3.0.0 +0 -0
- sf_veritas.libs/libselinux-d0805dcb.so.1 +0 -0
- sf_veritas.libs/libssh-c11d285b.so.4.8.7 +0 -0
- sf_veritas.libs/libssl-60250281.so.1.1.1k +0 -0
- sf_veritas.libs/libunistring-05abdd40.so.2.1.0 +0 -0
- 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
|
|
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")
|