sf-veritas 0.10.3__cp39-cp39-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-39-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnet.c +924 -0
- sf_veritas/_sffastnet.cpython-39-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnetworkrequest.c +730 -0
- sf_veritas/_sffastnetworkrequest.cpython-39-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan.c +2155 -0
- sf_veritas/_sffuncspan.cpython-39-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan_config.c +617 -0
- sf_veritas/_sffuncspan_config.cpython-39-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfheadercheck.c +341 -0
- sf_veritas/_sfheadercheck.cpython-39-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfnetworkhop.c +1451 -0
- sf_veritas/_sfnetworkhop.cpython-39-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfservice.c +1175 -0
- sf_veritas/_sfservice.cpython-39-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,395 @@
|
|
|
1
|
+
from functools import wraps
|
|
2
|
+
from typing import Callable, List, Optional, Set, Tuple
|
|
3
|
+
|
|
4
|
+
from ... import _sffuncspan_config, app_config
|
|
5
|
+
from ...constants import (
|
|
6
|
+
FUNCSPAN_OVERRIDE_HEADER_BYTES,
|
|
7
|
+
SAILFISH_TRACING_HEADER,
|
|
8
|
+
SAILFISH_TRACING_HEADER_BYTES,
|
|
9
|
+
)
|
|
10
|
+
from ...env_vars import (
|
|
11
|
+
SF_DEBUG,
|
|
12
|
+
SF_NETWORKHOP_CAPTURE_REQUEST_BODY,
|
|
13
|
+
SF_NETWORKHOP_CAPTURE_REQUEST_HEADERS,
|
|
14
|
+
SF_NETWORKHOP_CAPTURE_RESPONSE_BODY,
|
|
15
|
+
SF_NETWORKHOP_CAPTURE_RESPONSE_HEADERS,
|
|
16
|
+
SF_NETWORKHOP_REQUEST_LIMIT_MB,
|
|
17
|
+
SF_NETWORKHOP_RESPONSE_LIMIT_MB,
|
|
18
|
+
)
|
|
19
|
+
from ...fast_network_hop import fast_send_network_hop_fast, register_endpoint
|
|
20
|
+
from ...thread_local import (
|
|
21
|
+
clear_c_tls_parent_trace_id,
|
|
22
|
+
clear_current_request_path,
|
|
23
|
+
clear_outbound_header_base,
|
|
24
|
+
clear_trace_id,
|
|
25
|
+
generate_new_trace_id,
|
|
26
|
+
get_or_set_sf_trace_id,
|
|
27
|
+
get_sf_trace_id,
|
|
28
|
+
set_current_request_path,
|
|
29
|
+
set_outbound_header_base,
|
|
30
|
+
)
|
|
31
|
+
from .cors_utils import inject_sailfish_headers, should_inject_headers
|
|
32
|
+
from .utils import _is_user_code, _unwrap_user_func, should_skip_route # shared helpers
|
|
33
|
+
|
|
34
|
+
# Size limits in bytes
|
|
35
|
+
_REQUEST_LIMIT_BYTES = SF_NETWORKHOP_REQUEST_LIMIT_MB * 1024 * 1024
|
|
36
|
+
_RESPONSE_LIMIT_BYTES = SF_NETWORKHOP_RESPONSE_LIMIT_MB * 1024 * 1024
|
|
37
|
+
|
|
38
|
+
# Pre-registered endpoint IDs
|
|
39
|
+
_ENDPOINT_REGISTRY: dict[tuple, int] = {}
|
|
40
|
+
|
|
41
|
+
# Routes to skip (set by patch_eve)
|
|
42
|
+
_ROUTES_TO_SKIP = []
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ──────────────────────────────────────────────────────────────
|
|
46
|
+
# OTEL-STYLE: Request hooks (before + after)
|
|
47
|
+
# ──────────────────────────────────────────────────────────────
|
|
48
|
+
def _install_request_hooks(app):
|
|
49
|
+
from flask import g, request
|
|
50
|
+
|
|
51
|
+
@app.before_request
|
|
52
|
+
def _extract_sf_header():
|
|
53
|
+
"""OTEL-STYLE: Extract trace header and capture request data before handler."""
|
|
54
|
+
# Set current request path for route-based suppression (SF_DISABLE_INBOUND_NETWORK_TRACING_ON_ROUTES)
|
|
55
|
+
set_current_request_path(request.path)
|
|
56
|
+
|
|
57
|
+
# CRITICAL: Seed/ensure trace_id immediately (BEFORE any outbound work)
|
|
58
|
+
rid = request.headers.get(SAILFISH_TRACING_HEADER)
|
|
59
|
+
if rid:
|
|
60
|
+
# Incoming X-Sf3-Rid header provided - use it
|
|
61
|
+
get_or_set_sf_trace_id(rid, is_associated_with_inbound_request=True)
|
|
62
|
+
else:
|
|
63
|
+
# No incoming X-Sf3-Rid header - generate fresh trace_id for this request
|
|
64
|
+
generate_new_trace_id()
|
|
65
|
+
|
|
66
|
+
# Check for function span capture override header (highest priority!)
|
|
67
|
+
funcspan_override_header = request.headers.get(
|
|
68
|
+
"X-Sf3-FunctionSpanCaptureOverride"
|
|
69
|
+
)
|
|
70
|
+
if funcspan_override_header:
|
|
71
|
+
try:
|
|
72
|
+
_sffuncspan_config.set_thread_override(funcspan_override_header)
|
|
73
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
74
|
+
print(
|
|
75
|
+
f"[[Eve.before_request]] Set function span override from header: {funcspan_override_header}",
|
|
76
|
+
log=False,
|
|
77
|
+
)
|
|
78
|
+
except Exception as e:
|
|
79
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
80
|
+
print(
|
|
81
|
+
f"[[Eve.before_request]] Failed to set function span override: {e}",
|
|
82
|
+
log=False,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# Initialize outbound base without list/allocs from split()
|
|
86
|
+
try:
|
|
87
|
+
trace_id = get_sf_trace_id()
|
|
88
|
+
if trace_id:
|
|
89
|
+
s = str(trace_id)
|
|
90
|
+
i = s.find("/") # session
|
|
91
|
+
j = s.find("/", i + 1) if i != -1 else -1 # page
|
|
92
|
+
if j != -1:
|
|
93
|
+
base_trace = s[:j] # "session/page"
|
|
94
|
+
set_outbound_header_base(
|
|
95
|
+
base_trace=base_trace,
|
|
96
|
+
parent_trace_id=s, # "session/page/uuid"
|
|
97
|
+
funcspan=funcspan_override_header,
|
|
98
|
+
)
|
|
99
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
100
|
+
print(
|
|
101
|
+
f"[[Eve.before_request]] Initialized outbound header base (base={base_trace[:16]}...)",
|
|
102
|
+
log=False,
|
|
103
|
+
)
|
|
104
|
+
except Exception as e:
|
|
105
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
106
|
+
print(
|
|
107
|
+
f"[[Eve.before_request]] Failed to initialize outbound header base: {e}",
|
|
108
|
+
log=False,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Capture request headers if enabled
|
|
112
|
+
if SF_NETWORKHOP_CAPTURE_REQUEST_HEADERS:
|
|
113
|
+
try:
|
|
114
|
+
req_headers = dict(request.headers)
|
|
115
|
+
g._sf_request_headers = req_headers
|
|
116
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
117
|
+
print(
|
|
118
|
+
f"[[Eve]] Captured request headers: {len(req_headers)} headers",
|
|
119
|
+
log=False,
|
|
120
|
+
)
|
|
121
|
+
except Exception as e:
|
|
122
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
123
|
+
print(f"[[Eve]] Failed to capture request headers: {e}", log=False)
|
|
124
|
+
|
|
125
|
+
# Capture request body if enabled
|
|
126
|
+
if SF_NETWORKHOP_CAPTURE_REQUEST_BODY:
|
|
127
|
+
try:
|
|
128
|
+
# Flask/Eve: request.data gives raw bytes
|
|
129
|
+
body = request.get_data(cache=True) # cache=True to not consume it
|
|
130
|
+
if body:
|
|
131
|
+
req_body = body[:_REQUEST_LIMIT_BYTES]
|
|
132
|
+
g._sf_request_body = req_body
|
|
133
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
134
|
+
print(
|
|
135
|
+
f"[[Eve]] Request body capture: {len(req_body)} bytes",
|
|
136
|
+
log=False,
|
|
137
|
+
)
|
|
138
|
+
except Exception as e:
|
|
139
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
140
|
+
print(f"[[Eve]] Failed to capture request body: {e}", log=False)
|
|
141
|
+
|
|
142
|
+
@app.after_request
|
|
143
|
+
def _emit_network_hop(response):
|
|
144
|
+
"""
|
|
145
|
+
OTEL-STYLE: Emit network hop AFTER response is built.
|
|
146
|
+
Eve is Flask-based, so we use the same @after_request pattern.
|
|
147
|
+
"""
|
|
148
|
+
endpoint_id = getattr(g, "_sf_endpoint_id", None)
|
|
149
|
+
if endpoint_id is not None and endpoint_id >= 0:
|
|
150
|
+
try:
|
|
151
|
+
_, session_id = get_or_set_sf_trace_id()
|
|
152
|
+
|
|
153
|
+
# Get captured request data
|
|
154
|
+
req_headers = getattr(g, "_sf_request_headers", None)
|
|
155
|
+
req_body = getattr(g, "_sf_request_body", None)
|
|
156
|
+
|
|
157
|
+
# Capture response headers if enabled
|
|
158
|
+
resp_headers = None
|
|
159
|
+
if SF_NETWORKHOP_CAPTURE_RESPONSE_HEADERS:
|
|
160
|
+
try:
|
|
161
|
+
resp_headers = dict(response.headers)
|
|
162
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
163
|
+
print(
|
|
164
|
+
f"[[Eve]] Captured response headers: {len(resp_headers)} headers",
|
|
165
|
+
log=False,
|
|
166
|
+
)
|
|
167
|
+
except Exception as e:
|
|
168
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
169
|
+
print(
|
|
170
|
+
f"[[Eve]] Failed to capture response headers: {e}",
|
|
171
|
+
log=False,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Capture response body if enabled
|
|
175
|
+
resp_body = None
|
|
176
|
+
if SF_NETWORKHOP_CAPTURE_RESPONSE_BODY:
|
|
177
|
+
try:
|
|
178
|
+
# Flask/Eve response: get_data() returns bytes
|
|
179
|
+
body = response.get_data()
|
|
180
|
+
if body:
|
|
181
|
+
resp_body = body[:_RESPONSE_LIMIT_BYTES]
|
|
182
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
183
|
+
print(
|
|
184
|
+
f"[[Eve]] Captured response body: {len(resp_body)} bytes",
|
|
185
|
+
log=False,
|
|
186
|
+
)
|
|
187
|
+
except Exception as e:
|
|
188
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
189
|
+
print(
|
|
190
|
+
f"[[Eve]] Failed to capture response body: {e}",
|
|
191
|
+
log=False,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
# Extract raw path and query string for C to parse
|
|
195
|
+
raw_path = request.path # e.g., "/log"
|
|
196
|
+
raw_query = request.query_string # Already bytes (e.g., b"foo=5")
|
|
197
|
+
|
|
198
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
199
|
+
print(
|
|
200
|
+
f"[[Eve]] About to emit network hop: endpoint_id={endpoint_id}, "
|
|
201
|
+
f"req_headers={'present' if req_headers else 'None'}, "
|
|
202
|
+
f"req_body={len(req_body) if req_body else 0} bytes, "
|
|
203
|
+
f"resp_headers={'present' if resp_headers else 'None'}, "
|
|
204
|
+
f"resp_body={len(resp_body) if resp_body else 0} bytes",
|
|
205
|
+
log=False,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# Direct C call - queues to background worker, returns instantly
|
|
209
|
+
# C will parse route and query_params from raw data
|
|
210
|
+
fast_send_network_hop_fast(
|
|
211
|
+
session_id=session_id,
|
|
212
|
+
endpoint_id=endpoint_id,
|
|
213
|
+
raw_path=raw_path,
|
|
214
|
+
raw_query_string=raw_query,
|
|
215
|
+
request_headers=req_headers,
|
|
216
|
+
request_body=req_body,
|
|
217
|
+
response_headers=resp_headers,
|
|
218
|
+
response_body=resp_body,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
222
|
+
print(
|
|
223
|
+
f"[[Eve]] Emitted network hop: endpoint_id={endpoint_id} "
|
|
224
|
+
f"session={session_id}",
|
|
225
|
+
log=False,
|
|
226
|
+
)
|
|
227
|
+
except Exception as e: # noqa: BLE001 S110
|
|
228
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
229
|
+
print(f"[[Eve]] Failed to emit network hop: {e}", log=False)
|
|
230
|
+
|
|
231
|
+
# Clear function span override for this request (thread-local cleanup)
|
|
232
|
+
try:
|
|
233
|
+
_sffuncspan_config.clear_thread_override()
|
|
234
|
+
except Exception:
|
|
235
|
+
pass
|
|
236
|
+
|
|
237
|
+
# CRITICAL: Clear C TLS to prevent stale data in thread pools
|
|
238
|
+
clear_c_tls_parent_trace_id()
|
|
239
|
+
|
|
240
|
+
# CRITICAL: Clear outbound header base to prevent stale cached headers
|
|
241
|
+
# ContextVar does NOT automatically clean up in thread pools - must clear explicitly
|
|
242
|
+
clear_outbound_header_base()
|
|
243
|
+
|
|
244
|
+
# CRITICAL: Clear trace_id to ensure fresh generation for next request
|
|
245
|
+
# Without this, get_or_set_sf_trace_id() reuses trace_id from previous request
|
|
246
|
+
# causing X-Sf4-Prid to stay constant when no incoming X-Sf3-Rid header
|
|
247
|
+
clear_trace_id()
|
|
248
|
+
|
|
249
|
+
# CRITICAL: Clear current request path to prevent stale data in thread pools
|
|
250
|
+
clear_current_request_path()
|
|
251
|
+
|
|
252
|
+
return response
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
# ──────────────────────────────────────────────────────────────
|
|
256
|
+
# OTEL-STYLE: Per-view endpoint metadata capture
|
|
257
|
+
# ──────────────────────────────────────────────────────────────
|
|
258
|
+
def _hop_wrapper(view_fn: Callable):
|
|
259
|
+
"""
|
|
260
|
+
OTEL-STYLE: Pre-register endpoint and store endpoint_id in flask.g.
|
|
261
|
+
Emission happens in @after_request hook with captured body/headers.
|
|
262
|
+
"""
|
|
263
|
+
from flask import g
|
|
264
|
+
|
|
265
|
+
real_fn = _unwrap_user_func(view_fn)
|
|
266
|
+
|
|
267
|
+
# Skip Strawberry handlers – handled by Strawberry extension
|
|
268
|
+
if real_fn.__module__.startswith("strawberry"):
|
|
269
|
+
return view_fn
|
|
270
|
+
|
|
271
|
+
code = getattr(real_fn, "__code__", None)
|
|
272
|
+
if not code or not _is_user_code(code.co_filename):
|
|
273
|
+
return view_fn
|
|
274
|
+
|
|
275
|
+
hop_key = (code.co_filename, code.co_firstlineno)
|
|
276
|
+
fn_name = real_fn.__name__
|
|
277
|
+
filename = code.co_filename
|
|
278
|
+
line_no = code.co_firstlineno
|
|
279
|
+
|
|
280
|
+
# We'll check route at runtime in the wrapper since we need request context
|
|
281
|
+
# Pre-register endpoint if user code
|
|
282
|
+
endpoint_id = _ENDPOINT_REGISTRY.get(hop_key)
|
|
283
|
+
if endpoint_id is None:
|
|
284
|
+
# Note: route will be None here, will be passed at runtime
|
|
285
|
+
endpoint_id = register_endpoint(
|
|
286
|
+
line=str(line_no), column="0", name=fn_name, entrypoint=filename, route=None
|
|
287
|
+
)
|
|
288
|
+
if endpoint_id >= 0:
|
|
289
|
+
_ENDPOINT_REGISTRY[hop_key] = endpoint_id
|
|
290
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
291
|
+
print(
|
|
292
|
+
f"[[Eve]] Registered endpoint: {fn_name} @ {filename}:{line_no} (id={endpoint_id})",
|
|
293
|
+
log=False,
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
@wraps(view_fn)
|
|
297
|
+
def _wrapped(*args, **kwargs):
|
|
298
|
+
from flask import request
|
|
299
|
+
|
|
300
|
+
# Check if route should be skipped
|
|
301
|
+
route_pattern = request.path
|
|
302
|
+
if route_pattern and should_skip_route(route_pattern, _ROUTES_TO_SKIP):
|
|
303
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
304
|
+
print(
|
|
305
|
+
f"[[Eve]] Skipping endpoint (route matches skip pattern): {route_pattern}",
|
|
306
|
+
log=False,
|
|
307
|
+
)
|
|
308
|
+
return view_fn(*args, **kwargs)
|
|
309
|
+
|
|
310
|
+
# OTEL-STYLE: Store endpoint_id for after_request to emit
|
|
311
|
+
if not hasattr(g, "_sf_endpoint_id"):
|
|
312
|
+
g._sf_endpoint_id = endpoint_id
|
|
313
|
+
|
|
314
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
315
|
+
print(
|
|
316
|
+
f"[[Eve]] Captured endpoint: {fn_name} ({filename}:{line_no}) endpoint_id={endpoint_id}",
|
|
317
|
+
log=False,
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
return view_fn(*args, **kwargs)
|
|
321
|
+
|
|
322
|
+
return _wrapped
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _patch_add_url_rule(cls):
|
|
326
|
+
"""
|
|
327
|
+
Patch add_url_rule on *cls* (cls is Eve or Blueprint) so that the final
|
|
328
|
+
stored endpoint function is wrapped *after* Flask has done its own
|
|
329
|
+
bookkeeping. This catches:
|
|
330
|
+
• Eve resource endpoints created internally via register_resource()
|
|
331
|
+
• Manual @app.route() decorators
|
|
332
|
+
• Blueprints, CBVs, etc.
|
|
333
|
+
"""
|
|
334
|
+
original_add = cls.add_url_rule
|
|
335
|
+
|
|
336
|
+
def patched_add(
|
|
337
|
+
self, rule, endpoint=None, view_func=None, **options
|
|
338
|
+
): # noqa: ANN001
|
|
339
|
+
# let Eve/Flask register the route first
|
|
340
|
+
original_add(self, rule, endpoint=endpoint, view_func=view_func, **options)
|
|
341
|
+
|
|
342
|
+
ep = endpoint or (view_func and view_func.__name__)
|
|
343
|
+
if not ep: # defensive
|
|
344
|
+
return
|
|
345
|
+
|
|
346
|
+
target = self.view_functions.get(ep)
|
|
347
|
+
if callable(target):
|
|
348
|
+
self.view_functions[ep] = _hop_wrapper(target)
|
|
349
|
+
|
|
350
|
+
cls.add_url_rule = patched_add
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
# ──────────────────────────────────────────────────────────────
|
|
354
|
+
# Public entry-point
|
|
355
|
+
# ──────────────────────────────────────────────────────────────
|
|
356
|
+
def patch_eve(routes_to_skip: Optional[List[str]] = None):
|
|
357
|
+
"""
|
|
358
|
+
• Adds ContextVar propagation middleware
|
|
359
|
+
• Wraps every Eve endpoint (and Blueprint endpoints) to emit one hop
|
|
360
|
+
"""
|
|
361
|
+
global _ROUTES_TO_SKIP
|
|
362
|
+
_ROUTES_TO_SKIP = routes_to_skip or []
|
|
363
|
+
|
|
364
|
+
try:
|
|
365
|
+
import eve
|
|
366
|
+
from flask import Blueprint # Eve relies on Flask blueprints
|
|
367
|
+
except ImportError:
|
|
368
|
+
return
|
|
369
|
+
|
|
370
|
+
# Guard against double-patching
|
|
371
|
+
if getattr(eve.Eve, "__sf_tracing_patched__", False):
|
|
372
|
+
return
|
|
373
|
+
|
|
374
|
+
# 1. Patch Eve.add_url_rule *and* Blueprint.add_url_rule
|
|
375
|
+
_patch_add_url_rule(eve.Eve)
|
|
376
|
+
_patch_add_url_rule(Blueprint)
|
|
377
|
+
|
|
378
|
+
# 2. Patch Eve.__init__ to install request hooks
|
|
379
|
+
# Note: CORS patching is handled by patch_flask_cors() since Eve uses flask-cors
|
|
380
|
+
original_init = eve.Eve.__init__
|
|
381
|
+
|
|
382
|
+
def patched_init(self, import_name=None, settings=None, *args, **kwargs):
|
|
383
|
+
# Call original init
|
|
384
|
+
original_init(self, import_name, settings, *args, **kwargs)
|
|
385
|
+
|
|
386
|
+
# Install request hooks
|
|
387
|
+
_install_request_hooks(self)
|
|
388
|
+
|
|
389
|
+
eve.Eve.__init__ = patched_init
|
|
390
|
+
eve.Eve.__sf_tracing_patched__ = True
|
|
391
|
+
|
|
392
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
393
|
+
print(
|
|
394
|
+
"[[patch_eve]] OTEL-style request hooks + hop wrapper installed (CORS patching handled by Flask)", log=False
|
|
395
|
+
)
|