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,501 @@
1
+ """
2
+ Klein (Twisted web framework) patch for OTEL-style network hop capture.
3
+ Captures request/response headers and bodies when enabled via env vars.
4
+ """
5
+
6
+ import functools
7
+ import inspect
8
+ from typing import List, Optional
9
+
10
+ from ... import _sffuncspan_config, app_config
11
+ from ...constants import SAILFISH_TRACING_HEADER
12
+ from ...custom_excepthook import custom_excepthook
13
+ from ...env_vars import (
14
+ SF_DEBUG,
15
+ SF_NETWORKHOP_CAPTURE_REQUEST_BODY,
16
+ SF_NETWORKHOP_CAPTURE_REQUEST_HEADERS,
17
+ SF_NETWORKHOP_CAPTURE_RESPONSE_BODY,
18
+ SF_NETWORKHOP_CAPTURE_RESPONSE_HEADERS,
19
+ SF_NETWORKHOP_REQUEST_LIMIT_MB,
20
+ SF_NETWORKHOP_RESPONSE_LIMIT_MB,
21
+ )
22
+ from ...fast_network_hop import fast_send_network_hop_fast, register_endpoint
23
+ from ...thread_local import get_or_set_sf_trace_id
24
+ from .utils import _is_user_code, _unwrap_user_func, should_skip_route
25
+
26
+ # Size limits in bytes
27
+ _REQUEST_LIMIT_BYTES = SF_NETWORKHOP_REQUEST_LIMIT_MB * 1024 * 1024
28
+ _RESPONSE_LIMIT_BYTES = SF_NETWORKHOP_RESPONSE_LIMIT_MB * 1024 * 1024
29
+
30
+ # Pre-registered endpoint IDs
31
+ _ENDPOINT_REGISTRY: dict[tuple, int] = {}
32
+
33
+ # Routes to skip (set by patch_klein)
34
+ _ROUTES_TO_SKIP = []
35
+
36
+
37
+ def patch_klein(routes_to_skip: Optional[List[str]] = None):
38
+ """
39
+ OTEL-STYLE Klein patch:
40
+ • Pre-registers endpoints on first route decoration
41
+ • Captures request headers/body if enabled
42
+ • Emits network hop AFTER handler completes (zero-overhead)
43
+ • Captures response headers/body if enabled
44
+ • Funnels exceptions through custom_excepthook
45
+ No-op if Klein isn't installed.
46
+ """
47
+ global _ROUTES_TO_SKIP
48
+ _ROUTES_TO_SKIP = routes_to_skip or []
49
+
50
+ try:
51
+ import klein
52
+ except ImportError:
53
+ return
54
+
55
+ original_route = klein.Klein.route
56
+
57
+ def patched_route(self, *args, **kwargs):
58
+ # Grab Klein's decorator for this pattern
59
+ original_decorator = original_route(self, *args, **kwargs)
60
+
61
+ def new_decorator(fn):
62
+ real_fn = _unwrap_user_func(fn)
63
+
64
+ # Skip non-user code
65
+ code = getattr(real_fn, "__code__", None)
66
+ if not code or not _is_user_code(code.co_filename):
67
+ return original_decorator(fn)
68
+
69
+ filename = code.co_filename
70
+ line_no = code.co_firstlineno
71
+ fn_name = real_fn.__name__
72
+ hop_key = (filename, line_no)
73
+
74
+ # Get route pattern from args (first arg after self is usually the path)
75
+ route_pattern = args[0] if args else None
76
+
77
+ # Check if route should be skipped
78
+ if route_pattern and should_skip_route(route_pattern, _ROUTES_TO_SKIP):
79
+ if SF_DEBUG and app_config._interceptors_initialized:
80
+ print(
81
+ f"[[Klein]] Skipping endpoint (route matches skip pattern): {route_pattern}",
82
+ log=False,
83
+ )
84
+ return original_decorator(fn)
85
+
86
+ # Pre-register endpoint
87
+ endpoint_id = _ENDPOINT_REGISTRY.get(hop_key)
88
+ if endpoint_id is None:
89
+ endpoint_id = register_endpoint(
90
+ line=str(line_no),
91
+ column="0",
92
+ name=fn_name,
93
+ entrypoint=filename,
94
+ route=route_pattern,
95
+ )
96
+ if endpoint_id >= 0:
97
+ _ENDPOINT_REGISTRY[hop_key] = endpoint_id
98
+ if SF_DEBUG and app_config._interceptors_initialized:
99
+ print(
100
+ f"[[Klein]] Registered endpoint: {fn_name} @ {filename}:{line_no} (id={endpoint_id})",
101
+ log=False,
102
+ )
103
+
104
+ # Check if async or sync
105
+ is_async = inspect.iscoroutinefunction(real_fn)
106
+
107
+ if is_async:
108
+
109
+ @functools.wraps(fn)
110
+ async def wrapped_async(request, *f_args, **f_kwargs):
111
+ # 1. Trace-id propagation
112
+ header = request.getHeader(SAILFISH_TRACING_HEADER)
113
+ if header:
114
+ get_or_set_sf_trace_id(
115
+ header, is_associated_with_inbound_request=True
116
+ )
117
+
118
+ # Check for function span capture override header (highest priority!)
119
+ funcspan_override_header = request.getHeader(
120
+ "X-Sf3-FunctionSpanCaptureOverride"
121
+ )
122
+ if funcspan_override_header:
123
+ try:
124
+ _sffuncspan_config.set_thread_override(
125
+ funcspan_override_header
126
+ )
127
+ if SF_DEBUG and app_config._interceptors_initialized:
128
+ print(
129
+ f"[[Klein.async]] Set function span override from header: {funcspan_override_header}",
130
+ log=False,
131
+ )
132
+ except Exception as e:
133
+ if SF_DEBUG and app_config._interceptors_initialized:
134
+ print(
135
+ f"[[Klein.async]] Failed to set function span override: {e}",
136
+ log=False,
137
+ )
138
+
139
+ # 2. Capture request headers if enabled
140
+ req_headers = None
141
+ if SF_NETWORKHOP_CAPTURE_REQUEST_HEADERS:
142
+ try:
143
+ # Klein/Twisted request.requestHeaders is a Headers object
144
+ req_headers = dict(
145
+ request.requestHeaders.getAllRawHeaders()
146
+ )
147
+ if SF_DEBUG and app_config._interceptors_initialized:
148
+ print(
149
+ f"[[Klein]] Captured request headers: {len(req_headers)} headers",
150
+ log=False,
151
+ )
152
+ except Exception as e:
153
+ if SF_DEBUG and app_config._interceptors_initialized:
154
+ print(
155
+ f"[[Klein]] Failed to capture request headers: {e}",
156
+ log=False,
157
+ )
158
+
159
+ # 3. Capture request body if enabled
160
+ req_body = None
161
+ if SF_NETWORKHOP_CAPTURE_REQUEST_BODY:
162
+ try:
163
+ # Klein/Twisted request.content is a file-like object
164
+ body = request.content.read(_REQUEST_LIMIT_BYTES)
165
+ request.content.seek(0) # Reset for handler
166
+ req_body = body if body else None
167
+ if SF_DEBUG and app_config._interceptors_initialized:
168
+ print(
169
+ f"[[Klein]] Request body capture: {len(body) if body else 0} bytes (method={request.method})",
170
+ log=False,
171
+ )
172
+ except Exception as e:
173
+ if SF_DEBUG and app_config._interceptors_initialized:
174
+ print(
175
+ f"[[Klein]] Failed to capture request body: {e}",
176
+ log=False,
177
+ )
178
+
179
+ if SF_DEBUG and app_config._interceptors_initialized:
180
+ print(
181
+ f"[[Klein]] Captured endpoint: {fn_name} ({filename}:{line_no}) endpoint_id={endpoint_id}",
182
+ log=False,
183
+ )
184
+
185
+ # 4. Call handler and capture exceptions
186
+ response = None
187
+ try:
188
+ response = await fn(request, *f_args, **f_kwargs)
189
+ except Exception as exc:
190
+ custom_excepthook(type(exc), exc, exc.__traceback__)
191
+ raise
192
+
193
+ # 5. Capture response headers if enabled
194
+ resp_headers = None
195
+ if SF_NETWORKHOP_CAPTURE_RESPONSE_HEADERS:
196
+ try:
197
+ # Klein/Twisted request.responseHeaders is a Headers object
198
+ resp_headers = dict(
199
+ request.responseHeaders.getAllRawHeaders()
200
+ )
201
+ if SF_DEBUG and app_config._interceptors_initialized:
202
+ print(
203
+ f"[[Klein]] Captured response headers: {len(resp_headers)} headers",
204
+ log=False,
205
+ )
206
+ except Exception as e:
207
+ if SF_DEBUG and app_config._interceptors_initialized:
208
+ print(
209
+ f"[[Klein]] Failed to capture response headers: {e}",
210
+ log=False,
211
+ )
212
+
213
+ # 6. Capture response body if enabled
214
+ resp_body = None
215
+ if SF_NETWORKHOP_CAPTURE_RESPONSE_BODY:
216
+ try:
217
+ # Klein handlers return bytes directly
218
+ if isinstance(response, bytes):
219
+ resp_body = response[:_RESPONSE_LIMIT_BYTES]
220
+ if SF_DEBUG and app_config._interceptors_initialized:
221
+ print(
222
+ f"[[Klein]] Captured response body: {len(resp_body)} bytes",
223
+ log=False,
224
+ )
225
+ elif isinstance(response, str):
226
+ resp_body = response.encode("utf-8")[
227
+ :_RESPONSE_LIMIT_BYTES
228
+ ]
229
+ if SF_DEBUG and app_config._interceptors_initialized:
230
+ print(
231
+ f"[[Klein]] Captured response body (str): {len(resp_body)} bytes",
232
+ log=False,
233
+ )
234
+ except Exception as e:
235
+ if SF_DEBUG and app_config._interceptors_initialized:
236
+ print(
237
+ f"[[Klein]] Failed to capture response body: {e}",
238
+ log=False,
239
+ )
240
+
241
+ # 7. OTEL-STYLE: Emit network hop AFTER handler completes
242
+ if endpoint_id is not None and endpoint_id >= 0:
243
+ try:
244
+ _, session_id = get_or_set_sf_trace_id()
245
+
246
+ # Extract raw path and query string for C to parse
247
+ raw_path = (
248
+ request.path
249
+ if isinstance(request.path, str)
250
+ else request.path.decode("utf-8")
251
+ )
252
+ # Twisted request.uri includes full URI with query string (bytes)
253
+ uri_parts = request.uri.split(b"?", 1)
254
+ raw_query = uri_parts[1] if len(uri_parts) > 1 else b""
255
+
256
+ if SF_DEBUG and app_config._interceptors_initialized:
257
+ print(
258
+ f"[[Klein]] About to emit network hop: endpoint_id={endpoint_id}, "
259
+ f"req_headers={'present' if req_headers else 'None'}, "
260
+ f"req_body={len(req_body) if req_body else 0} bytes, "
261
+ f"resp_headers={'present' if resp_headers else 'None'}, "
262
+ f"resp_body={len(resp_body) if resp_body else 0} bytes",
263
+ log=False,
264
+ )
265
+
266
+ # Direct C call - queues to background worker, returns instantly
267
+ # C will parse route and query_params from raw data
268
+ fast_send_network_hop_fast(
269
+ session_id=session_id,
270
+ endpoint_id=endpoint_id,
271
+ raw_path=raw_path,
272
+ raw_query_string=raw_query,
273
+ request_headers=req_headers,
274
+ request_body=req_body,
275
+ response_headers=resp_headers,
276
+ response_body=resp_body,
277
+ )
278
+
279
+ if SF_DEBUG and app_config._interceptors_initialized:
280
+ print(
281
+ f"[[Klein]] Emitted network hop: endpoint_id={endpoint_id} "
282
+ f"session={session_id}",
283
+ log=False,
284
+ )
285
+ except Exception as e: # noqa: BLE001 S110
286
+ if SF_DEBUG and app_config._interceptors_initialized:
287
+ print(
288
+ f"[[Klein]] Failed to emit network hop: {e}",
289
+ log=False,
290
+ )
291
+
292
+ # Clear function span override for this request (thread-local cleanup)
293
+ try:
294
+ _sffuncspan_config.clear_thread_override()
295
+ except Exception:
296
+ pass
297
+
298
+ return response
299
+
300
+ return original_decorator(wrapped_async)
301
+ else:
302
+
303
+ @functools.wraps(fn)
304
+ def wrapped_sync(request, *f_args, **f_kwargs):
305
+ # 1. Trace-id propagation
306
+ header = request.getHeader(SAILFISH_TRACING_HEADER)
307
+ if header:
308
+ get_or_set_sf_trace_id(
309
+ header, is_associated_with_inbound_request=True
310
+ )
311
+
312
+ # Check for function span capture override header (highest priority!)
313
+ funcspan_override_header = request.getHeader(
314
+ "X-Sf3-FunctionSpanCaptureOverride"
315
+ )
316
+ if funcspan_override_header:
317
+ try:
318
+ _sffuncspan_config.set_thread_override(
319
+ funcspan_override_header
320
+ )
321
+ if SF_DEBUG and app_config._interceptors_initialized:
322
+ print(
323
+ f"[[Klein.sync]] Set function span override from header: {funcspan_override_header}",
324
+ log=False,
325
+ )
326
+ except Exception as e:
327
+ if SF_DEBUG and app_config._interceptors_initialized:
328
+ print(
329
+ f"[[Klein.sync]] Failed to set function span override: {e}",
330
+ log=False,
331
+ )
332
+
333
+ # 2. Capture request headers if enabled
334
+ req_headers = None
335
+ if SF_NETWORKHOP_CAPTURE_REQUEST_HEADERS:
336
+ try:
337
+ # Klein/Twisted request.requestHeaders is a Headers object
338
+ req_headers = dict(
339
+ request.requestHeaders.getAllRawHeaders()
340
+ )
341
+ if SF_DEBUG and app_config._interceptors_initialized:
342
+ print(
343
+ f"[[Klein]] Captured request headers: {len(req_headers)} headers",
344
+ log=False,
345
+ )
346
+ except Exception as e:
347
+ if SF_DEBUG and app_config._interceptors_initialized:
348
+ print(
349
+ f"[[Klein]] Failed to capture request headers: {e}",
350
+ log=False,
351
+ )
352
+
353
+ # 3. Capture request body if enabled
354
+ req_body = None
355
+ if SF_NETWORKHOP_CAPTURE_REQUEST_BODY:
356
+ try:
357
+ # Klein/Twisted request.content is a file-like object
358
+ body = request.content.read(_REQUEST_LIMIT_BYTES)
359
+ request.content.seek(0) # Reset for handler
360
+ req_body = body if body else None
361
+ if SF_DEBUG and app_config._interceptors_initialized:
362
+ print(
363
+ f"[[Klein]] Request body capture: {len(body) if body else 0} bytes (method={request.method})",
364
+ log=False,
365
+ )
366
+ except Exception as e:
367
+ if SF_DEBUG and app_config._interceptors_initialized:
368
+ print(
369
+ f"[[Klein]] Failed to capture request body: {e}",
370
+ log=False,
371
+ )
372
+
373
+ if SF_DEBUG and app_config._interceptors_initialized:
374
+ print(
375
+ f"[[Klein]] Captured endpoint: {fn_name} ({filename}:{line_no}) endpoint_id={endpoint_id}",
376
+ log=False,
377
+ )
378
+
379
+ # 4. Call handler and capture exceptions
380
+ response = None
381
+ try:
382
+ response = fn(request, *f_args, **f_kwargs)
383
+ except Exception as exc:
384
+ custom_excepthook(type(exc), exc, exc.__traceback__)
385
+ raise
386
+
387
+ # 5. Capture response headers if enabled
388
+ resp_headers = None
389
+ if SF_NETWORKHOP_CAPTURE_RESPONSE_HEADERS:
390
+ try:
391
+ # Klein/Twisted request.responseHeaders is a Headers object
392
+ resp_headers = dict(
393
+ request.responseHeaders.getAllRawHeaders()
394
+ )
395
+ if SF_DEBUG and app_config._interceptors_initialized:
396
+ print(
397
+ f"[[Klein]] Captured response headers: {len(resp_headers)} headers",
398
+ log=False,
399
+ )
400
+ except Exception as e:
401
+ if SF_DEBUG and app_config._interceptors_initialized:
402
+ print(
403
+ f"[[Klein]] Failed to capture response headers: {e}",
404
+ log=False,
405
+ )
406
+
407
+ # 6. Capture response body if enabled
408
+ resp_body = None
409
+ if SF_NETWORKHOP_CAPTURE_RESPONSE_BODY:
410
+ try:
411
+ # Klein handlers return bytes directly
412
+ if isinstance(response, bytes):
413
+ resp_body = response[:_RESPONSE_LIMIT_BYTES]
414
+ if SF_DEBUG and app_config._interceptors_initialized:
415
+ print(
416
+ f"[[Klein]] Captured response body: {len(resp_body)} bytes",
417
+ log=False,
418
+ )
419
+ elif isinstance(response, str):
420
+ resp_body = response.encode("utf-8")[
421
+ :_RESPONSE_LIMIT_BYTES
422
+ ]
423
+ if SF_DEBUG and app_config._interceptors_initialized:
424
+ print(
425
+ f"[[Klein]] Captured response body (str): {len(resp_body)} bytes",
426
+ log=False,
427
+ )
428
+ except Exception as e:
429
+ if SF_DEBUG and app_config._interceptors_initialized:
430
+ print(
431
+ f"[[Klein]] Failed to capture response body: {e}",
432
+ log=False,
433
+ )
434
+
435
+ # 7. OTEL-STYLE: Emit network hop AFTER handler completes
436
+ if endpoint_id is not None and endpoint_id >= 0:
437
+ try:
438
+ _, session_id = get_or_set_sf_trace_id()
439
+
440
+ # Extract raw path and query string for C to parse
441
+ raw_path = (
442
+ request.path
443
+ if isinstance(request.path, str)
444
+ else request.path.decode("utf-8")
445
+ )
446
+ # Twisted request.uri includes full URI with query string (bytes)
447
+ uri_parts = request.uri.split(b"?", 1)
448
+ raw_query = uri_parts[1] if len(uri_parts) > 1 else b""
449
+
450
+ if SF_DEBUG and app_config._interceptors_initialized:
451
+ print(
452
+ f"[[Klein]] About to emit network hop: endpoint_id={endpoint_id}, "
453
+ f"req_headers={'present' if req_headers else 'None'}, "
454
+ f"req_body={len(req_body) if req_body else 0} bytes, "
455
+ f"resp_headers={'present' if resp_headers else 'None'}, "
456
+ f"resp_body={len(resp_body) if resp_body else 0} bytes",
457
+ log=False,
458
+ )
459
+
460
+ # Direct C call - queues to background worker, returns instantly
461
+ # C will parse route and query_params from raw data
462
+ fast_send_network_hop_fast(
463
+ session_id=session_id,
464
+ endpoint_id=endpoint_id,
465
+ raw_path=raw_path,
466
+ raw_query_string=raw_query,
467
+ request_headers=req_headers,
468
+ request_body=req_body,
469
+ response_headers=resp_headers,
470
+ response_body=resp_body,
471
+ )
472
+
473
+ if SF_DEBUG and app_config._interceptors_initialized:
474
+ print(
475
+ f"[[Klein]] Emitted network hop: endpoint_id={endpoint_id} "
476
+ f"session={session_id}",
477
+ log=False,
478
+ )
479
+ except Exception as e: # noqa: BLE001 S110
480
+ if SF_DEBUG and app_config._interceptors_initialized:
481
+ print(
482
+ f"[[Klein]] Failed to emit network hop: {e}",
483
+ log=False,
484
+ )
485
+
486
+ # Clear function span override for this request (thread-local cleanup)
487
+ try:
488
+ _sffuncspan_config.clear_thread_override()
489
+ except Exception:
490
+ pass
491
+
492
+ return response
493
+
494
+ return original_decorator(wrapped_sync)
495
+
496
+ return new_decorator
497
+
498
+ klein.Klein.route = patched_route
499
+
500
+ if SF_DEBUG and app_config._interceptors_initialized:
501
+ print("[[patch_klein]] OTEL-style route patch applied", log=False)