sf-veritas 0.9.7__py3-none-any.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.
- sf_veritas/.gitignore +2 -0
- sf_veritas/__init__.py +4 -0
- sf_veritas/app_config.py +49 -0
- sf_veritas/cli.py +336 -0
- sf_veritas/constants.py +3 -0
- sf_veritas/custom_excepthook.py +285 -0
- sf_veritas/custom_log_handler.py +53 -0
- sf_veritas/custom_output_wrapper.py +107 -0
- sf_veritas/custom_print.py +34 -0
- sf_veritas/django_app.py +5 -0
- sf_veritas/env_vars.py +83 -0
- sf_veritas/exception_handling_middleware.py +18 -0
- sf_veritas/exception_metaclass.py +69 -0
- sf_veritas/frame_tools.py +112 -0
- sf_veritas/import_hook.py +62 -0
- sf_veritas/infra_details/__init__.py +3 -0
- sf_veritas/infra_details/get_infra_details.py +24 -0
- sf_veritas/infra_details/kubernetes/__init__.py +3 -0
- sf_veritas/infra_details/kubernetes/get_cluster_name.py +147 -0
- sf_veritas/infra_details/kubernetes/get_details.py +7 -0
- sf_veritas/infra_details/running_on/__init__.py +17 -0
- sf_veritas/infra_details/running_on/kubernetes.py +11 -0
- sf_veritas/interceptors.py +252 -0
- sf_veritas/local_env_detect.py +118 -0
- sf_veritas/package_metadata.py +6 -0
- sf_veritas/patches/__init__.py +0 -0
- sf_veritas/patches/concurrent_futures.py +19 -0
- sf_veritas/patches/constants.py +1 -0
- sf_veritas/patches/exceptions.py +82 -0
- sf_veritas/patches/multiprocessing.py +32 -0
- sf_veritas/patches/network_libraries/__init__.py +51 -0
- sf_veritas/patches/network_libraries/aiohttp.py +100 -0
- sf_veritas/patches/network_libraries/curl_cffi.py +93 -0
- sf_veritas/patches/network_libraries/http_client.py +64 -0
- sf_veritas/patches/network_libraries/httpcore.py +152 -0
- sf_veritas/patches/network_libraries/httplib2.py +76 -0
- sf_veritas/patches/network_libraries/httpx.py +123 -0
- sf_veritas/patches/network_libraries/niquests.py +192 -0
- sf_veritas/patches/network_libraries/pycurl.py +71 -0
- sf_veritas/patches/network_libraries/requests.py +187 -0
- sf_veritas/patches/network_libraries/tornado.py +139 -0
- sf_veritas/patches/network_libraries/treq.py +122 -0
- sf_veritas/patches/network_libraries/urllib_request.py +129 -0
- sf_veritas/patches/network_libraries/utils.py +101 -0
- sf_veritas/patches/os.py +17 -0
- sf_veritas/patches/threading.py +32 -0
- sf_veritas/patches/web_frameworks/__init__.py +45 -0
- sf_veritas/patches/web_frameworks/aiohttp.py +133 -0
- sf_veritas/patches/web_frameworks/async_websocket_consumer.py +132 -0
- sf_veritas/patches/web_frameworks/blacksheep.py +107 -0
- sf_veritas/patches/web_frameworks/bottle.py +142 -0
- sf_veritas/patches/web_frameworks/cherrypy.py +246 -0
- sf_veritas/patches/web_frameworks/django.py +307 -0
- sf_veritas/patches/web_frameworks/eve.py +138 -0
- sf_veritas/patches/web_frameworks/falcon.py +229 -0
- sf_veritas/patches/web_frameworks/fastapi.py +145 -0
- sf_veritas/patches/web_frameworks/flask.py +186 -0
- sf_veritas/patches/web_frameworks/klein.py +40 -0
- sf_veritas/patches/web_frameworks/litestar.py +217 -0
- sf_veritas/patches/web_frameworks/pyramid.py +89 -0
- sf_veritas/patches/web_frameworks/quart.py +155 -0
- sf_veritas/patches/web_frameworks/robyn.py +114 -0
- sf_veritas/patches/web_frameworks/sanic.py +120 -0
- sf_veritas/patches/web_frameworks/starlette.py +144 -0
- sf_veritas/patches/web_frameworks/strawberry.py +269 -0
- sf_veritas/patches/web_frameworks/tornado.py +129 -0
- sf_veritas/patches/web_frameworks/utils.py +55 -0
- sf_veritas/print_override.py +13 -0
- sf_veritas/regular_data_transmitter.py +358 -0
- sf_veritas/request_interceptor.py +399 -0
- sf_veritas/request_utils.py +104 -0
- sf_veritas/server_status.py +1 -0
- sf_veritas/shutdown_flag.py +11 -0
- sf_veritas/subprocess_startup.py +3 -0
- sf_veritas/test_cli.py +145 -0
- sf_veritas/thread_local.py +436 -0
- sf_veritas/timeutil.py +114 -0
- sf_veritas/transmit_exception_to_sailfish.py +28 -0
- sf_veritas/transmitter.py +58 -0
- sf_veritas/types.py +44 -0
- sf_veritas/unified_interceptor.py +323 -0
- sf_veritas/utils.py +39 -0
- sf_veritas-0.9.7.dist-info/METADATA +83 -0
- sf_veritas-0.9.7.dist-info/RECORD +86 -0
- sf_veritas-0.9.7.dist-info/WHEEL +4 -0
- sf_veritas-0.9.7.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from . import app_config
|
|
5
|
+
from .env_vars import PRINT_CONFIGURATION_STATUSES, SF_DEBUG
|
|
6
|
+
from .interceptors import LogInterceptor
|
|
7
|
+
from .thread_local import get_or_set_sf_trace_id
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CustomLogHandler(logging.Handler, LogInterceptor):
|
|
11
|
+
def __init__(self):
|
|
12
|
+
logging.Handler.__init__(self)
|
|
13
|
+
LogInterceptor.__init__(self, api_key=app_config._sailfish_api_key)
|
|
14
|
+
if PRINT_CONFIGURATION_STATUSES:
|
|
15
|
+
print("Intercepting log statements")
|
|
16
|
+
|
|
17
|
+
def emit(self, record, trace_id: Optional[str] = None):
|
|
18
|
+
if isinstance(record, logging.LogRecord):
|
|
19
|
+
try:
|
|
20
|
+
log_entry = self.format(record)
|
|
21
|
+
log_level = record.levelname
|
|
22
|
+
|
|
23
|
+
if SF_DEBUG:
|
|
24
|
+
print(
|
|
25
|
+
"[[DEBUG custom_log_handler]]",
|
|
26
|
+
f"trace_id={trace_id}",
|
|
27
|
+
f"[[{log_level}]]",
|
|
28
|
+
log_entry,
|
|
29
|
+
"[[/DEBUG]]",
|
|
30
|
+
log=False,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
if SF_DEBUG:
|
|
34
|
+
print(
|
|
35
|
+
"CustomLogHandler...SENDING DATA...do_send args=",
|
|
36
|
+
(log_level, log_entry, trace_id),
|
|
37
|
+
trace_id,
|
|
38
|
+
log=False,
|
|
39
|
+
)
|
|
40
|
+
_, trace_id = get_or_set_sf_trace_id(trace_id)
|
|
41
|
+
if SF_DEBUG:
|
|
42
|
+
print(
|
|
43
|
+
"CustomLogHandler...SENDING DATA...do_send args=",
|
|
44
|
+
(log_level, log_entry, trace_id),
|
|
45
|
+
trace_id,
|
|
46
|
+
log=False,
|
|
47
|
+
)
|
|
48
|
+
self.do_send((log_level, log_entry, trace_id), trace_id)
|
|
49
|
+
|
|
50
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
|
51
|
+
# TODO - Sibyl post-launch - disable this print??
|
|
52
|
+
print("There's an error when emitting!!!!!\n", e)
|
|
53
|
+
self.handleError(record)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import sys
|
|
3
|
+
import threading
|
|
4
|
+
|
|
5
|
+
# from ._to_be_deleted__custom_output_wrapper_stderr import CustomOutputWrapperStdErr
|
|
6
|
+
from .env_vars import PRINT_CONFIGURATION_STATUSES, SF_DEBUG
|
|
7
|
+
from .interceptors import PrintInterceptor
|
|
8
|
+
from .thread_local import _thread_locals, get_or_set_sf_trace_id
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CustomOutputWrapper:
|
|
12
|
+
def __init__(self, original):
|
|
13
|
+
self.original = original
|
|
14
|
+
self.print_interceptor = PrintInterceptor()
|
|
15
|
+
self._lock = threading.Lock()
|
|
16
|
+
|
|
17
|
+
def write(self, msg):
|
|
18
|
+
if (
|
|
19
|
+
hasattr(_thread_locals, "reentrancy_guard_logging_active")
|
|
20
|
+
and _thread_locals.reentrancy_guard_logging_active
|
|
21
|
+
):
|
|
22
|
+
self.original.write(msg)
|
|
23
|
+
self.original.flush()
|
|
24
|
+
return
|
|
25
|
+
|
|
26
|
+
with self._lock:
|
|
27
|
+
# Add custom tags if SF_DEBUG is enabled
|
|
28
|
+
msg_with_tags = (
|
|
29
|
+
f"[[CUSTOM-OUTPUT]] {msg} [[/CUSTOM-OUTPUT]]\n" if SF_DEBUG else msg
|
|
30
|
+
)
|
|
31
|
+
self.original.write(msg_with_tags)
|
|
32
|
+
self.original.flush()
|
|
33
|
+
|
|
34
|
+
# Intercept the message and log it
|
|
35
|
+
# self.print_interceptor.send(msg_with_tags)
|
|
36
|
+
pattern = r"HTTP\s(POST|GET)\s(\/healthz|\/graphql\/)\s.*"
|
|
37
|
+
if re.match(pattern, msg):
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
_, trace_id = get_or_set_sf_trace_id()
|
|
41
|
+
if SF_DEBUG:
|
|
42
|
+
print(
|
|
43
|
+
"CustomOutputWrapper...SENDING DATA...args=",
|
|
44
|
+
(msg_with_tags, trace_id),
|
|
45
|
+
trace_id,
|
|
46
|
+
log=False,
|
|
47
|
+
)
|
|
48
|
+
self.print_interceptor.do_send((msg_with_tags, trace_id), trace_id)
|
|
49
|
+
|
|
50
|
+
def __getattr__(self, attr):
|
|
51
|
+
return getattr(self.original, attr)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def setup_custom_output_wrappers():
|
|
55
|
+
if PRINT_CONFIGURATION_STATUSES:
|
|
56
|
+
print("setup_custom_output_wrappers")
|
|
57
|
+
sys.stdout = CustomOutputWrapper(sys.stdout)
|
|
58
|
+
# sys.stderr = CustomOutputWrapperStdErr(sys.stderr) # TODO - uncomment
|
|
59
|
+
if PRINT_CONFIGURATION_STATUSES:
|
|
60
|
+
print("setup_custom_output_wrappers...DONE")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_custom_output_wrapper_django():
|
|
64
|
+
from django.core.management.base import OutputWrapper
|
|
65
|
+
|
|
66
|
+
class CustomOutputWrapperDjango(OutputWrapper):
|
|
67
|
+
def __init__(self, *args, **kwargs):
|
|
68
|
+
super().__init__(*args, **kwargs)
|
|
69
|
+
self.print_interceptor = PrintInterceptor()
|
|
70
|
+
|
|
71
|
+
def write(self, msg="", style_func=None, ending=None):
|
|
72
|
+
if (
|
|
73
|
+
hasattr(_thread_locals, "reentrancy_guard_logging_active")
|
|
74
|
+
and _thread_locals.reentrancy_guard_logging_active
|
|
75
|
+
):
|
|
76
|
+
super().write(msg, style_func, ending)
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
# Add custom tags if SF_DEBUG is enabled
|
|
80
|
+
if style_func is None:
|
|
81
|
+
style_func = lambda x: (
|
|
82
|
+
f"[[CUSTOM-OUTPUT-DJANGO]] {x} [[/CUSTOM-OUTPUT-DJANGO]]\n"
|
|
83
|
+
if SF_DEBUG
|
|
84
|
+
else x
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Use the original write method
|
|
88
|
+
super().write(msg, style_func, ending)
|
|
89
|
+
|
|
90
|
+
pattern = r"HTTP\s(POST|GET)\s(\/healthz|\/graphql\/)\s.*"
|
|
91
|
+
if re.match(pattern, msg):
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
# Intercept the message and log it
|
|
95
|
+
message = msg if ending is None else msg + ending
|
|
96
|
+
|
|
97
|
+
_, trace_id = get_or_set_sf_trace_id()
|
|
98
|
+
if SF_DEBUG:
|
|
99
|
+
print(
|
|
100
|
+
"get_custom_output_wrapper_django...SENDING DATA...",
|
|
101
|
+
message,
|
|
102
|
+
trace_id,
|
|
103
|
+
log=False,
|
|
104
|
+
)
|
|
105
|
+
self.print_interceptor.do_send((message, trace_id), trace_id)
|
|
106
|
+
|
|
107
|
+
return CustomOutputWrapperDjango
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import builtins
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from .env_vars import SF_DEBUG
|
|
5
|
+
from .thread_local import (
|
|
6
|
+
_thread_locals,
|
|
7
|
+
activate_reentrancy_guards_sys_stdout,
|
|
8
|
+
get_or_set_sf_trace_id,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def custom_print(*args, log=True, **kwargs):
|
|
13
|
+
"""
|
|
14
|
+
Custom print function to intercept print statements.
|
|
15
|
+
"""
|
|
16
|
+
# Prepare the message to print
|
|
17
|
+
print_args = ("[[CUSTOM-PRINT]]", *args, "[[/CUSTOM-PRINT]]") if SF_DEBUG else args
|
|
18
|
+
activate_reentrancy_guards_sys_stdout()
|
|
19
|
+
builtins._original_print( # pylint: disable=protected-access; This is added by our interceptor
|
|
20
|
+
*print_args, file=sys.__stdout__, **kwargs
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
message = " ".join(map(str, args))
|
|
24
|
+
# If log is True, send the message to the logging system
|
|
25
|
+
if log and message.strip():
|
|
26
|
+
_, trace_id = get_or_set_sf_trace_id()
|
|
27
|
+
if SF_DEBUG:
|
|
28
|
+
print(
|
|
29
|
+
"UnifiedInterceptor.custom_print...SENDING DATA...args=",
|
|
30
|
+
(message, trace_id),
|
|
31
|
+
trace_id,
|
|
32
|
+
log=False,
|
|
33
|
+
)
|
|
34
|
+
sys.stdout.print_interceptor.do_send((message, trace_id), trace_id)
|
sf_veritas/django_app.py
ADDED
sf_veritas/env_vars.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from .utils import strtobool
|
|
5
|
+
|
|
6
|
+
LITE_DEBUGGING = strtobool(os.getenv("LITE_DEBUGGING", "false"))
|
|
7
|
+
SF_DEBUG = strtobool(os.getenv("SF_DEBUG", "false"))
|
|
8
|
+
SF_DEBUG_TRACES = strtobool(os.getenv("SF_DEBUG_TRACES", "false"))
|
|
9
|
+
SF_INTERNAL = strtobool(os.getenv("SF_INTERNAL", "false"))
|
|
10
|
+
IS_SAILFISH_COLLECTOR = strtobool(os.getenv("IS_SAILFISH_COLLECTOR", "false"))
|
|
11
|
+
PRINT_CONFIGURATION_STATUSES = strtobool(
|
|
12
|
+
os.getenv("PRINT_CONFIGURATION_STATUSES", "false")
|
|
13
|
+
)
|
|
14
|
+
# Exception Tools
|
|
15
|
+
SAILFISH_EXCEPTION_LOCALS_HIDE_SELF = strtobool(
|
|
16
|
+
os.getenv("SAILFISH_EXCEPTION_LOCALS_HIDE_SELF", "true")
|
|
17
|
+
)
|
|
18
|
+
SAILFISH_EXCEPTION_LOCALS_HIDE_DUNDER = strtobool(
|
|
19
|
+
os.getenv("SAILFISH_EXCEPTION_LOCALS_HIDE_DUNDER", "true")
|
|
20
|
+
)
|
|
21
|
+
SAILFISH_EXCEPTION_LOCALS_HIDE_SUNDER = strtobool(
|
|
22
|
+
os.getenv("SAILFISH_EXCEPTION_LOCALS_HIDE_SUNDER", "true")
|
|
23
|
+
)
|
|
24
|
+
SAILFISH_EXCEPTION_STACK_DEPTH_LOCALS = int(
|
|
25
|
+
os.getenv("SAILFISH_EXCEPTION_STACK_DEPTH_LOCALS", "5")
|
|
26
|
+
)
|
|
27
|
+
SAILFISH_EXCEPTION_STACK_DEPTH_CODE_TRACE_DEPTH = os.getenv(
|
|
28
|
+
"SAILFISH_EXCEPTION_STACK_DEPTH_CODE_TRACE_DEPTH", "full"
|
|
29
|
+
)
|
|
30
|
+
SAILFISH_EXCEPTION_STACK_DEPTH_CODE_TRACE_TYPE = os.getenv(
|
|
31
|
+
"SAILFISH_EXCEPTION_STACK_DEPTH_CODE_TRACE_TYPE", "line"
|
|
32
|
+
)
|
|
33
|
+
SAILFISH_EXCEPTION_STACK_DEPTH_CODE_TRACE_TYPE_AT_OFFENSIVE_CALL = os.getenv(
|
|
34
|
+
"SAILFISH_EXCEPTION_STACK_DEPTH_CODE_TRACE_TYPE_AT_OFFENSIVE_CALL", "line"
|
|
35
|
+
)
|
|
36
|
+
SAILFISH_EXCEPTION_STACK_DEPTH_CODE_VALUES_AT_FULL_DEPTH_EXCEPTION = strtobool(
|
|
37
|
+
os.getenv(
|
|
38
|
+
"SAILFISH_EXCEPTION_STACK_DEPTH_CODE_VALUES_AT_FULL_DEPTH_EXCEPTION", "false"
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
SAILFISH_EXCEPTION_LOCALS_TYPES_TO_IGNORE = os.getenv(
|
|
42
|
+
"SAILFISH_EXCEPTION_LOCALS_TYPES_TO_IGNORE",
|
|
43
|
+
"strawberry.types.info.Info,graphql.type.definition.GraphQLResolveInfo,strawberry.field.StrawberryField",
|
|
44
|
+
)
|
|
45
|
+
SAILFISH_EXCEPTION_FETCH_BEYOND_OFFENDER_DEPTH = int(
|
|
46
|
+
os.getenv("SAILFISH_EXCEPTION_FETCH_BEYOND_OFFENDER_DEPTH", "3")
|
|
47
|
+
)
|
|
48
|
+
SAILFISH_EXCEPTION_FETCH_LOCALS_BEYOND_OFFENDER_DEPTH = int(
|
|
49
|
+
os.getenv(
|
|
50
|
+
"SAILFISH_EXCEPTION_FETCH_LOCALS_BEYOND_OFFENDER_DEPTH", "5"
|
|
51
|
+
) # Sibyl launch - lower this
|
|
52
|
+
)
|
|
53
|
+
SAILFISH_EXCEPTION_FETCH_ABOVE_OFFENDER_DEPTH = int(
|
|
54
|
+
os.getenv(
|
|
55
|
+
"SAILFISH_EXCEPTION_FETCH_ABOVE_OFFENDER_DEPTH", "3"
|
|
56
|
+
) # Sibyl launch - lower this
|
|
57
|
+
)
|
|
58
|
+
SAILFISH_EXCEPTION_FETCH_LOCALS_ABOVE_OFFENDER_DEPTH = int(
|
|
59
|
+
os.getenv(
|
|
60
|
+
"SAILFISH_EXCEPTION_FETCH_LOCALS_ABOVE_OFFENDER_DEPTH", "-1"
|
|
61
|
+
) # Sibyl launch - lower this
|
|
62
|
+
)
|
|
63
|
+
SAILFISH_EXCEPTION_FETCH_ABOVE_OFFENDER_INCLUDE_INSTALLED_PACKAGES = strtobool(
|
|
64
|
+
os.getenv(
|
|
65
|
+
"SAILFISH_EXCEPTION_FETCH_ABOVE_OFFENDER_INCLUDE_INSTALLED_PACKAGES", "false"
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_log_level():
|
|
71
|
+
if LOG_LEVEL_ENV_VAR == "DEBUG":
|
|
72
|
+
return logging.DEBUG
|
|
73
|
+
if LOG_LEVEL_ENV_VAR == "INFO":
|
|
74
|
+
return logging.INFO
|
|
75
|
+
if LOG_LEVEL_ENV_VAR == "WARN":
|
|
76
|
+
return logging.WARN
|
|
77
|
+
if LOG_LEVEL_ENV_VAR == "ERROR":
|
|
78
|
+
return logging.ERROR
|
|
79
|
+
return logging.CRITICAL
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
LOG_LEVEL_ENV_VAR = os.getenv("LOG_LEVEL", "INFO")
|
|
83
|
+
LOG_LEVEL = get_log_level()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
from .custom_excepthook import custom_excepthook
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CustomExceptionMiddleware:
|
|
7
|
+
def __init__(self, app):
|
|
8
|
+
self.app = app
|
|
9
|
+
|
|
10
|
+
async def __call__(self, scope, receive, send):
|
|
11
|
+
try:
|
|
12
|
+
await self.app(scope, receive, send)
|
|
13
|
+
except Exception as exc:
|
|
14
|
+
exc_type, exc_value, exc_traceback = sys.exc_info()
|
|
15
|
+
custom_excepthook(exc_type, exc_value, exc_traceback)
|
|
16
|
+
|
|
17
|
+
def __getattr__(self, attr):
|
|
18
|
+
return getattr(self.original, attr)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from abc import ABCMeta
|
|
2
|
+
|
|
3
|
+
from .custom_excepthook import transmit_exception
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ExceptionMeta(ABCMeta):
|
|
7
|
+
"""
|
|
8
|
+
Metaclass to add `capture_even_if_caught` functionality to exceptions
|
|
9
|
+
and provide a `transmit_to_sailfish` method for all exceptions.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __new__(cls, name, bases, dct):
|
|
13
|
+
# Wrap or define the `__init__` method
|
|
14
|
+
if "__init__" in dct:
|
|
15
|
+
original_init = dct["__init__"]
|
|
16
|
+
|
|
17
|
+
def wrapped_init(self, *args, **kwargs):
|
|
18
|
+
# Add the `capture_even_if_caught` attribute
|
|
19
|
+
self.capture_even_if_caught = kwargs.pop(
|
|
20
|
+
"capture_even_if_caught", False
|
|
21
|
+
)
|
|
22
|
+
self._handled = False # Ensure `_handled` is initialized
|
|
23
|
+
original_init(self, *args, **kwargs)
|
|
24
|
+
|
|
25
|
+
dct["__init__"] = wrapped_init
|
|
26
|
+
else:
|
|
27
|
+
|
|
28
|
+
def default_init(self, *args, **kwargs):
|
|
29
|
+
# Add the `capture_even_if_caught` attribute
|
|
30
|
+
self.capture_even_if_caught = kwargs.pop(
|
|
31
|
+
"capture_even_if_caught", False
|
|
32
|
+
)
|
|
33
|
+
self._handled = False # Ensure `_handled` is initialized
|
|
34
|
+
super(Exception, self).__init__(*args, **kwargs)
|
|
35
|
+
|
|
36
|
+
dct["__init__"] = default_init
|
|
37
|
+
|
|
38
|
+
# Add `transmit_to_sailfish` method to all exceptions
|
|
39
|
+
def transmit_to_sailfish(self, was_caught: bool = False):
|
|
40
|
+
"""
|
|
41
|
+
Transmit this exception to Sailfish.
|
|
42
|
+
"""
|
|
43
|
+
if not getattr(self, "_handled", False):
|
|
44
|
+
transmit_exception(type(self), self, self.__traceback__, was_caught)
|
|
45
|
+
setattr(
|
|
46
|
+
self, "_handled", True
|
|
47
|
+
) # Mark as handled to prevent duplication
|
|
48
|
+
|
|
49
|
+
dct["transmit_to_sailfish"] = transmit_to_sailfish
|
|
50
|
+
|
|
51
|
+
return super().__new__(cls, name, bases, dct)
|
|
52
|
+
|
|
53
|
+
def __call__(cls, *args, **kwargs):
|
|
54
|
+
"""
|
|
55
|
+
Intercept exception instantiation to handle `capture_even_if_caught`.
|
|
56
|
+
"""
|
|
57
|
+
instance = super().__call__(*args, **kwargs)
|
|
58
|
+
# Automatically handle `capture_even_if_caught` exceptions
|
|
59
|
+
if getattr(instance, "capture_even_if_caught", False):
|
|
60
|
+
instance.transmit_to_sailfish()
|
|
61
|
+
return instance
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class PatchedException(Exception, metaclass=ExceptionMeta):
|
|
65
|
+
"""
|
|
66
|
+
A patched version of the built-in Exception class with universal interception capabilities.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
pass
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from typing import Any, Iterable, List
|
|
3
|
+
|
|
4
|
+
from .env_vars import (
|
|
5
|
+
SAILFISH_EXCEPTION_LOCALS_HIDE_DUNDER,
|
|
6
|
+
SAILFISH_EXCEPTION_LOCALS_HIDE_SELF,
|
|
7
|
+
SAILFISH_EXCEPTION_LOCALS_HIDE_SUNDER,
|
|
8
|
+
SAILFISH_EXCEPTION_LOCALS_TYPES_TO_IGNORE,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def import_type(module_name: str, type_name: str):
|
|
13
|
+
try:
|
|
14
|
+
exec( # pylint: disable=exec-used
|
|
15
|
+
f"from {module_name} import {type_name}",
|
|
16
|
+
globals(),
|
|
17
|
+
)
|
|
18
|
+
return globals().get(type_name, None)
|
|
19
|
+
except ImportError:
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_types_from_str(types_str: str) -> List[type]:
|
|
24
|
+
types_list = []
|
|
25
|
+
if types_str:
|
|
26
|
+
for type_path in types_str.split(","):
|
|
27
|
+
type_path_fixed = type_path.replace(" ", "")
|
|
28
|
+
module_name, type_name = type_path_fixed.rsplit(".", 1)
|
|
29
|
+
type_obj = import_type(module_name, type_name)
|
|
30
|
+
if type_obj:
|
|
31
|
+
types_list.append(type_obj)
|
|
32
|
+
return types_list
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
EXCEPTION_LOCALS_TYPES_TO_IGNORE = get_types_from_str(
|
|
36
|
+
SAILFISH_EXCEPTION_LOCALS_TYPES_TO_IGNORE
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_current_frame():
|
|
41
|
+
return sys._getframe(1) # pylint: disable=protected-access
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def value_type_to_be_ignored(value: Any) -> bool:
|
|
45
|
+
return any(
|
|
46
|
+
isinstance(value, type_obj) for type_obj in EXCEPTION_LOCALS_TYPES_TO_IGNORE
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def key_is_str_to_be_ignored(
|
|
51
|
+
key: Any, locals_hide_self: bool, locals_hide_dunder: bool, locals_hide_sunder: bool
|
|
52
|
+
) -> bool:
|
|
53
|
+
return (
|
|
54
|
+
(locals_hide_self and key == "self")
|
|
55
|
+
or (locals_hide_dunder and key.startswith("__"))
|
|
56
|
+
or (locals_hide_sunder and key.startswith("_"))
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def filter_locals(
|
|
61
|
+
iter_locals: Iterable[tuple[str, object]],
|
|
62
|
+
locals_hide_self: bool,
|
|
63
|
+
locals_hide_dunder: bool,
|
|
64
|
+
locals_hide_sunder: bool,
|
|
65
|
+
) -> Iterable[tuple[str, object]]:
|
|
66
|
+
for key, value in iter_locals:
|
|
67
|
+
if not isinstance(key, str):
|
|
68
|
+
if value_type_to_be_ignored(value):
|
|
69
|
+
continue
|
|
70
|
+
else:
|
|
71
|
+
if key_is_str_to_be_ignored(
|
|
72
|
+
key, locals_hide_self, locals_hide_dunder, locals_hide_sunder
|
|
73
|
+
):
|
|
74
|
+
continue
|
|
75
|
+
if value_type_to_be_ignored(value):
|
|
76
|
+
continue
|
|
77
|
+
if key in ("args", "field_args") and isinstance(value, list):
|
|
78
|
+
value = list(
|
|
79
|
+
filter_locals(
|
|
80
|
+
enumerate(value),
|
|
81
|
+
locals_hide_self,
|
|
82
|
+
locals_hide_dunder,
|
|
83
|
+
locals_hide_sunder,
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
if key in ("kwargs", "field_kwargs") and isinstance(value, dict):
|
|
87
|
+
value = dict(
|
|
88
|
+
filter_locals(
|
|
89
|
+
value.items(),
|
|
90
|
+
locals_hide_self,
|
|
91
|
+
locals_hide_dunder,
|
|
92
|
+
locals_hide_sunder,
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
yield key, value
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def get_locals(
|
|
99
|
+
iter_locals: Iterable[tuple[str, object]],
|
|
100
|
+
locals_hide_self: bool = SAILFISH_EXCEPTION_LOCALS_HIDE_SELF,
|
|
101
|
+
locals_hide_dunder: bool = SAILFISH_EXCEPTION_LOCALS_HIDE_DUNDER,
|
|
102
|
+
locals_hide_sunder: bool = SAILFISH_EXCEPTION_LOCALS_HIDE_SUNDER,
|
|
103
|
+
) -> Iterable[tuple[str, object]]:
|
|
104
|
+
"""Extract locals from an iterator of key pairs."""
|
|
105
|
+
if not (locals_hide_dunder or locals_hide_sunder):
|
|
106
|
+
yield from iter_locals
|
|
107
|
+
return
|
|
108
|
+
iter_locals_filtered = filter_locals(
|
|
109
|
+
iter_locals, locals_hide_self, locals_hide_dunder, locals_hide_sunder
|
|
110
|
+
)
|
|
111
|
+
for key, value in iter_locals_filtered:
|
|
112
|
+
yield key, value
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import importlib.util
|
|
3
|
+
import sys
|
|
4
|
+
from importlib.abc import MetaPathFinder
|
|
5
|
+
|
|
6
|
+
from .custom_excepthook import custom_excepthook
|
|
7
|
+
from .custom_output_wrapper import get_custom_output_wrapper_django
|
|
8
|
+
from .env_vars import PRINT_CONFIGURATION_STATUSES
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ImportInterceptor:
|
|
12
|
+
def __init__(self, module_name, callback):
|
|
13
|
+
self.module_name = module_name
|
|
14
|
+
self.callback = callback
|
|
15
|
+
|
|
16
|
+
def find_spec(self, fullname, path, target=None):
|
|
17
|
+
if fullname == self.module_name:
|
|
18
|
+
self.callback()
|
|
19
|
+
# Remove the interceptor once the callback has been called
|
|
20
|
+
sys.meta_path.remove(self)
|
|
21
|
+
return importlib.util.find_spec(fullname, path)
|
|
22
|
+
return None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def setup_django_import_hook():
|
|
26
|
+
interceptor = ImportInterceptor(
|
|
27
|
+
"django.core.management", get_custom_output_wrapper_django
|
|
28
|
+
)
|
|
29
|
+
sys.meta_path.insert(0, interceptor)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def wrap_excepthook(old_excepthook):
|
|
33
|
+
def custom_wrapper_excepthook(exc_type, exc_value, exc_traceback):
|
|
34
|
+
# Call your custom exception hook
|
|
35
|
+
custom_excepthook(exc_type, exc_value, exc_traceback)
|
|
36
|
+
# Call the original exception hook (which might be Sentry's)
|
|
37
|
+
if old_excepthook is not None:
|
|
38
|
+
old_excepthook(exc_type, exc_value, exc_traceback)
|
|
39
|
+
|
|
40
|
+
return custom_wrapper_excepthook
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def set_excepthook():
|
|
44
|
+
# Wrap the current excepthook
|
|
45
|
+
current_excepthook = sys.excepthook
|
|
46
|
+
wrapped_excepthook = wrap_excepthook(current_excepthook)
|
|
47
|
+
sys.excepthook = wrapped_excepthook
|
|
48
|
+
if PRINT_CONFIGURATION_STATUSES:
|
|
49
|
+
print("Patched sys.excepthook for custom exception handling.")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ExcepthookImportHook(MetaPathFinder):
|
|
53
|
+
def find_spec(self, fullname, path, target=None):
|
|
54
|
+
print("fullname", fullname, log=False)
|
|
55
|
+
if fullname in sys.modules:
|
|
56
|
+
print("\t> fullname in sys.modules", log=False)
|
|
57
|
+
set_excepthook()
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def install_import_hook():
|
|
62
|
+
sys.meta_path.insert(0, ExcepthookImportHook())
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Any, Dict
|
|
3
|
+
|
|
4
|
+
from .kubernetes import get_details as get_kubernetes_details
|
|
5
|
+
from .running_on import System, running_on
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class InfraDetails:
|
|
10
|
+
system: System
|
|
11
|
+
details: Dict[str, Any]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_running_on_details(system: System) -> Dict[str, Any]:
|
|
15
|
+
if system == System.KUBERNETES:
|
|
16
|
+
return get_kubernetes_details()
|
|
17
|
+
if system == System.UNKNOWN:
|
|
18
|
+
return {}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_infra_details() -> InfraDetails:
|
|
22
|
+
system = running_on()
|
|
23
|
+
details = get_running_on_details(system)
|
|
24
|
+
return InfraDetails(system=system, details=details)
|