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,970 @@
1
+ import builtins
2
+ import ctypes
3
+ import fnmatch
4
+ import functools
5
+ import os
6
+ import threading
7
+ import uuid
8
+ from contextlib import contextmanager
9
+ from contextvars import ContextVar
10
+ from typing import Any, Dict, List, Optional, Set, Tuple, Union
11
+ from uuid import UUID
12
+
13
+ from . import app_config
14
+ from .constants import (
15
+ FUNCSPAN_OVERRIDE_HEADER,
16
+ NONSESSION_APPLOGS,
17
+ PARENT_SESSION_ID_HEADER,
18
+ SAILFISH_TRACING_HEADER,
19
+ )
20
+ from .env_vars import SF_DEBUG
21
+
22
+ # Check if LD_PRELOAD is active (cached for performance)
23
+ _ld_preload_active: Optional[bool] = None
24
+
25
+ # Cache SF_DEBUG flag at module load to avoid repeated checks in hot paths
26
+ _SF_DEBUG_ENABLED = False
27
+
28
+
29
+ def is_ld_preload_active() -> bool:
30
+ """
31
+ Check if LD_PRELOAD with _sfteepreload is active.
32
+
33
+ When LD_PRELOAD is active, the C extension handles UUID generation
34
+ and appending to X-Sf3-Rid headers (much faster than Python).
35
+
36
+ Returns True if:
37
+ - LD_PRELOAD env var contains 'libsfnettee.so'
38
+ - OR SF_TEEPRELOAD_ACTIVE env var is set to '1'
39
+
40
+ This is cached on first call for performance.
41
+ """
42
+ global _ld_preload_active, _SF_DEBUG_ENABLED
43
+
44
+ if _ld_preload_active is not None:
45
+ return _ld_preload_active
46
+
47
+ # Check if LD_PRELOAD contains our library
48
+ ld_preload = os.getenv("LD_PRELOAD", "")
49
+ if "libsfnettee.so" in ld_preload:
50
+ _ld_preload_active = True
51
+ _SF_DEBUG_ENABLED = SF_DEBUG and app_config._interceptors_initialized
52
+ if _SF_DEBUG_ENABLED:
53
+ print(f"[thread_local] LD_PRELOAD active: {ld_preload}", log=False)
54
+ return True
55
+
56
+ # Check explicit activation flag (set by LD_PRELOAD library itself)
57
+ if os.getenv("SF_TEEPRELOAD_ACTIVE") == "1":
58
+ _ld_preload_active = True
59
+ _SF_DEBUG_ENABLED = SF_DEBUG and app_config._interceptors_initialized
60
+ if _SF_DEBUG_ENABLED:
61
+ print("[thread_local] SF_TEEPRELOAD_ACTIVE=1", log=False)
62
+ return True
63
+
64
+ _ld_preload_active = False
65
+ _SF_DEBUG_ENABLED = SF_DEBUG and app_config._interceptors_initialized
66
+ return False
67
+
68
+
69
+ # Eager initialization at module load for C TLS function pointer
70
+ _sf_tls_setter = None
71
+
72
+
73
+ def _init_c_tls_setter():
74
+ """Initialize C TLS setter at module load time to avoid ctypes.CDLL overhead in hot path."""
75
+ global _sf_tls_setter
76
+ try:
77
+ # Use the main process (LD_PRELOAD library is in the global namespace)
78
+ _lib = ctypes.CDLL(None)
79
+ _fn = _lib.sf_set_parent_trace_id_tls
80
+ _fn.argtypes = [ctypes.c_char_p]
81
+ _fn.restype = None
82
+ _sf_tls_setter = _fn
83
+ except Exception:
84
+ _sf_tls_setter = False # don't retry every call
85
+
86
+
87
+ # Initialize at module load time (moves expensive CDLL call out of hot path)
88
+ _init_c_tls_setter()
89
+
90
+
91
+ def _set_c_tls_parent_trace_id(parent: str) -> None:
92
+ """
93
+ Set parent trace ID in C TLS for ultra-fast access by LD_PRELOAD hooks.
94
+
95
+ This avoids Python lookups in the C extension - the C hooks can read
96
+ the parent PRID directly from TLS with a single memory access.
97
+
98
+ CRITICAL: Must call clear_c_tls_parent_trace_id() at end of request to prevent stale data!
99
+
100
+ OPTIMIZED: Disabled when LD_PRELOAD active - C code reads from ContextVar directly (faster).
101
+ C function pointer initialized at module load time (not on first call).
102
+ """
103
+ # PERFORMANCE: Skip TLS call when LD_PRELOAD active - C reads from ContextVar/shared registry
104
+ # This eliminates: string encoding (expensive!), thread-local attribute setting, ctypes overhead
105
+ if not _ld_preload_active and _sf_tls_setter:
106
+ # Keep bytes alive for the request lifetime to keep C pointer valid
107
+ b = parent.encode("ascii", "ignore")
108
+ _cached_outbound_headers_tls._tls_parent_prid_bytes = b # anchor
109
+ _sf_tls_setter(b)
110
+
111
+
112
+ def clear_c_tls_parent_trace_id() -> None:
113
+ """
114
+ Clear parent trace ID from C TLS at end of request.
115
+
116
+ CRITICAL: Prevents stale data when threads are reused (e.g., thread pools).
117
+ Must be called at the end of EVERY request that set the C TLS.
118
+
119
+ OPTIMIZED: Disabled when LD_PRELOAD active - no TLS to clear.
120
+ """
121
+ # PERFORMANCE: Skip when LD_PRELOAD active - nothing was set in TLS
122
+ if not _ld_preload_active and _sf_tls_setter and _sf_tls_setter is not False:
123
+ try:
124
+ # Set to NULL to clear
125
+ _sf_tls_setter(None)
126
+ # Clear the anchored bytes
127
+ if hasattr(_cached_outbound_headers_tls, "_tls_parent_prid_bytes"):
128
+ delattr(_cached_outbound_headers_tls, "_tls_parent_prid_bytes")
129
+ except Exception:
130
+ pass # Ignore errors during cleanup
131
+
132
+
133
+ def clear_outbound_header_base() -> None:
134
+ """
135
+ Clear outbound header base from ContextVar at end of request.
136
+
137
+ CRITICAL: Prevents stale X-Sf4-Prid data from persisting across requests.
138
+ Must be called at the end of EVERY request that set the outbound header base.
139
+
140
+ This ensures fresh header generation for each request with proper isolation.
141
+ """
142
+ try:
143
+ outbound_header_base_ctx.set(None)
144
+ if _SF_DEBUG_ENABLED:
145
+ print(
146
+ "[clear_outbound_header_base] Cleared outbound_header_base_ctx ContextVar",
147
+ log=False,
148
+ )
149
+ except Exception as e:
150
+ # Don't let cleanup errors break the app
151
+ if _SF_DEBUG_ENABLED:
152
+ print(
153
+ f"[clear_outbound_header_base] ⚠️ Error during cleanup: {e}", log=False
154
+ )
155
+
156
+
157
+ def clear_trace_id() -> None:
158
+ """
159
+ Clear trace_id from ContextVar at end of request.
160
+
161
+ CRITICAL: Ensures fresh trace_id generation for requests without incoming X-Sf3-Rid header.
162
+ Must be called at the end of EVERY request that didn't have an incoming trace header.
163
+
164
+ Without this, get_or_set_sf_trace_id() reuses the trace_id from the previous request,
165
+ causing X-Sf4-Prid to remain constant across multiple requests (same parent_trace_id).
166
+ """
167
+ try:
168
+ trace_id_ctx.set(None)
169
+ if _SF_DEBUG_ENABLED:
170
+ print("[clear_trace_id] Cleared trace_id_ctx ContextVar", log=False)
171
+ except Exception as e:
172
+ # Don't let cleanup errors break the app
173
+ if _SF_DEBUG_ENABLED:
174
+ print(f"[clear_trace_id] ⚠️ Error during cleanup: {e}", log=False)
175
+
176
+
177
+ # Define context variables
178
+ trace_id_ctx = ContextVar("trace_id", default=None)
179
+ handled_exceptions_ctx = ContextVar("handled_exceptions", default=set())
180
+ reentrancy_guard_logging_active_ctx = ContextVar(
181
+ "reentrancy_guard_logging_active", default=False
182
+ )
183
+ reentrancy_guard_logging_preactive_ctx = ContextVar(
184
+ "reentrancy_guard_logging_preactive", default=False
185
+ )
186
+ reentrancy_guard_print_active_ctx = ContextVar(
187
+ "reentrancy_guard_print_active", default=False
188
+ )
189
+ reentrancy_guard_print_preactive_ctx = ContextVar(
190
+ "reentrancy_guard_print_preactive", default=False
191
+ )
192
+ reentrancy_guard_exception_active_ctx = ContextVar(
193
+ "reentrancy_guard_exception_active", default=False
194
+ )
195
+ reentrancy_guard_exception_preactive_ctx = ContextVar(
196
+ "reentrancy_guard_exception_preactive", default=False
197
+ )
198
+
199
+ # Suppressors
200
+ suppress_network_recording_ctx = ContextVar("suppress_network_recording", default=False)
201
+ suppress_log_output_ctx = ContextVar("suppress_log_output", default=False)
202
+
203
+ # Current request path for route-based suppression
204
+ current_request_path_ctx = ContextVar("current_request_path", default=None)
205
+
206
+ # Function span capture override (for header propagation)
207
+ funcspan_override_ctx = ContextVar("funcspan_override", default=None)
208
+
209
+ # Outbound header base (for ultra-fast header injection with cross-thread support)
210
+ outbound_header_base_ctx = ContextVar("outbound_header_base", default=None)
211
+
212
+ reentrancy_guard_sys_stdout_active_ctx = ContextVar(
213
+ "reentrancy_guard_sys_stdout_active", default=False
214
+ )
215
+
216
+ # Thread-local storage as a fallback
217
+ _thread_locals = threading.local()
218
+
219
+ _shared_trace_registry = {}
220
+ _shared_trace_registry_lock = threading.RLock()
221
+
222
+ # Shared registry for outbound header base (cross-thread support, same pattern as trace_id)
223
+ _shared_outbound_header_base_registry = {}
224
+ _shared_outbound_header_base_lock = threading.RLock()
225
+
226
+ # ULTRA-FAST: Cached headers dict in thread-local storage (NO LOCK, ~10-20ns access)
227
+ # This is the fully-built headers dict, ready to inject (no dict building overhead)
228
+ _cached_outbound_headers_tls = threading.local()
229
+
230
+
231
+ def _set_shared_trace_id(trace_id: Optional[str]) -> None:
232
+ # PERFORMANCE: In LD_PRELOAD mode, skip lock (ContextVar is primary source)
233
+ if _ld_preload_active:
234
+ _shared_trace_registry["trace_id"] = trace_id
235
+ return
236
+ with _shared_trace_registry_lock:
237
+ _shared_trace_registry["trace_id"] = trace_id
238
+
239
+
240
+ def _set_shared_outbound_header_base(base_dict: Optional[dict]) -> None:
241
+ """Store outbound header base in shared registry (works across threads)."""
242
+ # PERFORMANCE: In LD_PRELOAD mode, skip lock (ContextVar is primary source)
243
+ if _ld_preload_active:
244
+ _shared_outbound_header_base_registry["base_dict"] = base_dict
245
+ return
246
+ with _shared_outbound_header_base_lock:
247
+ _shared_outbound_header_base_registry["base_dict"] = base_dict
248
+ _clear_cached_outbound_headers()
249
+
250
+
251
+ def _get_shared_outbound_header_base() -> Optional[dict]:
252
+ """Get outbound header base from shared registry (works across threads)."""
253
+ # PERFORMANCE: In LD_PRELOAD mode, skip lock (ContextVar is primary source)
254
+ if _ld_preload_active:
255
+ return _shared_outbound_header_base_registry.get("base_dict")
256
+ with _shared_outbound_header_base_lock:
257
+ return _shared_outbound_header_base_registry.get("base_dict")
258
+
259
+
260
+ def _clear_cached_outbound_headers() -> None:
261
+ """Clear thread-local cached headers (called when base changes)."""
262
+ try:
263
+ if hasattr(_cached_outbound_headers_tls, "headers"):
264
+ delattr(_cached_outbound_headers_tls, "headers")
265
+ except AttributeError:
266
+ pass
267
+
268
+
269
+ def _get_shared_trace_id() -> Optional[str]:
270
+ # PERFORMANCE: In LD_PRELOAD mode, skip lock (ContextVar is primary source)
271
+ if _ld_preload_active:
272
+ return _shared_trace_registry.get("trace_id")
273
+ with _shared_trace_registry_lock:
274
+ return _shared_trace_registry.get("trace_id")
275
+
276
+
277
+ def _get_context_or_thread_local(
278
+ ctx_var: ContextVar, attr_name: str, default: Any
279
+ ) -> Any:
280
+ return ctx_var.get() # or getattr(_thread_locals, attr_name, default)
281
+
282
+
283
+ def _set_context_and_thread_local(
284
+ ctx_var: ContextVar, attr_name: str, value: Any
285
+ ) -> Any:
286
+ ctx_var.set(value)
287
+ # setattr(_thread_locals, attr_name, value)
288
+ return value
289
+
290
+
291
+ def unset_sf_trace_id() -> None:
292
+ _set_shared_trace_id(None)
293
+ _set_context_and_thread_local(trace_id_ctx, "trace_id", None)
294
+ if _SF_DEBUG_ENABLED:
295
+ print("[[DEBUG]] unset_sf_trace_id: trace_id cleared", log=False)
296
+
297
+
298
+ def _get_or_set_context_and_thread_local(
299
+ ctx_var: ContextVar, attr_name: str, value_if_not_set
300
+ ) -> Tuple[bool, Any]:
301
+ value = ctx_var.get() # or getattr(_thread_locals, attr_name, None)
302
+ if value is None:
303
+ _set_context_and_thread_local(ctx_var, attr_name, value_if_not_set)
304
+ return True, value_if_not_set
305
+ return False, value
306
+
307
+
308
+ # Trace ID functions
309
+ def get_sf_trace_id() -> Optional[Union[str, UUID]]:
310
+ # Use ContextVar for both LD_PRELOAD and Python-only modes
311
+ # ContextVar is async-safe and thread-safe, no shared registry needed
312
+ return _get_context_or_thread_local(trace_id_ctx, "trace_id", None)
313
+
314
+
315
+ def set_sf_trace_id(trace_id: Union[str, UUID]) -> Union[str, UUID]:
316
+ # Set in ContextVar for both LD_PRELOAD and Python-only modes
317
+ # ContextVar is async-safe and thread-safe, no shared registry needed
318
+ return _set_context_and_thread_local(trace_id_ctx, "trace_id", trace_id)
319
+
320
+
321
+ def generate_new_trace_id() -> str:
322
+ """
323
+ Generate and set a fresh trace_id for requests without incoming X-Sf3-Rid header.
324
+
325
+ This is called explicitly when there's no incoming tracing header, ensuring
326
+ a fresh trace_id is generated for each request (avoiding stale ContextVar reuse).
327
+
328
+ Returns:
329
+ The newly generated trace_id string.
330
+ """
331
+ unique_id = uuid.uuid4()
332
+ trace_id = f"{NONSESSION_APPLOGS}-v3/{app_config._sailfish_api_key}/{unique_id}"
333
+
334
+ # Set in ContextVar
335
+ _set_context_and_thread_local(trace_id_ctx, "trace_id", trace_id)
336
+
337
+ if _SF_DEBUG_ENABLED:
338
+ print(f"[generate_new_trace_id] Generated fresh trace_id: {trace_id}", log=False)
339
+
340
+ return trace_id
341
+
342
+
343
+ def get_or_set_sf_trace_id(
344
+ new_trace_id_if_not_set: Optional[str] = None,
345
+ is_associated_with_inbound_request: bool = False,
346
+ ) -> Tuple[bool, Union[str, UUID]]:
347
+ ###
348
+ ###
349
+ ###
350
+ ### IMPLEMENT skip if not ready yet?
351
+ ###
352
+ ###
353
+ ###
354
+ # Check if trace_id already exists
355
+ if not new_trace_id_if_not_set:
356
+ # Use ContextVar for both LD_PRELOAD and Python-only modes
357
+ trace_id = _get_context_or_thread_local(trace_id_ctx, "trace_id", None)
358
+ if trace_id:
359
+ if _SF_DEBUG_ENABLED:
360
+ print(f"[trace_id] Returning existing trace_id: {trace_id}", log=False)
361
+ return False, trace_id
362
+
363
+ # No trace_id found - generate new one
364
+ if _SF_DEBUG_ENABLED:
365
+ print("[trace_id] No trace_id found. Generating new trace_id.", log=False)
366
+ unique_id = uuid.uuid4()
367
+ trace_id = f"{NONSESSION_APPLOGS}-v3/{app_config._sailfish_api_key}/{unique_id}"
368
+
369
+ # Set using ContextVar only (no shared registry)
370
+ _set_context_and_thread_local(trace_id_ctx, "trace_id", trace_id)
371
+
372
+ if _SF_DEBUG_ENABLED:
373
+ print(f"[trace_id] Generated and set new trace_id: {trace_id}", log=False)
374
+ return True, trace_id
375
+
376
+ # new_trace_id_if_not_set provided - set it directly
377
+ if _SF_DEBUG_ENABLED:
378
+ print(
379
+ f"[trace_id] Setting new trace_id from argument: {new_trace_id_if_not_set}",
380
+ log=False,
381
+ )
382
+
383
+ # Set using ContextVar only (no shared registry)
384
+ _set_context_and_thread_local(trace_id_ctx, "trace_id", new_trace_id_if_not_set)
385
+
386
+ return True, new_trace_id_if_not_set
387
+
388
+
389
+ # Handled exceptions functions
390
+ def get_handled_exceptions() -> Set[Any]:
391
+ return _get_context_or_thread_local(
392
+ handled_exceptions_ctx, "handled_exceptions", set()
393
+ )
394
+
395
+
396
+ def set_handled_exceptions(exceptions_set: Set[Any]) -> Set[Any]:
397
+ return _set_context_and_thread_local(
398
+ handled_exceptions_ctx, "handled_exceptions", exceptions_set
399
+ )
400
+
401
+
402
+ def get_or_set_handled_exceptions(default: set = None) -> Tuple[bool, Set[Any]]:
403
+ if default is None:
404
+ default = set()
405
+ return _get_or_set_context_and_thread_local(
406
+ handled_exceptions_ctx, "handled_exceptions", default
407
+ )
408
+
409
+
410
+ def mark_exception_handled(exception) -> None:
411
+ handled = get_handled_exceptions()
412
+ handled.add(id(exception))
413
+ set_handled_exceptions(handled)
414
+ if hasattr(exception, "_handled"):
415
+ setattr(exception, "_handled", True)
416
+
417
+
418
+ def has_handled_exception(exception) -> bool:
419
+ return id(exception) in get_handled_exceptions() or getattr(
420
+ exception, "_handled", False
421
+ )
422
+
423
+
424
+ def reset_handled_exceptions() -> Set[Any]:
425
+ return set_handled_exceptions(set())
426
+
427
+
428
+ # Reentrancy guards (logging)
429
+ def get_reentrancy_guard_logging_active() -> bool:
430
+ return _get_context_or_thread_local(
431
+ reentrancy_guard_logging_active_ctx, "reentrancy_guard_logging_active", False
432
+ )
433
+
434
+
435
+ def set_reentrancy_guard_logging_active(value: bool) -> bool:
436
+ return _set_context_and_thread_local(
437
+ reentrancy_guard_logging_active_ctx, "reentrancy_guard_logging_active", value
438
+ )
439
+
440
+
441
+ def get_or_set_reentrancy_guard_logging_active(
442
+ value_if_not_set: bool,
443
+ ) -> Tuple[bool, bool]:
444
+ return _get_or_set_context_and_thread_local(
445
+ reentrancy_guard_logging_active_ctx,
446
+ "reentrancy_guard_logging_active",
447
+ value_if_not_set,
448
+ )
449
+
450
+
451
+ def activate_reentrancy_guards_logging() -> bool:
452
+ set_reentrancy_guard_logging_active(True)
453
+ set_reentrancy_guard_logging_preactive(True)
454
+ return True
455
+
456
+
457
+ def get_reentrancy_guard_logging_preactive() -> bool:
458
+ return _get_context_or_thread_local(
459
+ reentrancy_guard_logging_preactive_ctx,
460
+ "reentrancy_guard_logging_preactive",
461
+ False,
462
+ )
463
+
464
+
465
+ def set_reentrancy_guard_logging_preactive(value: bool) -> bool:
466
+ return _set_context_and_thread_local(
467
+ reentrancy_guard_logging_preactive_ctx,
468
+ "reentrancy_guard_logging_preactive",
469
+ value,
470
+ )
471
+
472
+
473
+ def get_or_set_reentrancy_guard_logging_preactive(
474
+ value_if_not_set: bool,
475
+ ) -> Tuple[bool, bool]:
476
+ return _get_or_set_context_and_thread_local(
477
+ reentrancy_guard_logging_preactive_ctx,
478
+ "reentrancy_guard_logging_preactive",
479
+ value_if_not_set,
480
+ )
481
+
482
+
483
+ def activate_reentrancy_guards_logging_preactive() -> bool:
484
+ return set_reentrancy_guard_logging_preactive(True)
485
+
486
+
487
+ # Reentrancy guards (stdout)
488
+ def get_reentrancy_guard_sys_stdout_active() -> bool:
489
+ return _get_context_or_thread_local(
490
+ reentrancy_guard_sys_stdout_active_ctx,
491
+ "reentrancy_guard_sys_stdout_active",
492
+ False,
493
+ )
494
+
495
+
496
+ def set_reentrancy_guard_sys_stdout_active(value: bool) -> bool:
497
+ return _set_context_and_thread_local(
498
+ reentrancy_guard_sys_stdout_active_ctx,
499
+ "reentrancy_guard_sys_stdout_active",
500
+ value,
501
+ )
502
+
503
+
504
+ def activate_reentrancy_guards_sys_stdout() -> bool:
505
+ set_reentrancy_guard_sys_stdout_active(True)
506
+ return True
507
+
508
+
509
+ # Reentrancy guards (print)
510
+ def get_reentrancy_guard_print_active() -> bool:
511
+ return _get_context_or_thread_local(
512
+ reentrancy_guard_print_active_ctx, "reentrancy_guard_print_active", False
513
+ )
514
+
515
+
516
+ def set_reentrancy_guard_print_active(value: bool) -> bool:
517
+ return _set_context_and_thread_local(
518
+ reentrancy_guard_print_active_ctx, "reentrancy_guard_print_active", value
519
+ )
520
+
521
+
522
+ def get_or_set_reentrancy_guard_print_active(
523
+ value_if_not_set: bool,
524
+ ) -> Tuple[bool, bool]:
525
+ return _get_or_set_context_and_thread_local(
526
+ reentrancy_guard_print_active_ctx,
527
+ "reentrancy_guard_print_active",
528
+ value_if_not_set,
529
+ )
530
+
531
+
532
+ def activate_reentrancy_guards_print() -> bool:
533
+ set_reentrancy_guard_print_active(True)
534
+ set_reentrancy_guard_print_preactive(True)
535
+ return True
536
+
537
+
538
+ def get_reentrancy_guard_print_preactive() -> bool:
539
+ return _get_context_or_thread_local(
540
+ reentrancy_guard_print_preactive_ctx, "reentrancy_guard_print_preactive", False
541
+ )
542
+
543
+
544
+ def set_reentrancy_guard_print_preactive(value: bool) -> bool:
545
+ return _set_context_and_thread_local(
546
+ reentrancy_guard_print_preactive_ctx, "reentrancy_guard_print_preactive", value
547
+ )
548
+
549
+
550
+ def get_or_set_reentrancy_guard_print_preactive(
551
+ value_if_not_set: bool,
552
+ ) -> Tuple[bool, bool]:
553
+ return _get_or_set_context_and_thread_local(
554
+ reentrancy_guard_print_preactive_ctx,
555
+ "reentrancy_guard_print_preactive",
556
+ value_if_not_set,
557
+ )
558
+
559
+
560
+ def activate_reentrancy_guards_print_preactive() -> bool:
561
+ return set_reentrancy_guard_print_preactive(True)
562
+
563
+
564
+ # Reentrancy guards (exception)
565
+ def get_reentrancy_guard_exception_active() -> bool:
566
+ return _get_context_or_thread_local(
567
+ reentrancy_guard_exception_active_ctx,
568
+ "reentrancy_guard_exception_active",
569
+ False,
570
+ )
571
+
572
+
573
+ def set_reentrancy_guard_exception_active(value: bool) -> bool:
574
+ return _set_context_and_thread_local(
575
+ reentrancy_guard_exception_active_ctx,
576
+ "reentrancy_guard_exception_active",
577
+ value,
578
+ )
579
+
580
+
581
+ def get_or_set_reentrancy_guard_exception_active(
582
+ value_if_not_set: bool,
583
+ ) -> Tuple[bool, bool]:
584
+ return _get_or_set_context_and_thread_local(
585
+ reentrancy_guard_exception_active_ctx,
586
+ "reentrancy_guard_exception_active",
587
+ value_if_not_set,
588
+ )
589
+
590
+
591
+ def activate_reentrancy_guards_exception() -> bool:
592
+ set_reentrancy_guard_exception_active(True)
593
+ set_reentrancy_guard_exception_preactive(True)
594
+ return True
595
+
596
+
597
+ def get_reentrancy_guard_exception_preactive() -> bool:
598
+ return _get_context_or_thread_local(
599
+ reentrancy_guard_exception_preactive_ctx,
600
+ "reentrancy_guard_exception_preactive",
601
+ False,
602
+ )
603
+
604
+
605
+ def set_reentrancy_guard_exception_preactive(value: bool) -> bool:
606
+ return _set_context_and_thread_local(
607
+ reentrancy_guard_exception_preactive_ctx,
608
+ "reentrancy_guard_exception_preactive",
609
+ value,
610
+ )
611
+
612
+
613
+ def get_or_set_reentrancy_guard_exception_preactive(
614
+ value_if_not_set: bool,
615
+ ) -> Tuple[bool, bool]:
616
+ return _get_or_set_context_and_thread_local(
617
+ reentrancy_guard_exception_preactive_ctx,
618
+ "reentrancy_guard_exception_preactive",
619
+ value_if_not_set,
620
+ )
621
+
622
+
623
+ def activate_reentrancy_guards_exception_preactive() -> bool:
624
+ return set_reentrancy_guard_exception_preactive(True)
625
+
626
+
627
+ # Get and set context
628
+ def get_context() -> Dict[str, Any]:
629
+ return {
630
+ "trace_id": get_sf_trace_id(),
631
+ "handled_exceptions": get_handled_exceptions(),
632
+ "reentrancy_guard_logging_active": get_reentrancy_guard_logging_active(),
633
+ "reentrancy_guard_logging_preactive": get_reentrancy_guard_logging_preactive(),
634
+ "reentrancy_guard_print_active": get_reentrancy_guard_print_active(),
635
+ "reentrancy_guard_print_preactive": get_reentrancy_guard_print_preactive(),
636
+ "reentrancy_guard_exception_active": get_reentrancy_guard_exception_active(),
637
+ "reentrancy_guard_exception_preactive": get_reentrancy_guard_exception_preactive(),
638
+ "reentrancy_guard_sys_stdout_active": get_reentrancy_guard_sys_stdout_active(),
639
+ "suppress_network_recording": is_network_recording_suppressed(),
640
+ "suppress_log_output": is_log_output_suppressed(),
641
+ }
642
+
643
+
644
+ def set_context(context) -> None:
645
+ set_sf_trace_id(context.get("trace_id"))
646
+ set_handled_exceptions(context.get("handled_exceptions", set()))
647
+ set_reentrancy_guard_logging_active(
648
+ context.get("reentrancy_guard_logging_active", False)
649
+ )
650
+ set_reentrancy_guard_logging_preactive(
651
+ context.get("reentrancy_guard_logging_preactive", False)
652
+ )
653
+ set_reentrancy_guard_print_active(
654
+ context.get("reentrancy_guard_print_active", False)
655
+ )
656
+ set_reentrancy_guard_print_preactive(
657
+ context.get("reentrancy_guard_print_preactive", False)
658
+ )
659
+ set_reentrancy_guard_exception_active(
660
+ context.get("reentrancy_guard_exception_active", False)
661
+ )
662
+ set_reentrancy_guard_exception_preactive(
663
+ context.get("reentrancy_guard_exception_preactive", False)
664
+ )
665
+ set_reentrancy_guard_sys_stdout_active(
666
+ context.get("reentrancy_guard_sys_stdout_active", False)
667
+ )
668
+ # suppressors are transient; don't set them from incoming context
669
+
670
+
671
+ @contextmanager
672
+ def suppress_network_recording():
673
+ token = suppress_network_recording_ctx.set(True)
674
+ try:
675
+ yield
676
+ finally:
677
+ suppress_network_recording_ctx.reset(token)
678
+
679
+
680
+ @functools.lru_cache(maxsize=1)
681
+ def _get_disabled_route_patterns() -> List[str]:
682
+ """
683
+ Get route patterns to skip network hop capture.
684
+
685
+ Routes are configured via setup_interceptors(routes_to_skip_network_hops=[...])
686
+ which defaults to the SF_DISABLE_INBOUND_NETWORK_TRACING_ON_ROUTES env var.
687
+
688
+ Returns a list of route patterns with wildcard support (* and ? characters).
689
+ Cached for performance (called on every request).
690
+
691
+ Examples:
692
+ "/healthz, /metrics" -> ["/healthz", "/metrics"]
693
+ "/admin/*, /api/v1/status*" -> ["/admin/*", "/api/v1/status*"]
694
+ """
695
+ # Get patterns from app_config (already contains parameter or env var default)
696
+ patterns = getattr(app_config, "_routes_to_skip_network_hops", [])
697
+
698
+ if _SF_DEBUG_ENABLED and patterns:
699
+ print(f"[_get_disabled_route_patterns] Route patterns to skip: {patterns}", log=False)
700
+
701
+ return patterns
702
+
703
+
704
+ def _route_matches_pattern(path: str) -> bool:
705
+ """
706
+ Check if the given path matches any disabled route pattern.
707
+
708
+ Uses fnmatch for glob-style pattern matching:
709
+ - * matches any sequence of characters
710
+ - ? matches any single character
711
+
712
+ Args:
713
+ path: Request path to check (e.g., "/api/v1/users")
714
+
715
+ Returns:
716
+ True if path matches any disabled pattern, False otherwise.
717
+
718
+ Examples:
719
+ _route_matches_pattern("/healthz") -> True if "/healthz" in patterns
720
+ _route_matches_pattern("/admin/users") -> True if "/admin/*" in patterns
721
+ _route_matches_pattern("/api/v1/status") -> True if "/api/v1/status*" in patterns
722
+ """
723
+ patterns = _get_disabled_route_patterns()
724
+ if not patterns:
725
+ return False
726
+
727
+ # Use fnmatch for glob pattern matching (* and ? wildcards)
728
+ for pattern in patterns:
729
+ if fnmatch.fnmatch(path, pattern):
730
+ if _SF_DEBUG_ENABLED:
731
+ print(f"[_route_matches_pattern] Path '{path}' matches pattern '{pattern}' - suppressing", log=False)
732
+ return True
733
+
734
+ return False
735
+
736
+
737
+ def is_network_recording_suppressed() -> bool:
738
+ """
739
+ Check if network recording is suppressed.
740
+
741
+ Checks three suppression mechanisms (any one triggers suppression):
742
+ 1. Explicit suppression via context manager or decorator (suppress_network_recording_ctx)
743
+ 2. Route-based suppression via SF_DISABLE_INBOUND_NETWORK_TRACING_ON_ROUTES
744
+ 3. Thread-local C telemetry guard (g_in_telemetry_send) - used by C extension sender threads
745
+
746
+ Returns:
747
+ True if network recording is suppressed by any mechanism, False otherwise.
748
+ """
749
+ # Check explicit suppression (context manager / decorator)
750
+ if suppress_network_recording_ctx.get():
751
+ return True
752
+
753
+ # Check route-based suppression
754
+ current_path = get_current_request_path()
755
+ if current_path and _route_matches_pattern(current_path):
756
+ return True
757
+
758
+ return False
759
+
760
+
761
+ @contextmanager
762
+ def suppress_log_output():
763
+ token = suppress_log_output_ctx.set(True)
764
+ try:
765
+ yield
766
+ finally:
767
+ suppress_log_output_ctx.reset(token)
768
+
769
+
770
+ def is_log_output_suppressed() -> bool:
771
+ return suppress_log_output_ctx.get()
772
+
773
+
774
+ # Current request path functions (for route-based suppression)
775
+ def set_current_request_path(path: str) -> None:
776
+ """Set current request path for route-based network suppression."""
777
+ current_request_path_ctx.set(path)
778
+
779
+
780
+ def get_current_request_path() -> Optional[str]:
781
+ """Get current request path."""
782
+ return current_request_path_ctx.get()
783
+
784
+
785
+ def clear_current_request_path() -> None:
786
+ """Clear current request path at end of request."""
787
+ current_request_path_ctx.set(None)
788
+
789
+
790
+ # Function span capture override functions (ultra-fast, <10ns)
791
+ def get_funcspan_override() -> Optional[str]:
792
+ """Get function span capture override header value (fast ContextVar lookup ~8ns)."""
793
+ return funcspan_override_ctx.get()
794
+
795
+
796
+ def set_funcspan_override(value: Optional[str]) -> Optional[str]:
797
+ """Set function span capture override header value."""
798
+ funcspan_override_ctx.set(value)
799
+ return value
800
+
801
+
802
+ def clear_funcspan_override() -> None:
803
+ """Clear function span capture override."""
804
+ funcspan_override_ctx.set(None)
805
+
806
+
807
+ # ================================
808
+ # Outbound header generation (ultra-fast header injection with shared registry + ContextVar)
809
+ # ================================
810
+
811
+
812
+ def set_outbound_header_base(
813
+ base_trace: str, parent_trace_id: str, funcspan: Optional[str]
814
+ ) -> None:
815
+ """
816
+ Store base header info in BOTH shared registry AND ContextVar.
817
+
818
+ **OPTIMIZATION:** When LD_PRELOAD is active, pre-builds the headers dict HERE
819
+ so all outbound calls can reuse it (~10ns vs 1-10μs per call).
820
+
821
+ Uses same pattern as trace_id for cross-thread support.
822
+
823
+ Args:
824
+ base_trace: Base trace path (e.g., "session_id/page_visit_id") - used for generating new X-Sf3-Rid
825
+ parent_trace_id: FULL incoming trace_id (e.g., "session_id/page_visit_id/request_uuid") - used for X-Sf4-Prid
826
+ funcspan: Optional function span capture override header value
827
+
828
+ Performance: <1μs (dict creation + set operations)
829
+
830
+ ULTRA-OPTIMIZED: Lockless path when LD_PRELOAD active, minimal dict allocations, no C calls.
831
+ """
832
+ # Pre-build cached headers for both LD_PRELOAD and Python-only modes
833
+ # This optimization applies to both modes now
834
+ if funcspan:
835
+ # Construct nested dict in one go for funcspan case
836
+ cached_headers = {
837
+ SAILFISH_TRACING_HEADER: base_trace,
838
+ PARENT_SESSION_ID_HEADER: parent_trace_id,
839
+ FUNCSPAN_OVERRIDE_HEADER: funcspan,
840
+ }
841
+ base_dict = {
842
+ "base_trace": base_trace,
843
+ "parent_trace_id": parent_trace_id,
844
+ "funcspan": funcspan,
845
+ "_cached_headers": cached_headers,
846
+ }
847
+ else:
848
+ # Fast path: No funcspan (most common case) - construct in one go
849
+ cached_headers = {
850
+ SAILFISH_TRACING_HEADER: base_trace,
851
+ PARENT_SESSION_ID_HEADER: parent_trace_id,
852
+ }
853
+ base_dict = {
854
+ "base_trace": base_trace,
855
+ "parent_trace_id": parent_trace_id,
856
+ "funcspan": None,
857
+ "_cached_headers": cached_headers,
858
+ }
859
+
860
+ # Store in ContextVar only (no shared registry)
861
+ outbound_header_base_ctx.set(base_dict)
862
+
863
+ # DEBUG: Log when outbound header base is set (helps troubleshoot X-Sf4-Prid issues)
864
+ if _SF_DEBUG_ENABLED:
865
+ print(
866
+ f"[set_outbound_header_base] Set parent_trace_id={parent_trace_id}, base_trace={base_trace}, cached_headers={cached_headers}",
867
+ log=False,
868
+ )
869
+
870
+ # Set C TLS for non-LD_PRELOAD mode
871
+ if not _ld_preload_active:
872
+ _set_c_tls_parent_trace_id(parent_trace_id)
873
+
874
+
875
+ def get_outbound_headers_with_new_uuid() -> dict:
876
+ """
877
+ Generate fresh outbound headers with new UUID appended to base trace.
878
+
879
+ **ULTRA-FAST when LD_PRELOAD is active:**
880
+ - Headers dict cached in ContextVar (no lock, ~10-20ns)
881
+ - C appends UUID at socket intercept time (no Python UUID generation)
882
+ - Total overhead: ~10-20ns (just ContextVar read + dict return)
883
+
884
+ **When LD_PRELOAD inactive:**
885
+ - Each call generates new UUID in Python (~100ns)
886
+ - Headers dict built fresh each time
887
+
888
+ Returns:
889
+ Dictionary with X-Sf3-Rid, X-Sf4-Prid, and optionally X-Sf3-FunctionSpanCaptureOverride.
890
+ Empty dict if no base initialized.
891
+
892
+ Performance:
893
+ - LD_PRELOAD active: ~10-20ns (cached headers from ContextVar, NO LOCK)
894
+ - LD_PRELOAD inactive: ~100ns (uuid4 generation + string concat + dict creation)
895
+ """
896
+ # Get base_dict from ContextVar (no shared registry fallback)
897
+ base_dict = outbound_header_base_ctx.get()
898
+
899
+ if not base_dict:
900
+ if _SF_DEBUG_ENABLED:
901
+ print(
902
+ f"[get_outbound_headers_with_new_uuid] ⚠️ No outbound header base found in ContextVar",
903
+ log=False,
904
+ )
905
+ return {}
906
+
907
+ # ULTRA-FAST PATH: Return pre-built headers if available (LD_PRELOAD mode)
908
+ cached_headers = base_dict.get("_cached_headers")
909
+ if cached_headers:
910
+ # DEBUG: ENABLED for troubleshooting X-Sf4-Prid issue
911
+ if _SF_DEBUG_ENABLED:
912
+ print(
913
+ f"[get_outbound_headers_with_new_uuid] ⚡ Returning pre-built headers: {cached_headers}",
914
+ log=False,
915
+ )
916
+ # Return a shallow copy to prevent mutations from affecting cached dict
917
+ return dict(cached_headers)
918
+
919
+ # SLOW PATH: Generate UUID in Python (Python-only mode)
920
+ if _SF_DEBUG_ENABLED:
921
+ print(
922
+ f"[get_outbound_headers_with_new_uuid] 🐌 LD_PRELOAD inactive - generating UUID in Python",
923
+ log=False,
924
+ )
925
+
926
+ base_trace = base_dict.get("base_trace")
927
+ parent_trace_id = base_dict.get("parent_trace_id")
928
+ funcspan = base_dict.get("funcspan")
929
+
930
+ if not base_trace or not parent_trace_id:
931
+ if _SF_DEBUG_ENABLED:
932
+ print(
933
+ f"[get_outbound_headers_with_new_uuid] ⚠️ Missing base_trace or parent_trace_id!",
934
+ log=False,
935
+ )
936
+ return {}
937
+
938
+ # Generate new UUID for each call
939
+ new_uuid = str(uuid.uuid4())
940
+ outbound_trace_id = f"{base_trace}/{new_uuid}"
941
+
942
+ headers = {
943
+ SAILFISH_TRACING_HEADER: outbound_trace_id, # X-Sf3-Rid: session/page/uuid (Python)
944
+ PARENT_SESSION_ID_HEADER: parent_trace_id,
945
+ }
946
+
947
+ if funcspan:
948
+ headers[FUNCSPAN_OVERRIDE_HEADER] = funcspan
949
+
950
+ return headers
951
+
952
+
953
+ import logging
954
+
955
+ # include httpcore/h11/h2 because we now call httpcore directly
956
+ _HTTPX_LOGGERS = ("httpx", "httpcore", "h11", "h2")
957
+
958
+
959
+ @contextmanager
960
+ def suppress_logs():
961
+ """Temporarily silence client libraries without touching global logging config."""
962
+ loggers = [logging.getLogger(n) for n in _HTTPX_LOGGERS]
963
+ prev_disabled = [lg.disabled for lg in loggers]
964
+ try:
965
+ for lg in loggers:
966
+ lg.disabled = True
967
+ yield
968
+ finally:
969
+ for lg, was_disabled in zip(loggers, prev_disabled):
970
+ lg.disabled = was_disabled