sf-veritas 0.10.3__cp313-cp313-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-313-x86_64-linux-gnu.so +0 -0
  4. sf_veritas/_sffastnet.c +924 -0
  5. sf_veritas/_sffastnet.cpython-313-x86_64-linux-gnu.so +0 -0
  6. sf_veritas/_sffastnetworkrequest.c +730 -0
  7. sf_veritas/_sffastnetworkrequest.cpython-313-x86_64-linux-gnu.so +0 -0
  8. sf_veritas/_sffuncspan.c +2155 -0
  9. sf_veritas/_sffuncspan.cpython-313-x86_64-linux-gnu.so +0 -0
  10. sf_veritas/_sffuncspan_config.c +617 -0
  11. sf_veritas/_sffuncspan_config.cpython-313-x86_64-linux-gnu.so +0 -0
  12. sf_veritas/_sfheadercheck.c +341 -0
  13. sf_veritas/_sfheadercheck.cpython-313-x86_64-linux-gnu.so +0 -0
  14. sf_veritas/_sfnetworkhop.c +1451 -0
  15. sf_veritas/_sfnetworkhop.cpython-313-x86_64-linux-gnu.so +0 -0
  16. sf_veritas/_sfservice.c +1175 -0
  17. sf_veritas/_sfservice.cpython-313-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,49 @@
1
+ import os
2
+ import uuid
3
+ from typing import Dict, List, Optional, Union
4
+
5
+ from .constants import SAILFISH_DEFAULT_GRAPHQL_ENDPOINT
6
+ from .infra_details import get_infra_details
7
+
8
+ _service_uuid = str(uuid.uuid4())
9
+ _service_identifier = None
10
+ _service_version = None
11
+ _git_sha = None
12
+ _service_identification_received = False
13
+ _service_additional_metadata: Optional[Dict[str, Union[str, int, float, None]]] = None
14
+
15
+ _setup_interceptors_call_filename = None
16
+ _setup_interceptors_call_lineno = None
17
+
18
+ _interceptors_initialized = False
19
+
20
+ _profiling_mode_enabled: bool = False
21
+ _profiling_max_depth: int = 5
22
+
23
+ _site_and_dist_packages_to_collect_local_variables_on: Optional[List[str]] = []
24
+
25
+ _sailfish_api_key = None
26
+ _sailfish_graphql_endpoint: str = os.getenv(
27
+ "SAILFISH_GRAPHQL_ENDPOINT", SAILFISH_DEFAULT_GRAPHQL_ENDPOINT
28
+ )
29
+
30
+ _infra_details = get_infra_details()
31
+
32
+
33
+ def _set_site_and_dist_packages_to_collect_local_variables_on(
34
+ site_and_dist_packages_to_collect_local_variables_on: Optional[List[str]] = None,
35
+ ):
36
+ if site_and_dist_packages_to_collect_local_variables_on is None:
37
+ _site_and_dist_packages_to_collect_local_variables_on = []
38
+ site_and_dist_packages_to_collect_local_variables_on_final: List[str] = (
39
+ site_and_dist_packages_to_collect_local_variables_on.copy()
40
+ )
41
+ for package in site_and_dist_packages_to_collect_local_variables_on:
42
+ if "-" not in package:
43
+ continue
44
+ site_and_dist_packages_to_collect_local_variables_on_final.append(
45
+ package.replace("-", "_")
46
+ )
47
+ _site_and_dist_packages_to_collect_local_variables_on = (
48
+ site_and_dist_packages_to_collect_local_variables_on_final
49
+ )
sf_veritas/cli.py ADDED
@@ -0,0 +1,336 @@
1
+ import os
2
+ import subprocess
3
+ import sys
4
+ import tempfile
5
+ from typing import Dict, List, Optional, Union
6
+
7
+ from fire import Fire
8
+
9
+ from .env_vars import SF_DEBUG
10
+ from .unified_interceptor import setup_interceptors
11
+
12
+ METHOD = "os.execvp"
13
+
14
+
15
+ def inject_code(file_path, **kwargs):
16
+ """
17
+ Injects the given code snippet at the top of the target Python file.
18
+ """
19
+ code_to_inject = f"""
20
+ from sf_veritas import setup_interceptors
21
+
22
+ setup_interceptors(
23
+ api_key={kwargs.get('api_key')!r},
24
+ service_identifier={kwargs.get('service_identifier', None)!r},
25
+ service_version={kwargs.get('service_version', None)!r},
26
+ service_additional_metadata={kwargs.get('service_additional_metadata', None)!r},
27
+ profiling_mode_enabled={kwargs.get('profiling_mode_enabled', False)},
28
+ profiling_max_depth={kwargs.get('profiling_max_depth', 5)},
29
+ domains_to_not_propagate_headers_to={kwargs.get('domains_to_not_propagate_headers_to', None)!r},
30
+ site_and_dist_packages_to_collect_local_variables_on={kwargs.get('site_and_dist_packages_to_collect_local_variables_on', None)!r}
31
+ )
32
+ """
33
+
34
+ # Read the original file content
35
+ with open(file_path, "r") as f:
36
+ original_content = f.read()
37
+
38
+ # Combine the injected code with the original content
39
+ new_content = code_to_inject + "\n" + original_content
40
+
41
+ # Create a temporary file in the same directory as the original script
42
+ temp_dir = os.path.dirname(file_path)
43
+ temp_file = tempfile.NamedTemporaryFile(
44
+ delete=False, dir=temp_dir, prefix="temp-", suffix=".py"
45
+ )
46
+
47
+ # Write the new content to the temporary file
48
+ with open(temp_file.name, "w") as f:
49
+ f.write(new_content)
50
+
51
+ return temp_file.name
52
+
53
+
54
+ def get_sf_veritas_index(argv=sys.argv):
55
+ for i, arg in enumerate(argv):
56
+ if "sf-veritas" in arg:
57
+ return i
58
+ return None
59
+
60
+
61
+ def get_python_index(argv=sys.argv):
62
+ if "python" in argv:
63
+ return argv.index("python")
64
+ if "python3" in argv:
65
+ return argv.index("python3")
66
+ return None
67
+
68
+
69
+ def get_command_index(command_name, argv=sys.argv):
70
+ """
71
+ Returns the index of a given command in sys.argv or None if not found.
72
+ """
73
+ if command_name in argv:
74
+ return argv.index(command_name)
75
+ return None
76
+
77
+
78
+ def handle_framework_command(
79
+ command_index,
80
+ module_delimiter=":",
81
+ argv=sys.argv,
82
+ app_path_offset: int = 1,
83
+ **kwargs,
84
+ ):
85
+ """
86
+ Handles framework commands like uvicorn, gunicorn, sanic, waitress-serve, daphne, etc.
87
+ """
88
+ app_path = argv[command_index + app_path_offset]
89
+ module_path = app_path.split(module_delimiter)[0].replace(".", "/") + ".py"
90
+
91
+ # Inject code into the module
92
+ modified_module_path = inject_code(module_path, **kwargs)
93
+
94
+ # Turn the modified file path into a dotted module path
95
+ modified_module_relpath = os.path.relpath(modified_module_path, start=os.getcwd())
96
+ new_module_path = os.path.splitext(modified_module_relpath)[0].replace("/", ".")
97
+
98
+ # Strip off any leading dots that appear because of ../ or ./ references
99
+ new_module_path = new_module_path.lstrip(".")
100
+
101
+ # If there's still nothing, you could do a sanity check here:
102
+ # if not new_module_path:
103
+ # raise ValueError("Unable to determine a valid module name from path")
104
+
105
+ # Reconstruct the final "module:app" string
106
+ if module_delimiter in app_path:
107
+ # e.g., "backend.temp-abcdef:application"
108
+ modified_app_path = (
109
+ new_module_path + module_delimiter + app_path.split(module_delimiter)[1]
110
+ )
111
+ else:
112
+ modified_app_path = new_module_path
113
+
114
+ # Build the run_command
115
+ run_command = [argv[command_index], *argv[command_index + 1 :]]
116
+ run_command[app_path_offset] = modified_app_path
117
+
118
+ return run_command
119
+
120
+
121
+ def find_application_arg(command_index, argv=sys.argv):
122
+ """
123
+ Finds the application argument for daphne and granian commands, ignoring any flags.
124
+ """
125
+ if SF_DEBUG:
126
+ print(
127
+ f"Checking arguments after index {command_index}: {argv[command_index + 1:]}",
128
+ log=False,
129
+ )
130
+ should_skip = False
131
+ for i in range(command_index + 1, len(argv)):
132
+ if should_skip:
133
+ should_skip = False
134
+ continue
135
+ if argv[i].startswith("-"):
136
+ should_skip = True
137
+ continue
138
+ return argv[i]
139
+ return None
140
+
141
+
142
+ def main(
143
+ api_key: str,
144
+ service_identifier: str = None,
145
+ service_version: Union[str, int] = None, # Allow both string and int
146
+ service_additional_metadata: Dict[str, Union[str, int, float, None]] = None,
147
+ profiling_mode_enabled: bool = False,
148
+ profiling_max_depth: int = 5,
149
+ domains_to_not_propagate_headers_to: Optional[List[str]] = None,
150
+ site_and_dist_packages_to_collect_local_variables_on: Optional[List[str]] = None,
151
+ *args,
152
+ ):
153
+ """
154
+ Main function to handle CLI and set up interceptors.
155
+
156
+ Args:
157
+ api_key: (Required) API key for authentication. This must be provided as a CLI argument.
158
+ service_identifier: Identifier for the service.
159
+ service_version: Version of the service.
160
+ service_additional_metadata: Additional metadata to associate with the service.
161
+ profiling_mode_enabled: Whether profiling mode is enabled.
162
+ profiling_max_depth: Maximum depth for profiling.
163
+ domains_to_not_propagate_headers_to: Domains to which headers should not be propagated.
164
+ site_and_dist_packages_to_collect_local_variables_on: If set to None, defaults to [], excluding all installed packages.
165
+ *args: Additional command-line arguments to be passed through.
166
+ """
167
+
168
+ # Convert service_version to string if it's numeric
169
+ if service_version is not None:
170
+ service_version = str(service_version)
171
+
172
+ # Collect setup parameters
173
+ setup_params = {
174
+ "api_key": api_key,
175
+ "service_identifier": service_identifier,
176
+ "service_version": service_version,
177
+ "service_additional_metadata": service_additional_metadata,
178
+ "profiling_mode_enabled": profiling_mode_enabled,
179
+ "profiling_max_depth": profiling_max_depth,
180
+ "domains_to_not_propagate_headers_to": domains_to_not_propagate_headers_to,
181
+ "site_and_dist_packages_to_collect_local_variables_on": site_and_dist_packages_to_collect_local_variables_on,
182
+ }
183
+
184
+ # Remaining arguments are captured in *args
185
+ remaining_args = sys.argv[1:]
186
+ try:
187
+ separator_index = sys.argv.index("--")
188
+ remaining_args = sys.argv[separator_index + 1 :]
189
+ except ValueError:
190
+ pass
191
+
192
+ if SF_DEBUG:
193
+ print("[[ DEBUG ]] sys.argv [[ /DEBUG ]]", sys.argv)
194
+ print("[[ DEBUG ]] setup_params [[ /DEBUG ]]", setup_params)
195
+ print("[[ DEBUG ]] remaining_args [[ /DEBUG ]]", remaining_args)
196
+
197
+ setup_interceptors(**setup_params)
198
+
199
+ if not remaining_args:
200
+ print("Usage: sf-veritas [options] <command> [args]")
201
+ sys.exit(1)
202
+
203
+ # Detect CLI command and adjust injection based on the framework
204
+ python_index = get_python_index(remaining_args)
205
+ uvicorn_index = get_command_index("uvicorn", remaining_args)
206
+ gunicorn_index = get_command_index("gunicorn", remaining_args)
207
+ flask_index = get_command_index("flask", remaining_args)
208
+ django_admin_index = get_command_index("django-admin", remaining_args)
209
+ sanic_index = get_command_index("sanic", remaining_args)
210
+ pserve_index = get_command_index("pserve", remaining_args)
211
+ waitress_index = get_command_index("waitress-serve", remaining_args)
212
+ daphne_index = get_command_index("daphne", remaining_args)
213
+ granian_index = get_command_index("granian", remaining_args)
214
+
215
+ if python_index is not None and python_index >= 0:
216
+ if "-m" in remaining_args:
217
+ module_index = remaining_args.index("-m", python_index)
218
+
219
+ # Ensure there's a module specified after `-m`
220
+ if module_index + 1 < len(remaining_args):
221
+ module_dot_path = remaining_args[module_index + 1]
222
+ module_path = module_dot_path.replace(".", "/") + ".py"
223
+
224
+ # Inject code into the module, passing setup_params
225
+ modified_module_path = inject_code(module_path, **setup_params)
226
+
227
+ # Calculate the relative path for the new temporary file's dot-path
228
+ new_module_dot_path = os.path.splitext(
229
+ os.path.relpath(modified_module_path, start=os.getcwd())
230
+ )[0].replace("/", ".")
231
+
232
+ # Rebuild the command with the modified module dot-path
233
+ code_after_module_index = module_index + 2
234
+ run_command = [
235
+ sys.executable,
236
+ "-m",
237
+ new_module_dot_path,
238
+ ] + remaining_args[code_after_module_index:]
239
+ else:
240
+ print("Error: No module specified after '-m'.")
241
+ sys.exit(1)
242
+ else:
243
+ script_path = remaining_args[python_index + 1]
244
+
245
+ # Inject code into the script file directly, passing setup_params
246
+ modified_script_path = inject_code(script_path, **setup_params)
247
+
248
+ # Rebuild the command to use the path to the modified script file
249
+ run_command = [sys.executable, modified_script_path] + remaining_args[
250
+ python_index + 2 :
251
+ ]
252
+ elif uvicorn_index is not None:
253
+ run_command = handle_framework_command(
254
+ uvicorn_index, module_delimiter=".", argv=remaining_args, **setup_params
255
+ )
256
+ elif gunicorn_index is not None:
257
+ run_command = handle_framework_command(
258
+ gunicorn_index, argv=remaining_args, **setup_params
259
+ )
260
+ elif sanic_index is not None:
261
+ run_command = handle_framework_command(
262
+ sanic_index, module_delimiter=".", argv=remaining_args, **setup_params
263
+ )
264
+ elif waitress_index is not None:
265
+ run_command = handle_framework_command(
266
+ waitress_index, module_delimiter=":", argv=remaining_args, **setup_params
267
+ )
268
+ elif flask_index is not None:
269
+ # Flask expects the FLASK_APP environment variable
270
+ app_path = os.environ.get("FLASK_APP", None)
271
+ if app_path:
272
+ module_path = app_path.replace(".", "/") + ".py"
273
+
274
+ # Inject code into the module, passing setup_params
275
+ modified_module_path = inject_code(module_path, **setup_params)
276
+
277
+ # Set the modified path back to FLASK_APP
278
+ os.environ["FLASK_APP"] = modified_module_path.replace("/", ".").replace(
279
+ ".py", ""
280
+ )
281
+
282
+ run_command = remaining_args
283
+ elif django_admin_index is not None:
284
+ # Handle django-admin commands
285
+ script_path = remaining_args[django_admin_index]
286
+
287
+ # Inject code into the script file directly, passing setup_params
288
+ modified_script_path = inject_code(script_path, **setup_params)
289
+
290
+ # Rebuild the command to use the path to the modified script file
291
+ run_command = (
292
+ remaining_args[:django_admin_index]
293
+ + [modified_script_path]
294
+ + remaining_args[django_admin_index + 1 :]
295
+ )
296
+ elif daphne_index is not None or granian_index is not None:
297
+ command_index = daphne_index if daphne_index is not None else granian_index
298
+ app_arg = find_application_arg(command_index, argv=remaining_args)
299
+ app_path_offset = remaining_args.index(app_arg)
300
+
301
+ if not app_arg:
302
+ print("Error: No application module specified for daphne or granian.")
303
+ sys.exit(1)
304
+
305
+ run_command = handle_framework_command(
306
+ command_index,
307
+ module_delimiter=":",
308
+ argv=remaining_args,
309
+ app_path_offset=app_path_offset,
310
+ **setup_params,
311
+ )
312
+ elif pserve_index is not None:
313
+ # Rebuild the command to use pserve
314
+ run_command = remaining_args
315
+ else:
316
+ # For all other cases, pass the command as-is
317
+ run_command = remaining_args
318
+
319
+ if SF_DEBUG:
320
+ print("Run command is now:", run_command, log=False)
321
+
322
+ env = os.environ.copy()
323
+
324
+ if METHOD == "subprocess.run":
325
+ result = subprocess.run(run_command, env=env)
326
+ sys.exit(result.returncode)
327
+ else:
328
+ os.execvpe(run_command[0], run_command, env)
329
+
330
+
331
+ def fire_main():
332
+ return Fire(main)
333
+
334
+
335
+ if __name__ == "__main__":
336
+ fire_main()
@@ -0,0 +1,10 @@
1
+ NONSESSION_APPLOGS = "nonsession-applogs"
2
+ SAILFISH_DEFAULT_GRAPHQL_ENDPOINT = "https://api-service.sailfishqa.com/graphql/"
3
+ SAILFISH_TRACING_HEADER = "X-Sf3-Rid"
4
+ FUNCSPAN_OVERRIDE_HEADER = "X-Sf3-FunctionSpanCaptureOverride"
5
+ PARENT_SESSION_ID_HEADER = "X-Sf4-Prid" # Parent Recording ID (stripped from outbound, internal only)
6
+
7
+ # Byte-string constants for fast header matching (avoid decode/dict overhead)
8
+ SAILFISH_TRACING_HEADER_BYTES = SAILFISH_TRACING_HEADER.encode("ascii").lower() # b"x-sf3-rid"
9
+ FUNCSPAN_OVERRIDE_HEADER_BYTES = FUNCSPAN_OVERRIDE_HEADER.encode("ascii").lower() # b"x-sf3-functionspancaptureoverride"
10
+ PARENT_SESSION_ID_HEADER_BYTES = PARENT_SESSION_ID_HEADER.encode("ascii").lower() # b"x-sf4-prid"
@@ -0,0 +1,304 @@
1
+ import inspect
2
+
3
+ # import linecache
4
+ import logging
5
+ import re
6
+ import sys
7
+ import threading
8
+ from types import FrameType, TracebackType
9
+ from typing import List, Optional
10
+
11
+ from . import app_config
12
+ from .env_vars import (
13
+ SAILFISH_EXCEPTION_FETCH_BEYOND_OFFENDER_DEPTH,
14
+ SAILFISH_EXCEPTION_FETCH_LOCALS_BEYOND_OFFENDER_DEPTH,
15
+ SF_DEBUG,
16
+ SF_DEBUG_TRACES,
17
+ )
18
+
19
+ # from .frame_tools import get_locals
20
+ from .interceptors import ExceptionInterceptor
21
+ from .local_env_detect import sf_is_local_dev_environment
22
+ from .thread_local import (
23
+ get_or_set_sf_trace_id,
24
+ has_handled_exception,
25
+ mark_exception_handled,
26
+ )
27
+ from .types import FrameInfo
28
+
29
+ REGEX_EVERYTHING_BEFORE_PYTHON_PACKAGE_NAME_WHEN_EXAMINING_FILENAME = (
30
+ r".*\/(site|dist)-packages\/"
31
+ )
32
+ REGEX_EVERYTHING_AFTER_NEXT_SLASH = r"\/.*"
33
+
34
+ logger = logging.getLogger(__name__)
35
+
36
+ # Profiling data structure to collect function call information
37
+ call_hierarchy = []
38
+
39
+ # Lock to manage thread-safe access to profiling data
40
+ call_hierarchy_lock = threading.Lock()
41
+
42
+ _original_excepthook = sys.excepthook
43
+
44
+
45
+ def custom_thread_excepthook(args):
46
+ sys.excepthook(args.exc_type, args.exc_value, args.exc_traceback)
47
+
48
+
49
+ def process_traceback_only(
50
+ exc_value: Exception, exc_traceback: TracebackType, was_caught: bool = True
51
+ ):
52
+ """
53
+ Processes only the traceback information for an exception.
54
+
55
+ Args:
56
+ exc_value (Exception): The exception value.
57
+ exc_traceback (TracebackType): The traceback object for the exception.
58
+ """
59
+ trace = extract_trace(exc_traceback)
60
+ except_interceptor = ExceptionInterceptor()
61
+ _, trace_id = get_or_set_sf_trace_id()
62
+ if SF_DEBUG:
63
+ print(
64
+ "process_traceback_only...SENDING DATA...do_send args=",
65
+ (str(exc_value), trace, trace_id, was_caught),
66
+ trace_id,
67
+ log=False,
68
+ )
69
+ except_interceptor.do_send(
70
+ (str(exc_value), trace, trace_id, was_caught, sf_is_local_dev_environment()[0]),
71
+ trace_id,
72
+ )
73
+
74
+
75
+ # Usage example in the custom_excepthook function:
76
+ def custom_excepthook(
77
+ exc_type, exc_value, exc_traceback: TracebackType, raise_original: bool = True
78
+ ):
79
+ if has_handled_exception(exc_value):
80
+ return
81
+ mark_exception_handled(exc_value)
82
+
83
+ if SF_DEBUG:
84
+ print(f"custom_excepthook -> raise_original={raise_original}", log=False)
85
+
86
+ transmit_exception(exc_type, exc_value, exc_traceback)
87
+
88
+ if raise_original:
89
+ _original_excepthook(exc_type, exc_value, exc_traceback)
90
+
91
+
92
+ def transmit_exception(exc_type, exc_value, exc_traceback, was_caught: bool = True):
93
+ """
94
+ Handles transmitting exception information based on profiling mode.
95
+
96
+ Args:
97
+ exc_type: The type of the exception.
98
+ exc_value: The exception instance.
99
+ exc_traceback: The traceback object for the exception.
100
+ """
101
+ if app_config._profiling_mode_enabled:
102
+ threading.Thread(
103
+ target=process_profiling_data,
104
+ args=(exc_type, exc_value, exc_traceback, was_caught),
105
+ ).start()
106
+ return
107
+ process_traceback_only(exc_value, exc_traceback, was_caught)
108
+
109
+
110
+ def profiling_function(frame: FrameType, event: str, arg):
111
+ if not app_config._profiling_mode_enabled:
112
+ return
113
+
114
+ if event in ["call", "return"]:
115
+ code = frame.f_code
116
+ function_name = code.co_name
117
+ filename = code.co_filename
118
+ line_no = frame.f_lineno
119
+ class_name = None
120
+
121
+ # Try to determine if we're inside a method of a class
122
+ if "self" in frame.f_locals:
123
+ class_name = type(frame.f_locals["self"]).__name__
124
+
125
+ call_info = {
126
+ "event": event,
127
+ "function": function_name,
128
+ "class": class_name,
129
+ "file": filename,
130
+ "line_no": line_no,
131
+ }
132
+
133
+ # Add profiling data to call hierarchy with a lock for thread safety
134
+ with call_hierarchy_lock:
135
+ if len(call_hierarchy) < app_config._profiling_max_depth:
136
+ call_hierarchy.append(call_info)
137
+
138
+
139
+ def process_profiling_data(exc_type, exc_value, exc_traceback, was_caught: bool = True):
140
+ # Extract the traceback and process detailed information
141
+ trace = extract_trace(exc_traceback)
142
+
143
+ # Process profiling data
144
+ with call_hierarchy_lock:
145
+ if SF_DEBUG and SF_DEBUG_TRACES:
146
+ print("=" * 40, log=False)
147
+ print("Printing the complete call hierarchy after exception...", log=False)
148
+ print("=" * 40, log=False)
149
+
150
+ for entry in call_hierarchy:
151
+ if entry["event"] == "call":
152
+ class_name = f"{entry['class']}." if entry["class"] else ""
153
+ if SF_DEBUG and SF_DEBUG_TRACES:
154
+ print(
155
+ f"Called: {class_name}{entry['function']} (File: {entry['file']}, Line: {entry['line_no']})",
156
+ log=False,
157
+ )
158
+ elif entry["event"] == "return":
159
+ if SF_DEBUG and SF_DEBUG_TRACES:
160
+ print(f"Return from: {entry['function']}", log=False)
161
+
162
+ if SF_DEBUG and SF_DEBUG_TRACES:
163
+ print("-" * 40, log=False)
164
+
165
+ # Clear profiling data after processing
166
+ call_hierarchy.clear()
167
+
168
+ # Send the extracted traceback data to the exception interceptor
169
+ except_interceptor = ExceptionInterceptor()
170
+ _, trace_id = get_or_set_sf_trace_id()
171
+ if SF_DEBUG:
172
+ print(
173
+ "process_traceback_only...SENDING DATA...do_send args=",
174
+ (str(exc_value), trace, trace_id, was_caught),
175
+ trace_id,
176
+ log=False,
177
+ )
178
+ except_interceptor.do_send(
179
+ (str(exc_value), trace, trace_id, was_caught, sf_is_local_dev_environment()[0]),
180
+ trace_id,
181
+ )
182
+
183
+
184
+ def safe_repr(value):
185
+ try:
186
+ return repr(value)
187
+ except Exception:
188
+ return f"<unrepresentable: {type(value).__name__}>"
189
+
190
+
191
+ def extract_trace(tb: Optional[TracebackType] = None) -> List[FrameInfo]:
192
+ tb_list = []
193
+
194
+ # Collect all traceback frames in natural order (most recent last)
195
+ while tb is not None:
196
+ tb_list.append(tb)
197
+ tb = tb.tb_next
198
+
199
+ if not tb_list:
200
+ return []
201
+
202
+ frame_stack = []
203
+
204
+ # Iterate through the traceback, capturing frame information
205
+ for tb in tb_list:
206
+ frame = tb.tb_frame
207
+ frame_info = inspect.getframeinfo(frame)
208
+ filename = frame_info.filename
209
+ function_name = frame_info.function
210
+ line_no = frame_info.lineno
211
+ code_context = (
212
+ frame_info.code_context[0].strip()
213
+ if frame_info.code_context
214
+ else "No code context"
215
+ )
216
+
217
+ frame_info_obj = FrameInfo(
218
+ file=filename,
219
+ line=line_no,
220
+ function=function_name,
221
+ code=code_context,
222
+ )
223
+
224
+ # Capture ALL local variables at each frame - no depth or package restrictions
225
+ frame_info_obj.locals = {k: safe_repr(v) for k, v in frame.f_locals.items()}
226
+
227
+ frame_stack.append(frame_info_obj)
228
+
229
+ frame_stack[-1].offender = True
230
+ return frame_stack
231
+
232
+
233
+ def start_profiling():
234
+ """Start profiling to collect call hierarchy information if profiling is enabled."""
235
+ from .env_vars import SF_DEBUG
236
+
237
+ if SF_DEBUG:
238
+ try:
239
+ print(
240
+ f"[[DEBUG]] start_profiling() called, _profiling_mode_enabled={app_config._profiling_mode_enabled}",
241
+ log=False,
242
+ )
243
+ except:
244
+ print(
245
+ f"[[DEBUG]] start_profiling() called, _profiling_mode_enabled={app_config._profiling_mode_enabled}"
246
+ )
247
+ if app_config._profiling_mode_enabled:
248
+ try:
249
+ print(
250
+ f"[[DEBUG]] Setting sys.setprofile to profiling_function, current={sys.getprofile()}",
251
+ log=False,
252
+ )
253
+ except:
254
+ print(
255
+ f"[[DEBUG]] Setting sys.setprofile to profiling_function, current={sys.getprofile()}"
256
+ )
257
+ sys.setprofile(profiling_function)
258
+ try:
259
+ print(
260
+ f"[[DEBUG]] After setting, sys.getprofile()={sys.getprofile()}",
261
+ log=False,
262
+ )
263
+ except:
264
+ print(f"[[DEBUG]] After setting, sys.getprofile()={sys.getprofile()}")
265
+
266
+
267
+ def stop_profiling():
268
+ """Stop profiling to prevent collecting more call hierarchy data."""
269
+ sys.setprofile(None)
270
+
271
+
272
+ def should_collect_local_variables_for_stack_item(filename: str) -> bool:
273
+ if "__all__" in app_config._site_and_dist_packages_to_collect_local_variables_on:
274
+ return True
275
+ return is_allowed_package(filename)
276
+
277
+
278
+ def is_installed_package(filename: str) -> bool:
279
+ return bool(
280
+ re.search(
281
+ REGEX_EVERYTHING_BEFORE_PYTHON_PACKAGE_NAME_WHEN_EXAMINING_FILENAME,
282
+ filename,
283
+ )
284
+ )
285
+
286
+
287
+ def get_package_name_from_site_or_dist_package(filename: str):
288
+ site_dist_packages_removed = re.sub(
289
+ REGEX_EVERYTHING_BEFORE_PYTHON_PACKAGE_NAME_WHEN_EXAMINING_FILENAME,
290
+ "",
291
+ filename,
292
+ )
293
+ package_installed_name = re.sub(
294
+ REGEX_EVERYTHING_AFTER_NEXT_SLASH, "", site_dist_packages_removed
295
+ )
296
+ return package_installed_name
297
+
298
+
299
+ def is_allowed_package(filename: str) -> bool:
300
+ if not is_installed_package(filename):
301
+ return True
302
+
303
+ package = get_package_name_from_site_or_dist_package(filename)
304
+ return package in app_config._site_and_dist_packages_to_collect_local_variables_on