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.

Files changed (132) hide show
  1. sf_veritas/__init__.py +20 -0
  2. sf_veritas/_sffastlog.c +889 -0
  3. sf_veritas/_sffastlog.cpython-39-x86_64-linux-gnu.so +0 -0
  4. sf_veritas/_sffastnet.c +924 -0
  5. sf_veritas/_sffastnet.cpython-39-x86_64-linux-gnu.so +0 -0
  6. sf_veritas/_sffastnetworkrequest.c +730 -0
  7. sf_veritas/_sffastnetworkrequest.cpython-39-x86_64-linux-gnu.so +0 -0
  8. sf_veritas/_sffuncspan.c +2155 -0
  9. sf_veritas/_sffuncspan.cpython-39-x86_64-linux-gnu.so +0 -0
  10. sf_veritas/_sffuncspan_config.c +617 -0
  11. sf_veritas/_sffuncspan_config.cpython-39-x86_64-linux-gnu.so +0 -0
  12. sf_veritas/_sfheadercheck.c +341 -0
  13. sf_veritas/_sfheadercheck.cpython-39-x86_64-linux-gnu.so +0 -0
  14. sf_veritas/_sfnetworkhop.c +1451 -0
  15. sf_veritas/_sfnetworkhop.cpython-39-x86_64-linux-gnu.so +0 -0
  16. sf_veritas/_sfservice.c +1175 -0
  17. sf_veritas/_sfservice.cpython-39-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,481 @@
1
+ import inspect
2
+ import site
3
+ import sysconfig
4
+ from functools import lru_cache
5
+ from typing import Any, Callable, List, Optional, Set, Tuple
6
+
7
+ from ... import app_config
8
+ from ...constants import (
9
+ FUNCSPAN_OVERRIDE_HEADER_BYTES,
10
+ SAILFISH_TRACING_HEADER,
11
+ SAILFISH_TRACING_HEADER_BYTES,
12
+ )
13
+ from ...custom_excepthook import custom_excepthook
14
+ from ...env_vars import (
15
+ SF_DEBUG,
16
+ SF_NETWORKHOP_CAPTURE_REQUEST_BODY,
17
+ SF_NETWORKHOP_CAPTURE_REQUEST_HEADERS,
18
+ SF_NETWORKHOP_CAPTURE_RESPONSE_BODY,
19
+ SF_NETWORKHOP_CAPTURE_RESPONSE_HEADERS,
20
+ SF_NETWORKHOP_REQUEST_LIMIT_MB,
21
+ SF_NETWORKHOP_RESPONSE_LIMIT_MB,
22
+ )
23
+ from ...fast_network_hop import fast_send_network_hop_fast, register_endpoint
24
+ from ...thread_local import (
25
+ clear_c_tls_parent_trace_id,
26
+ clear_current_request_path,
27
+ clear_funcspan_override,
28
+ clear_outbound_header_base,
29
+ clear_trace_id,
30
+ generate_new_trace_id,
31
+ get_or_set_sf_trace_id,
32
+ get_sf_trace_id,
33
+ set_current_request_path,
34
+ set_funcspan_override,
35
+ set_outbound_header_base,
36
+ )
37
+ from .cors_utils import inject_sailfish_headers, should_inject_headers
38
+ from .utils import _is_user_code, _unwrap_user_func, should_skip_route
39
+
40
+ # Size limits in bytes
41
+ _REQUEST_LIMIT_BYTES = SF_NETWORKHOP_REQUEST_LIMIT_MB * 1024 * 1024
42
+ _RESPONSE_LIMIT_BYTES = SF_NETWORKHOP_RESPONSE_LIMIT_MB * 1024 * 1024
43
+
44
+ # Pre-registered endpoint IDs
45
+ _ENDPOINT_REGISTRY: dict[tuple, int] = {}
46
+
47
+ # Module-level variable for routes to skip (set by patch_tornado)
48
+ _ROUTES_TO_SKIP = []
49
+
50
+
51
+ def patch_tornado(routes_to_skip: Optional[List[str]] = None):
52
+ """
53
+ Monkey-patch tornado.web.RequestHandler so that every request:
54
+
55
+ 1. Propagates SAILFISH_TRACING_HEADER into the ContextVar.
56
+ 2. Emits ONE NetworkHop when user-land verb handler starts.
57
+ 3. Funnels *all* exceptions—including tornado.web.HTTPError—through
58
+ custom_excepthook before Tornado's own error machinery runs.
59
+
60
+ Safe no-op if Tornado isn't installed.
61
+ """
62
+ global _ROUTES_TO_SKIP
63
+ _ROUTES_TO_SKIP = routes_to_skip or []
64
+ try:
65
+ import tornado.web
66
+ except ImportError: # Tornado not installed
67
+ return
68
+
69
+ # --------------------------------------------------------------- #
70
+ # a) Header capture + endpoint metadata (prepare)
71
+ # --------------------------------------------------------------- #
72
+ original_prepare = tornado.web.RequestHandler.prepare
73
+
74
+ def patched_prepare(self, *args, **kwargs):
75
+ # Set current request path for route-based suppression (SF_DISABLE_INBOUND_NETWORK_TRACING_ON_ROUTES)
76
+ set_current_request_path(self.request.path)
77
+
78
+ # -- 1) PERFORMANCE: Single-pass bytes-level header scan (similar to FastAPI optimization)
79
+ # Tornado stores headers as HTTPHeaders object, iterate once to extract needed headers
80
+ incoming_trace_header = None
81
+ funcspan_override_header = None
82
+
83
+ # Scan headers once
84
+ for name, value in self.request.headers.get_all():
85
+ name_lower = name.lower()
86
+ if name_lower == SAILFISH_TRACING_HEADER.lower():
87
+ incoming_trace_header = value
88
+ elif name_lower == "x-sf3-functionspancaptureoverride":
89
+ funcspan_override_header = value
90
+
91
+ # -- 2) CRITICAL: Seed/ensure trace_id immediately (BEFORE any outbound work)
92
+ if incoming_trace_header:
93
+ # Incoming X-Sf3-Rid header provided - use it
94
+ get_or_set_sf_trace_id(
95
+ incoming_trace_header, is_associated_with_inbound_request=True
96
+ )
97
+ else:
98
+ # No incoming X-Sf3-Rid header - generate fresh trace_id for this request
99
+ generate_new_trace_id()
100
+
101
+ # -- 3) Set function span override if provided
102
+ if funcspan_override_header:
103
+ try:
104
+ set_funcspan_override(funcspan_override_header)
105
+ if SF_DEBUG and app_config._interceptors_initialized:
106
+ print(
107
+ f"[[Tornado.prepare]] Set function span override from header: {funcspan_override_header}",
108
+ log=False,
109
+ )
110
+ except Exception as e:
111
+ if SF_DEBUG and app_config._interceptors_initialized:
112
+ print(
113
+ f"[[Tornado.prepare]] Failed to set function span override: {e}",
114
+ log=False,
115
+ )
116
+
117
+ # -- 4) Initialize outbound base without list/allocs from split()
118
+ try:
119
+ trace_id = get_sf_trace_id()
120
+ if trace_id:
121
+ s = str(trace_id)
122
+ i = s.find("/") # session
123
+ j = s.find("/", i + 1) if i != -1 else -1 # page
124
+ if j != -1:
125
+ base_trace = s[:j] # "session/page"
126
+ set_outbound_header_base(
127
+ base_trace=base_trace,
128
+ parent_trace_id=s, # "session/page/uuid"
129
+ funcspan=funcspan_override_header,
130
+ )
131
+ if SF_DEBUG and app_config._interceptors_initialized:
132
+ print(
133
+ f"[[Tornado.prepare]] Initialized outbound header base (base={base_trace[:16]}...)",
134
+ log=False,
135
+ )
136
+ except Exception as e:
137
+ if SF_DEBUG and app_config._interceptors_initialized:
138
+ print(
139
+ f"[[Tornado.prepare]] Failed to initialize outbound header base: {e}",
140
+ log=False,
141
+ )
142
+
143
+ # -- 5) Capture request headers if enabled
144
+ if SF_NETWORKHOP_CAPTURE_REQUEST_HEADERS:
145
+ try:
146
+ req_headers = dict(self.request.headers)
147
+ self._sf_request_headers = req_headers
148
+ if SF_DEBUG and app_config._interceptors_initialized:
149
+ print(
150
+ f"[[Tornado]] Captured request headers: {len(req_headers)} headers",
151
+ log=False,
152
+ )
153
+ except Exception as e:
154
+ if SF_DEBUG and app_config._interceptors_initialized:
155
+ print(
156
+ f"[[Tornado]] Failed to capture request headers: {e}", log=False
157
+ )
158
+
159
+ # -- 6) Capture request body if enabled
160
+ if SF_NETWORKHOP_CAPTURE_REQUEST_BODY:
161
+ try:
162
+ body = self.request.body
163
+ if body:
164
+ req_body = body[:_REQUEST_LIMIT_BYTES]
165
+ self._sf_request_body = req_body
166
+ if SF_DEBUG and app_config._interceptors_initialized:
167
+ print(
168
+ f"[[Tornado]] Request body capture: {len(req_body)} bytes",
169
+ log=False,
170
+ )
171
+ except Exception as e:
172
+ if SF_DEBUG and app_config._interceptors_initialized:
173
+ print(f"[[Tornado]] Failed to capture request body: {e}", log=False)
174
+
175
+ # -- 7) OTEL-STYLE: Pre-register endpoint and store endpoint_id
176
+ method_name = self.request.method.lower()
177
+ handler_fn = getattr(self, method_name, None)
178
+
179
+ if callable(handler_fn):
180
+ module = getattr(handler_fn, "__module__", "")
181
+ if not module.startswith("strawberry"):
182
+ real_fn = _unwrap_user_func(handler_fn)
183
+ code_obj = getattr(real_fn, "__code__", None)
184
+ if code_obj and _is_user_code(code_obj.co_filename):
185
+ hop_key = (code_obj.co_filename, code_obj.co_firstlineno)
186
+
187
+ # Extract route pattern from the handler's route_spec
188
+ route_pattern = None
189
+ try:
190
+ # Tornado stores the route pattern in the request's path_kwargs
191
+ # We can get the pattern from the application's handlers
192
+ if hasattr(self, "application") and hasattr(
193
+ self.application, "handlers"
194
+ ):
195
+ # Find the matching route spec
196
+ for host_pattern, handlers in self.application.handlers:
197
+ for spec in handlers:
198
+ # spec is a URLSpec with regex, handler_class, kwargs, name
199
+ if spec.handler_class == type(self):
200
+ route_pattern = spec.regex.pattern
201
+ break
202
+ if route_pattern:
203
+ break
204
+ except Exception:
205
+ pass
206
+
207
+ # Check if route should be skipped
208
+ if should_skip_route(
209
+ route_pattern or self.request.path, _ROUTES_TO_SKIP
210
+ ):
211
+ if SF_DEBUG and app_config._interceptors_initialized:
212
+ print(
213
+ f"[[Tornado]] Skipping endpoint (route matches skip pattern): {route_pattern or self.request.path}",
214
+ log=False,
215
+ )
216
+ return original_prepare(self, *args, **kwargs)
217
+
218
+ # Pre-register endpoint if not already registered
219
+ endpoint_id = _ENDPOINT_REGISTRY.get(hop_key)
220
+ if endpoint_id is None:
221
+ endpoint_id = register_endpoint(
222
+ line=str(code_obj.co_firstlineno),
223
+ column="0",
224
+ name=real_fn.__name__,
225
+ entrypoint=code_obj.co_filename,
226
+ route=route_pattern,
227
+ )
228
+ if endpoint_id >= 0:
229
+ _ENDPOINT_REGISTRY[hop_key] = endpoint_id
230
+ if SF_DEBUG and app_config._interceptors_initialized:
231
+ print(
232
+ f"[[Tornado]] Registered endpoint: {real_fn.__name__} @ "
233
+ f"{code_obj.co_filename}:{code_obj.co_firstlineno} (id={endpoint_id})",
234
+ log=False,
235
+ )
236
+
237
+ # Store endpoint_id for on_finish() to emit
238
+ self._sf_endpoint_id = endpoint_id
239
+
240
+ if SF_DEBUG and app_config._interceptors_initialized:
241
+ print(
242
+ f"[[Tornado]] Captured endpoint: {real_fn.__name__} "
243
+ f"({code_obj.co_filename}:{code_obj.co_firstlineno}) endpoint_id={endpoint_id}",
244
+ log=False,
245
+ )
246
+
247
+ return original_prepare(self, *args, **kwargs)
248
+
249
+ tornado.web.RequestHandler.prepare = patched_prepare
250
+
251
+ # --------------------------------------------------------------- #
252
+ # b) Exception capture – patch _execute and write_error
253
+ # --------------------------------------------------------------- #
254
+ original_execute = tornado.web.RequestHandler._execute
255
+ original_write_error = tornado.web.RequestHandler.write_error
256
+
257
+ async def patched_execute(self, *args, **kwargs):
258
+ try:
259
+ return await original_execute(self, *args, **kwargs)
260
+ except Exception as exc: # HTTPError included
261
+ custom_excepthook(type(exc), exc, exc.__traceback__)
262
+ raise # let Tornado handle 500/4xx
263
+
264
+ def patched_write_error(self, status_code, **kwargs):
265
+ """
266
+ Tornado calls write_error for HTTPError and uncaught exceptions.
267
+ Capture the exception (when provided) before rendering.
268
+ """
269
+ exc_info = kwargs.get("exc_info")
270
+ if exc_info and isinstance(exc_info, tuple) and exc_info[1]:
271
+ exc_type, exc_val, exc_tb = exc_info
272
+ custom_excepthook(exc_type, exc_val, exc_tb)
273
+ # Fallback: still call original renderer
274
+ return original_write_error(self, status_code, **kwargs)
275
+
276
+ tornado.web.RequestHandler._execute = patched_execute
277
+ tornado.web.RequestHandler.write_error = patched_write_error
278
+
279
+ # --------------------------------------------------------------- #
280
+ # c) CORS patching – inject Sailfish headers
281
+ # --------------------------------------------------------------- #
282
+ patch_tornado_cors()
283
+
284
+
285
+ def patch_tornado_cors():
286
+ """
287
+ Patch Tornado's RequestHandler to automatically inject Sailfish headers into CORS.
288
+
289
+ SAFE: Only modifies Access-Control-Allow-Headers if the handler sets it.
290
+ Tornado doesn't have a standard CORS library, so we patch the common patterns:
291
+ 1. set_default_headers() - called for every request
292
+ 2. options() - called for preflight requests
293
+ """
294
+ try:
295
+ import tornado.web
296
+ except ImportError:
297
+ if SF_DEBUG and app_config._interceptors_initialized:
298
+ print("[[patch_tornado_cors]] Tornado not available, skipping", log=False)
299
+ return
300
+
301
+ # Check if already patched
302
+ if hasattr(tornado.web.RequestHandler, "_sf_cors_patched"):
303
+ if SF_DEBUG and app_config._interceptors_initialized:
304
+ print("[[patch_tornado_cors]] Already patched, skipping", log=False)
305
+ return
306
+
307
+ # Patch set_default_headers to intercept and modify Access-Control-Allow-Headers
308
+ original_set_header = tornado.web.RequestHandler.set_header
309
+
310
+ def patched_set_header(self, name, value):
311
+ # Intercept Access-Control-Allow-Headers header
312
+ if name.lower() == "access-control-allow-headers":
313
+ if should_inject_headers(value):
314
+ injected = inject_sailfish_headers(value)
315
+ # Convert list back to comma-separated string for Tornado
316
+ # (inject_sailfish_headers returns a list, but Tornado expects a string)
317
+ if isinstance(injected, list):
318
+ value = ", ".join(injected)
319
+ else:
320
+ value = injected
321
+ if SF_DEBUG and app_config._interceptors_initialized:
322
+ print(
323
+ f"[[patch_tornado_cors]] Injected Sailfish headers: {value}",
324
+ log=False,
325
+ )
326
+
327
+ # Call original set_header
328
+ return original_set_header(self, name, value)
329
+
330
+ tornado.web.RequestHandler.set_header = patched_set_header
331
+ tornado.web.RequestHandler._sf_cors_patched = True
332
+
333
+ if SF_DEBUG and app_config._interceptors_initialized:
334
+ print(
335
+ "[[patch_tornado_cors]] Successfully patched Tornado RequestHandler.set_header",
336
+ log=False,
337
+ )
338
+
339
+
340
+ # Hook into on_finish for OTEL-style post-response emission
341
+ try:
342
+ import tornado.web
343
+
344
+ original_on_finish = tornado.web.RequestHandler.on_finish
345
+
346
+ def patched_on_finish(self):
347
+ """
348
+ OTEL-STYLE: Emit network hop AFTER response is sent.
349
+ Tornado calls on_finish() after the response is fully sent to client.
350
+ """
351
+ # Emit network hop if we captured endpoint_id
352
+ endpoint_id = getattr(self, "_sf_endpoint_id", None)
353
+ if endpoint_id is not None and endpoint_id >= 0:
354
+ try:
355
+ _, session_id = get_or_set_sf_trace_id()
356
+
357
+ # Get captured request data
358
+ req_headers = getattr(self, "_sf_request_headers", None)
359
+ req_body = getattr(self, "_sf_request_body", None)
360
+
361
+ # Capture response headers if enabled
362
+ resp_headers = None
363
+ if SF_NETWORKHOP_CAPTURE_RESPONSE_HEADERS:
364
+ try:
365
+ resp_headers = dict(self._headers)
366
+ if SF_DEBUG and app_config._interceptors_initialized:
367
+ print(
368
+ f"[[Tornado]] Captured response headers: {len(resp_headers)} headers",
369
+ log=False,
370
+ )
371
+ except Exception as e:
372
+ if SF_DEBUG and app_config._interceptors_initialized:
373
+ print(
374
+ f"[[Tornado]] Failed to capture response headers: {e}",
375
+ log=False,
376
+ )
377
+
378
+ # Capture response body if enabled
379
+ resp_body = None
380
+ if SF_NETWORKHOP_CAPTURE_RESPONSE_BODY:
381
+ try:
382
+ # Tornado stores written chunks in self._write_buffer
383
+ if hasattr(self, "_write_buffer") and self._write_buffer:
384
+ body_parts = [
385
+ chunk
386
+ for chunk in self._write_buffer
387
+ if isinstance(chunk, bytes)
388
+ ]
389
+ if body_parts:
390
+ full_body = b"".join(body_parts)
391
+ resp_body = full_body[:_RESPONSE_LIMIT_BYTES]
392
+ if SF_DEBUG and app_config._interceptors_initialized:
393
+ print(
394
+ f"[[Tornado]] Captured response body: {len(resp_body)} bytes",
395
+ log=False,
396
+ )
397
+ except Exception as e:
398
+ if SF_DEBUG and app_config._interceptors_initialized:
399
+ print(
400
+ f"[[Tornado]] Failed to capture response body: {e}",
401
+ log=False,
402
+ )
403
+
404
+ # Extract raw path and query string for C to parse
405
+ raw_path = self.request.path # e.g., "/log"
406
+ # Tornado's request.query is the query string without '?'
407
+ raw_query = (
408
+ self.request.query.encode("utf-8") if self.request.query else b""
409
+ ) # e.g., b"foo=5"
410
+
411
+ if SF_DEBUG and app_config._interceptors_initialized:
412
+ print(
413
+ f"[[Tornado]] About to emit network hop: endpoint_id={endpoint_id}, "
414
+ f"req_headers={'present' if req_headers else 'None'}, "
415
+ f"req_body={len(req_body) if req_body else 0} bytes, "
416
+ f"resp_headers={'present' if resp_headers else 'None'}, "
417
+ f"resp_body={len(resp_body) if resp_body else 0} bytes",
418
+ log=False,
419
+ )
420
+
421
+ # Direct C call - queues to background worker, returns instantly
422
+ # C will parse route and query_params from raw data
423
+ fast_send_network_hop_fast(
424
+ session_id=session_id,
425
+ endpoint_id=endpoint_id,
426
+ raw_path=raw_path,
427
+ raw_query_string=raw_query,
428
+ request_headers=req_headers,
429
+ request_body=req_body,
430
+ response_headers=resp_headers,
431
+ response_body=resp_body,
432
+ )
433
+
434
+ if SF_DEBUG and app_config._interceptors_initialized:
435
+ print(
436
+ f"[[Tornado]] Emitted network hop: endpoint_id={endpoint_id} "
437
+ f"session={session_id}",
438
+ log=False,
439
+ )
440
+ except Exception as e: # noqa: BLE001 S110
441
+ if SF_DEBUG and app_config._interceptors_initialized:
442
+ print(f"[[Tornado]] Failed to emit network hop: {e}", log=False)
443
+
444
+ # Clear function span override for this request (ContextVar cleanup)
445
+ try:
446
+ clear_funcspan_override()
447
+ except Exception:
448
+ pass
449
+
450
+ # CRITICAL: Clear C TLS to prevent stale data in thread pools
451
+ try:
452
+ clear_c_tls_parent_trace_id()
453
+ except Exception:
454
+ pass
455
+
456
+ # CRITICAL: Clear outbound header base to prevent stale cached headers
457
+ # ContextVar does NOT automatically clean up in thread pools - must clear explicitly
458
+ try:
459
+ clear_outbound_header_base()
460
+ except Exception:
461
+ pass
462
+
463
+ # CRITICAL: Clear trace_id to ensure fresh generation for next request
464
+ # Without this, get_or_set_sf_trace_id() reuses trace_id from previous request
465
+ # causing X-Sf4-Prid to stay constant when no incoming X-Sf3-Rid header
466
+ try:
467
+ clear_trace_id()
468
+ except Exception:
469
+ pass
470
+
471
+ # CRITICAL: Clear current request path to prevent stale data in thread pools
472
+ try:
473
+ clear_current_request_path()
474
+ except Exception:
475
+ pass
476
+
477
+ return original_on_finish(self)
478
+
479
+ tornado.web.RequestHandler.on_finish = patched_on_finish
480
+ except ImportError:
481
+ pass
@@ -0,0 +1,91 @@
1
+ import fnmatch
2
+ import inspect
3
+ import sysconfig
4
+ from typing import Any, Callable, List, Optional, Set
5
+
6
+ _stdlib = sysconfig.get_paths()["stdlib"]
7
+
8
+
9
+ _ATTR_CANDIDATES = (
10
+ "resolver",
11
+ "func",
12
+ "python_func",
13
+ "_resolver",
14
+ "wrapped_func",
15
+ "__func",
16
+ )
17
+
18
+
19
+ def _is_user_code(path: Optional[str] = None) -> bool:
20
+ return (
21
+ bool(path)
22
+ and not path.startswith(_stdlib)
23
+ and "site-packages" not in path
24
+ and "dist-packages" not in path
25
+ and not path.startswith("<")
26
+ )
27
+
28
+
29
+ def _unwrap_user_func(fn: Callable[..., Any]) -> Callable[..., Any]:
30
+ """Unwrap decorators & closures until we find your user function."""
31
+ seen: Set[int] = set()
32
+ queue = [fn]
33
+ while queue:
34
+ current = queue.pop()
35
+ if id(current) in seen:
36
+ continue
37
+ seen.add(id(current))
38
+
39
+ if inspect.isfunction(current) and _is_user_code(current.__code__.co_filename):
40
+ return current
41
+
42
+ inner = getattr(current, "__wrapped__", None)
43
+ if inner:
44
+ queue.append(inner)
45
+
46
+ for attr in _ATTR_CANDIDATES:
47
+ attr_val = getattr(current, attr, None)
48
+ if inspect.isfunction(attr_val):
49
+ queue.append(attr_val)
50
+
51
+ for cell in getattr(current, "__closure__", []) or []:
52
+ cc = cell.cell_contents
53
+ if inspect.isfunction(cc):
54
+ queue.append(cc)
55
+
56
+ return fn # fallback
57
+
58
+
59
+ def should_skip_route(route_pattern: str, routes_to_skip: List[str]) -> bool:
60
+ """
61
+ Check if route should be skipped based on wildcard patterns.
62
+
63
+ Supports Unix shell-style wildcards:
64
+ - Exact match: "/healthz" matches "/healthz"
65
+ - Wildcard *: "/he*" matches "/health", "/healthz", "/healthz/foo"
66
+ - Wildcard ?: "/health?" matches "/healthz" but not "/health"
67
+ - Character sets: "/health[z12]" matches "/healthz", "/health1", "/health2"
68
+
69
+ Examples:
70
+ - "/he*" → matches "/health", "/healthz", "/healthz/foo"
71
+ - "/metrics*" → matches "/metrics", "/metrics/detailed"
72
+ - "/api/internal/*" → matches "/api/internal/status", "/api/internal/debug"
73
+ - "*/admin" → matches "/foo/admin", "/bar/admin"
74
+
75
+ Args:
76
+ route_pattern: Route pattern to check (e.g., "/healthz", "/log/{n}")
77
+ routes_to_skip: List of patterns to skip (can contain wildcards)
78
+
79
+ Returns:
80
+ True if route should be skipped, False otherwise
81
+ """
82
+ if not routes_to_skip or not route_pattern:
83
+ return False
84
+
85
+ for skip_pattern in routes_to_skip:
86
+ # Use fnmatch for Unix shell-style wildcards
87
+ # This supports * (matches anything) and ? (matches single char)
88
+ if fnmatch.fnmatch(route_pattern, skip_pattern):
89
+ return True
90
+
91
+ return False
@@ -0,0 +1,13 @@
1
+ import builtins
2
+
3
+ from .custom_print import SF_DEBUG, custom_print
4
+
5
+
6
+ def override_print():
7
+ if hasattr(builtins, "_original_print"):
8
+ return
9
+ # Save the original print function
10
+ builtins._original_print = builtins.print
11
+
12
+ # Override the built-in print function
13
+ builtins.print = custom_print