sf-veritas 0.11.10__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.
Files changed (141) hide show
  1. sf_veritas/__init__.py +46 -0
  2. sf_veritas/_auto_preload.py +73 -0
  3. sf_veritas/_sfconfig.c +162 -0
  4. sf_veritas/_sfconfig.cpython-314-x86_64-linux-gnu.so +0 -0
  5. sf_veritas/_sfcrashhandler.c +267 -0
  6. sf_veritas/_sfcrashhandler.cpython-314-x86_64-linux-gnu.so +0 -0
  7. sf_veritas/_sffastlog.c +953 -0
  8. sf_veritas/_sffastlog.cpython-314-x86_64-linux-gnu.so +0 -0
  9. sf_veritas/_sffastnet.c +994 -0
  10. sf_veritas/_sffastnet.cpython-314-x86_64-linux-gnu.so +0 -0
  11. sf_veritas/_sffastnetworkrequest.c +727 -0
  12. sf_veritas/_sffastnetworkrequest.cpython-314-x86_64-linux-gnu.so +0 -0
  13. sf_veritas/_sffuncspan.c +2791 -0
  14. sf_veritas/_sffuncspan.cpython-314-x86_64-linux-gnu.so +0 -0
  15. sf_veritas/_sffuncspan_config.c +730 -0
  16. sf_veritas/_sffuncspan_config.cpython-314-x86_64-linux-gnu.so +0 -0
  17. sf_veritas/_sfheadercheck.c +341 -0
  18. sf_veritas/_sfheadercheck.cpython-314-x86_64-linux-gnu.so +0 -0
  19. sf_veritas/_sfnetworkhop.c +1454 -0
  20. sf_veritas/_sfnetworkhop.cpython-314-x86_64-linux-gnu.so +0 -0
  21. sf_veritas/_sfservice.c +1223 -0
  22. sf_veritas/_sfservice.cpython-314-x86_64-linux-gnu.so +0 -0
  23. sf_veritas/_sfteepreload.c +6227 -0
  24. sf_veritas/app_config.py +57 -0
  25. sf_veritas/cli.py +336 -0
  26. sf_veritas/constants.py +10 -0
  27. sf_veritas/custom_excepthook.py +304 -0
  28. sf_veritas/custom_log_handler.py +146 -0
  29. sf_veritas/custom_output_wrapper.py +153 -0
  30. sf_veritas/custom_print.py +153 -0
  31. sf_veritas/django_app.py +5 -0
  32. sf_veritas/env_vars.py +186 -0
  33. sf_veritas/exception_handling_middleware.py +18 -0
  34. sf_veritas/exception_metaclass.py +69 -0
  35. sf_veritas/fast_frame_info.py +116 -0
  36. sf_veritas/fast_network_hop.py +293 -0
  37. sf_veritas/frame_tools.py +112 -0
  38. sf_veritas/funcspan_config_loader.py +693 -0
  39. sf_veritas/function_span_profiler.py +1313 -0
  40. sf_veritas/get_preload_path.py +34 -0
  41. sf_veritas/import_hook.py +62 -0
  42. sf_veritas/infra_details/__init__.py +3 -0
  43. sf_veritas/infra_details/get_infra_details.py +24 -0
  44. sf_veritas/infra_details/kubernetes/__init__.py +3 -0
  45. sf_veritas/infra_details/kubernetes/get_cluster_name.py +147 -0
  46. sf_veritas/infra_details/kubernetes/get_details.py +7 -0
  47. sf_veritas/infra_details/running_on/__init__.py +17 -0
  48. sf_veritas/infra_details/running_on/kubernetes.py +11 -0
  49. sf_veritas/interceptors.py +543 -0
  50. sf_veritas/libsfnettee.so +0 -0
  51. sf_veritas/local_env_detect.py +118 -0
  52. sf_veritas/package_metadata.py +6 -0
  53. sf_veritas/patches/__init__.py +0 -0
  54. sf_veritas/patches/_patch_tracker.py +74 -0
  55. sf_veritas/patches/concurrent_futures.py +19 -0
  56. sf_veritas/patches/constants.py +1 -0
  57. sf_veritas/patches/exceptions.py +82 -0
  58. sf_veritas/patches/multiprocessing.py +32 -0
  59. sf_veritas/patches/network_libraries/__init__.py +99 -0
  60. sf_veritas/patches/network_libraries/aiohttp.py +294 -0
  61. sf_veritas/patches/network_libraries/curl_cffi.py +363 -0
  62. sf_veritas/patches/network_libraries/http_client.py +670 -0
  63. sf_veritas/patches/network_libraries/httpcore.py +580 -0
  64. sf_veritas/patches/network_libraries/httplib2.py +315 -0
  65. sf_veritas/patches/network_libraries/httpx.py +557 -0
  66. sf_veritas/patches/network_libraries/niquests.py +218 -0
  67. sf_veritas/patches/network_libraries/pycurl.py +399 -0
  68. sf_veritas/patches/network_libraries/requests.py +595 -0
  69. sf_veritas/patches/network_libraries/ssl_socket.py +822 -0
  70. sf_veritas/patches/network_libraries/tornado.py +360 -0
  71. sf_veritas/patches/network_libraries/treq.py +270 -0
  72. sf_veritas/patches/network_libraries/urllib_request.py +483 -0
  73. sf_veritas/patches/network_libraries/utils.py +598 -0
  74. sf_veritas/patches/os.py +17 -0
  75. sf_veritas/patches/threading.py +231 -0
  76. sf_veritas/patches/web_frameworks/__init__.py +54 -0
  77. sf_veritas/patches/web_frameworks/aiohttp.py +798 -0
  78. sf_veritas/patches/web_frameworks/async_websocket_consumer.py +337 -0
  79. sf_veritas/patches/web_frameworks/blacksheep.py +532 -0
  80. sf_veritas/patches/web_frameworks/bottle.py +513 -0
  81. sf_veritas/patches/web_frameworks/cherrypy.py +683 -0
  82. sf_veritas/patches/web_frameworks/cors_utils.py +122 -0
  83. sf_veritas/patches/web_frameworks/django.py +963 -0
  84. sf_veritas/patches/web_frameworks/eve.py +401 -0
  85. sf_veritas/patches/web_frameworks/falcon.py +931 -0
  86. sf_veritas/patches/web_frameworks/fastapi.py +738 -0
  87. sf_veritas/patches/web_frameworks/flask.py +526 -0
  88. sf_veritas/patches/web_frameworks/klein.py +501 -0
  89. sf_veritas/patches/web_frameworks/litestar.py +616 -0
  90. sf_veritas/patches/web_frameworks/pyramid.py +440 -0
  91. sf_veritas/patches/web_frameworks/quart.py +841 -0
  92. sf_veritas/patches/web_frameworks/robyn.py +708 -0
  93. sf_veritas/patches/web_frameworks/sanic.py +874 -0
  94. sf_veritas/patches/web_frameworks/starlette.py +742 -0
  95. sf_veritas/patches/web_frameworks/strawberry.py +1446 -0
  96. sf_veritas/patches/web_frameworks/tornado.py +485 -0
  97. sf_veritas/patches/web_frameworks/utils.py +170 -0
  98. sf_veritas/print_override.py +13 -0
  99. sf_veritas/regular_data_transmitter.py +444 -0
  100. sf_veritas/request_interceptor.py +401 -0
  101. sf_veritas/request_utils.py +550 -0
  102. sf_veritas/segfault_handler.py +116 -0
  103. sf_veritas/server_status.py +1 -0
  104. sf_veritas/shutdown_flag.py +11 -0
  105. sf_veritas/subprocess_startup.py +3 -0
  106. sf_veritas/test_cli.py +145 -0
  107. sf_veritas/thread_local.py +1319 -0
  108. sf_veritas/timeutil.py +114 -0
  109. sf_veritas/transmit_exception_to_sailfish.py +28 -0
  110. sf_veritas/transmitter.py +132 -0
  111. sf_veritas/types.py +47 -0
  112. sf_veritas/unified_interceptor.py +1678 -0
  113. sf_veritas/utils.py +39 -0
  114. sf_veritas-0.11.10.dist-info/METADATA +97 -0
  115. sf_veritas-0.11.10.dist-info/RECORD +141 -0
  116. sf_veritas-0.11.10.dist-info/WHEEL +5 -0
  117. sf_veritas-0.11.10.dist-info/entry_points.txt +2 -0
  118. sf_veritas-0.11.10.dist-info/top_level.txt +1 -0
  119. sf_veritas.libs/libbrotlicommon-6ce2a53c.so.1.0.6 +0 -0
  120. sf_veritas.libs/libbrotlidec-811d1be3.so.1.0.6 +0 -0
  121. sf_veritas.libs/libcom_err-730ca923.so.2.1 +0 -0
  122. sf_veritas.libs/libcrypt-52aca757.so.1.1.0 +0 -0
  123. sf_veritas.libs/libcrypto-bdaed0ea.so.1.1.1k +0 -0
  124. sf_veritas.libs/libcurl-eaa3cf66.so.4.5.0 +0 -0
  125. sf_veritas.libs/libgssapi_krb5-323bbd21.so.2.2 +0 -0
  126. sf_veritas.libs/libidn2-2f4a5893.so.0.3.6 +0 -0
  127. sf_veritas.libs/libk5crypto-9a74ff38.so.3.1 +0 -0
  128. sf_veritas.libs/libkeyutils-2777d33d.so.1.6 +0 -0
  129. sf_veritas.libs/libkrb5-a55300e8.so.3.3 +0 -0
  130. sf_veritas.libs/libkrb5support-e6594cfc.so.0.1 +0 -0
  131. sf_veritas.libs/liblber-2-d20824ef.4.so.2.10.9 +0 -0
  132. sf_veritas.libs/libldap-2-cea2a960.4.so.2.10.9 +0 -0
  133. sf_veritas.libs/libnghttp2-39367a22.so.14.17.0 +0 -0
  134. sf_veritas.libs/libpcre2-8-516f4c9d.so.0.7.1 +0 -0
  135. sf_veritas.libs/libpsl-99becdd3.so.5.3.1 +0 -0
  136. sf_veritas.libs/libsasl2-7de4d792.so.3.0.0 +0 -0
  137. sf_veritas.libs/libselinux-d0805dcb.so.1 +0 -0
  138. sf_veritas.libs/libssh-c11d285b.so.4.8.7 +0 -0
  139. sf_veritas.libs/libssl-60250281.so.1.1.1k +0 -0
  140. sf_veritas.libs/libunistring-05abdd40.so.2.1.0 +0 -0
  141. 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
+ import os
9
+ import threading
10
+ from typing import List, Optional
11
+
12
+ from ... import _sffuncspan, _sffuncspan_config, app_config
13
+ from ...constants import SAILFISH_TRACING_HEADER
14
+ from ...custom_excepthook import custom_excepthook
15
+ from ...env_vars import (
16
+ SF_DEBUG,
17
+ SF_NETWORKHOP_CAPTURE_REQUEST_BODY,
18
+ SF_NETWORKHOP_CAPTURE_REQUEST_HEADERS,
19
+ SF_NETWORKHOP_CAPTURE_RESPONSE_BODY,
20
+ SF_NETWORKHOP_CAPTURE_RESPONSE_HEADERS,
21
+ SF_NETWORKHOP_REQUEST_LIMIT_MB,
22
+ SF_NETWORKHOP_RESPONSE_LIMIT_MB,
23
+ )
24
+ from ...fast_network_hop import fast_send_network_hop_fast, register_endpoint
25
+ from ...thread_local import clear_funcspan_override, get_or_set_sf_trace_id, set_funcspan_override
26
+ from .utils import _is_user_code, _unwrap_user_func, should_skip_route, reinitialize_log_print_capture_for_worker
27
+
28
+ # Size limits in bytes
29
+ _REQUEST_LIMIT_BYTES = SF_NETWORKHOP_REQUEST_LIMIT_MB * 1024 * 1024
30
+ _RESPONSE_LIMIT_BYTES = SF_NETWORKHOP_RESPONSE_LIMIT_MB * 1024 * 1024
31
+
32
+ # Pre-registered endpoint IDs
33
+ _ENDPOINT_REGISTRY: dict[tuple, int] = {}
34
+
35
+ # Routes to skip (set by patch_klein)
36
+ _ROUTES_TO_SKIP = []
37
+
38
+
39
+ def patch_klein(routes_to_skip: Optional[List[str]] = None):
40
+ """
41
+ OTEL-STYLE Klein patch:
42
+ • Pre-registers endpoints on first route decoration
43
+ • Captures request headers/body if enabled
44
+ • Emits network hop AFTER handler completes (zero-overhead)
45
+ • Captures response headers/body if enabled
46
+ • Funnels exceptions through custom_excepthook
47
+ No-op if Klein isn't installed.
48
+ """
49
+ global _ROUTES_TO_SKIP
50
+ _ROUTES_TO_SKIP = routes_to_skip or []
51
+
52
+ try:
53
+ import klein
54
+ except ImportError:
55
+ return
56
+
57
+ # Note: Profiler is already installed by unified_interceptor.py
58
+
59
+ original_route = klein.Klein.route
60
+
61
+ def patched_route(self, *args, **kwargs):
62
+ # Grab Klein's decorator for this pattern
63
+ original_decorator = original_route(self, *args, **kwargs)
64
+
65
+ def new_decorator(fn):
66
+ real_fn = _unwrap_user_func(fn)
67
+
68
+ # Skip non-user code
69
+ code = getattr(real_fn, "__code__", None)
70
+ if not code or not _is_user_code(code.co_filename):
71
+ return original_decorator(fn)
72
+
73
+ filename = code.co_filename
74
+ line_no = code.co_firstlineno
75
+ fn_name = real_fn.__name__
76
+ hop_key = (filename, line_no)
77
+
78
+ # Get route pattern from args (first arg after self is usually the path)
79
+ route_pattern = args[0] if args else None
80
+
81
+ # Check if route should be skipped
82
+ if route_pattern and should_skip_route(route_pattern, _ROUTES_TO_SKIP):
83
+ if SF_DEBUG and app_config._interceptors_initialized:
84
+ print(
85
+ f"[[Klein]] Skipping endpoint (route matches skip pattern): {route_pattern}",
86
+ log=False,
87
+ )
88
+ return original_decorator(fn)
89
+
90
+ # Pre-register endpoint
91
+ endpoint_id = _ENDPOINT_REGISTRY.get(hop_key)
92
+ if endpoint_id is None:
93
+ endpoint_id = register_endpoint(
94
+ line=str(line_no),
95
+ column="0",
96
+ name=fn_name,
97
+ entrypoint=filename,
98
+ route=route_pattern,
99
+ )
100
+ if endpoint_id >= 0:
101
+ _ENDPOINT_REGISTRY[hop_key] = endpoint_id
102
+ if SF_DEBUG and app_config._interceptors_initialized:
103
+ print(
104
+ f"[[Klein]] Registered endpoint: {fn_name} @ {filename}:{line_no} (id={endpoint_id})",
105
+ log=False,
106
+ )
107
+
108
+ # Check if async or sync
109
+ is_async = inspect.iscoroutinefunction(real_fn)
110
+
111
+ if is_async:
112
+
113
+ @functools.wraps(fn)
114
+ async def wrapped_async(request, *f_args, **f_kwargs):
115
+ # 1. Trace-id propagation
116
+ header = request.getHeader(SAILFISH_TRACING_HEADER)
117
+ if header:
118
+ get_or_set_sf_trace_id(
119
+ header, is_associated_with_inbound_request=True
120
+ )
121
+
122
+ # Check for function span capture override header (highest priority!)
123
+ funcspan_override_header = request.getHeader(
124
+ "X-Sf3-FunctionSpanCaptureOverride"
125
+ )
126
+ if funcspan_override_header:
127
+ try:
128
+ set_funcspan_override(funcspan_override_header)
129
+ if SF_DEBUG and app_config._interceptors_initialized:
130
+ print(
131
+ f"[[Klein.async]] Set function span override from header: {funcspan_override_header}",
132
+ log=False,
133
+ )
134
+ except Exception as e:
135
+ if SF_DEBUG and app_config._interceptors_initialized:
136
+ print(
137
+ f"[[Klein.async]] Failed to set function span override: {e}",
138
+ log=False,
139
+ )
140
+
141
+ # 2. Capture request headers if enabled
142
+ req_headers = None
143
+ if SF_NETWORKHOP_CAPTURE_REQUEST_HEADERS:
144
+ try:
145
+ # Klein/Twisted request.requestHeaders is a Headers object
146
+ req_headers = dict(
147
+ request.requestHeaders.getAllRawHeaders()
148
+ )
149
+ if SF_DEBUG and app_config._interceptors_initialized:
150
+ print(
151
+ f"[[Klein]] Captured request headers: {len(req_headers)} headers",
152
+ log=False,
153
+ )
154
+ except Exception as e:
155
+ if SF_DEBUG and app_config._interceptors_initialized:
156
+ print(
157
+ f"[[Klein]] Failed to capture request headers: {e}",
158
+ log=False,
159
+ )
160
+
161
+ # 3. Capture request body if enabled
162
+ req_body = None
163
+ if SF_NETWORKHOP_CAPTURE_REQUEST_BODY:
164
+ try:
165
+ # Klein/Twisted request.content is a file-like object
166
+ body = request.content.read(_REQUEST_LIMIT_BYTES)
167
+ request.content.seek(0) # Reset for handler
168
+ req_body = body if body else None
169
+ if SF_DEBUG and app_config._interceptors_initialized:
170
+ print(
171
+ f"[[Klein]] Request body capture: {len(body) if body else 0} bytes (method={request.method})",
172
+ log=False,
173
+ )
174
+ except Exception as e:
175
+ if SF_DEBUG and app_config._interceptors_initialized:
176
+ print(
177
+ f"[[Klein]] Failed to capture request body: {e}",
178
+ log=False,
179
+ )
180
+
181
+ if SF_DEBUG and app_config._interceptors_initialized:
182
+ print(
183
+ f"[[Klein]] Captured endpoint: {fn_name} ({filename}:{line_no}) endpoint_id={endpoint_id}",
184
+ log=False,
185
+ )
186
+
187
+ # 4. Call handler and capture exceptions
188
+ response = None
189
+ try:
190
+ response = await fn(request, *f_args, **f_kwargs)
191
+ except Exception as exc:
192
+ custom_excepthook(type(exc), exc, exc.__traceback__)
193
+ raise
194
+
195
+ # 5. Capture response headers if enabled
196
+ resp_headers = None
197
+ if SF_NETWORKHOP_CAPTURE_RESPONSE_HEADERS:
198
+ try:
199
+ # Klein/Twisted request.responseHeaders is a Headers object
200
+ resp_headers = dict(
201
+ request.responseHeaders.getAllRawHeaders()
202
+ )
203
+ if SF_DEBUG and app_config._interceptors_initialized:
204
+ print(
205
+ f"[[Klein]] Captured response headers: {len(resp_headers)} headers",
206
+ log=False,
207
+ )
208
+ except Exception as e:
209
+ if SF_DEBUG and app_config._interceptors_initialized:
210
+ print(
211
+ f"[[Klein]] Failed to capture response headers: {e}",
212
+ log=False,
213
+ )
214
+
215
+ # 6. Capture response body if enabled
216
+ resp_body = None
217
+ if SF_NETWORKHOP_CAPTURE_RESPONSE_BODY:
218
+ try:
219
+ # Klein handlers return bytes directly
220
+ if isinstance(response, bytes):
221
+ resp_body = response[:_RESPONSE_LIMIT_BYTES]
222
+ if SF_DEBUG and app_config._interceptors_initialized:
223
+ print(
224
+ f"[[Klein]] Captured response body: {len(resp_body)} bytes",
225
+ log=False,
226
+ )
227
+ elif isinstance(response, str):
228
+ resp_body = response.encode("utf-8")[
229
+ :_RESPONSE_LIMIT_BYTES
230
+ ]
231
+ if SF_DEBUG and app_config._interceptors_initialized:
232
+ print(
233
+ f"[[Klein]] Captured response body (str): {len(resp_body)} bytes",
234
+ log=False,
235
+ )
236
+ except Exception as e:
237
+ if SF_DEBUG and app_config._interceptors_initialized:
238
+ print(
239
+ f"[[Klein]] Failed to capture response body: {e}",
240
+ log=False,
241
+ )
242
+
243
+ # 7. OTEL-STYLE: Emit network hop AFTER handler completes
244
+ if endpoint_id is not None and endpoint_id >= 0:
245
+ try:
246
+ _, session_id = get_or_set_sf_trace_id()
247
+
248
+ # Extract raw path and query string for C to parse
249
+ raw_path = (
250
+ request.path
251
+ if isinstance(request.path, str)
252
+ else request.path.decode("utf-8")
253
+ )
254
+ # Twisted request.uri includes full URI with query string (bytes)
255
+ uri_parts = request.uri.split(b"?", 1)
256
+ raw_query = uri_parts[1] if len(uri_parts) > 1 else b""
257
+
258
+ if SF_DEBUG and app_config._interceptors_initialized:
259
+ print(
260
+ f"[[Klein]] About to emit network hop: endpoint_id={endpoint_id}, "
261
+ f"req_headers={'present' if req_headers else 'None'}, "
262
+ f"req_body={len(req_body) if req_body else 0} bytes, "
263
+ f"resp_headers={'present' if resp_headers else 'None'}, "
264
+ f"resp_body={len(resp_body) if resp_body else 0} bytes",
265
+ log=False,
266
+ )
267
+
268
+ # Direct C call - queues to background worker, returns instantly
269
+ # C will parse route and query_params from raw data
270
+ fast_send_network_hop_fast(
271
+ session_id=session_id,
272
+ endpoint_id=endpoint_id,
273
+ raw_path=raw_path,
274
+ raw_query_string=raw_query,
275
+ request_headers=req_headers,
276
+ request_body=req_body,
277
+ response_headers=resp_headers,
278
+ response_body=resp_body,
279
+ )
280
+
281
+ if SF_DEBUG and app_config._interceptors_initialized:
282
+ print(
283
+ f"[[Klein]] Emitted network hop: endpoint_id={endpoint_id} "
284
+ f"session={session_id}",
285
+ log=False,
286
+ )
287
+ except Exception as e: # noqa: BLE001 S110
288
+ if SF_DEBUG and app_config._interceptors_initialized:
289
+ print(
290
+ f"[[Klein]] Failed to emit network hop: {e}",
291
+ log=False,
292
+ )
293
+
294
+ # Clear function span override for this request (ContextVar cleanup - also syncs C thread-local)
295
+ try:
296
+ clear_funcspan_override()
297
+ except Exception:
298
+ pass
299
+
300
+ return response
301
+
302
+ return original_decorator(wrapped_async)
303
+ else:
304
+
305
+ @functools.wraps(fn)
306
+ def wrapped_sync(request, *f_args, **f_kwargs):
307
+ # 1. Trace-id propagation
308
+ header = request.getHeader(SAILFISH_TRACING_HEADER)
309
+ if header:
310
+ get_or_set_sf_trace_id(
311
+ header, is_associated_with_inbound_request=True
312
+ )
313
+
314
+ # Check for function span capture override header (highest priority!)
315
+ funcspan_override_header = request.getHeader(
316
+ "X-Sf3-FunctionSpanCaptureOverride"
317
+ )
318
+ if funcspan_override_header:
319
+ try:
320
+ set_funcspan_override(funcspan_override_header)
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 (ContextVar cleanup - also syncs C thread-local)
487
+ try:
488
+ clear_funcspan_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)