2025-12-01

This commit is contained in:
2026-03-17 14:58:51 -06:00
parent 183e865f8b
commit 4b82b57113
6846 changed files with 954887 additions and 162606 deletions
@@ -59,7 +59,7 @@ if TYPE_CHECKING:
from gevent.hub import Hub
from sentry_sdk._types import Event, ExcInfo
from sentry_sdk._types import Event, ExcInfo, Log, Hint, Metric
P = ParamSpec("P")
R = TypeVar("R")
@@ -77,6 +77,15 @@ BASE64_ALPHABET = re.compile(r"^[a-zA-Z0-9/+=]*$")
FALSY_ENV_VALUES = frozenset(("false", "f", "n", "no", "off", "0"))
TRUTHY_ENV_VALUES = frozenset(("true", "t", "y", "yes", "on", "1"))
MAX_STACK_FRAMES = 2000
"""Maximum number of stack frames to send to Sentry.
If we have more than this number of stack frames, we will stop processing
the stacktrace to avoid getting stuck in a long-lasting loop. This value
exceeds the default sys.getrecursionlimit() of 1000, so users will only
be affected by this limit if they have a custom recursion limit.
"""
def env_to_bool(value, *, strict=False):
# type: (Any, Optional[bool]) -> bool | None
@@ -380,7 +389,8 @@ class Auth:
self.client = client
def get_api_url(
self, type=EndpointType.ENVELOPE # type: EndpointType
self,
type=EndpointType.ENVELOPE, # type: EndpointType
):
# type: (...) -> str
"""Returns the API url for storing events."""
@@ -582,9 +592,14 @@ def serialize_frame(
if tb_lineno is None:
tb_lineno = frame.f_lineno
try:
os_abs_path = os.path.abspath(abs_path) if abs_path else None
except Exception:
os_abs_path = None
rv = {
"filename": filename_for_module(module, abs_path) or None,
"abs_path": os.path.abspath(abs_path) if abs_path else None,
"abs_path": os_abs_path,
"function": function or "<unknown>",
"module": module,
"lineno": tb_lineno,
@@ -732,10 +747,23 @@ def single_exception_from_error_tuple(
max_value_length=max_value_length,
custom_repr=custom_repr,
)
for tb in iter_stacks(tb)
# Process at most MAX_STACK_FRAMES + 1 frames, to avoid hanging on
# processing a super-long stacktrace.
for tb, _ in zip(iter_stacks(tb), range(MAX_STACK_FRAMES + 1))
] # type: List[Dict[str, Any]]
if frames:
if len(frames) > MAX_STACK_FRAMES:
# If we have more frames than the limit, we remove the stacktrace completely.
# We don't trim the stacktrace here because we have not processed the whole
# thing (see above, we stop at MAX_STACK_FRAMES + 1). Normally, Relay would
# intelligently trim by removing frames in the middle of the stacktrace, but
# since we don't have the whole stacktrace, we can't do that. Instead, we
# drop the entire stacktrace.
exception_value["stacktrace"] = AnnotatedValue.removed_because_over_size_limit(
value=None
)
elif frames:
if not full_stack:
new_frames = frames
else:
@@ -823,7 +851,9 @@ def exceptions_from_error(
parent_id = exception_id
exception_id += 1
should_supress_context = hasattr(exc_value, "__suppress_context__") and exc_value.__suppress_context__ # type: ignore
should_supress_context = (
hasattr(exc_value, "__suppress_context__") and exc_value.__suppress_context__ # type: ignore
)
if should_supress_context:
# Add direct cause.
# The field `__cause__` is set when raised with the exception (using the `from` keyword).
@@ -941,7 +971,7 @@ def to_string(value):
def iter_event_stacktraces(event):
# type: (Event) -> Iterator[Dict[str, Any]]
# type: (Event) -> Iterator[Annotated[Dict[str, Any]]]
if "stacktrace" in event:
yield event["stacktrace"]
if "threads" in event:
@@ -950,13 +980,16 @@ def iter_event_stacktraces(event):
yield thread["stacktrace"]
if "exception" in event:
for exception in event["exception"].get("values") or ():
if "stacktrace" in exception:
if isinstance(exception, dict) and "stacktrace" in exception:
yield exception["stacktrace"]
def iter_event_frames(event):
# type: (Event) -> Iterator[Dict[str, Any]]
for stacktrace in iter_event_stacktraces(event):
if isinstance(stacktrace, AnnotatedValue):
stacktrace = stacktrace.value or {}
for frame in stacktrace.get("frames") or ():
yield frame
@@ -964,6 +997,9 @@ def iter_event_frames(event):
def handle_in_app(event, in_app_exclude=None, in_app_include=None, project_root=None):
# type: (Event, Optional[List[str]], Optional[List[str]], Optional[str]) -> Event
for stacktrace in iter_event_stacktraces(event):
if isinstance(stacktrace, AnnotatedValue):
stacktrace = stacktrace.value or {}
set_in_app_in_frames(
stacktrace.get("frames"),
in_app_exclude=in_app_exclude,
@@ -1448,17 +1484,37 @@ class TimeoutThread(threading.Thread):
waiting_time and raises a custom ServerlessTimeout exception.
"""
def __init__(self, waiting_time, configured_timeout):
# type: (float, int) -> None
def __init__(
self, waiting_time, configured_timeout, isolation_scope=None, current_scope=None
):
# type: (float, int, Optional[sentry_sdk.Scope], Optional[sentry_sdk.Scope]) -> None
threading.Thread.__init__(self)
self.waiting_time = waiting_time
self.configured_timeout = configured_timeout
self.isolation_scope = isolation_scope
self.current_scope = current_scope
self._stop_event = threading.Event()
def stop(self):
# type: () -> None
self._stop_event.set()
def _capture_exception(self):
# type: () -> ExcInfo
exc_info = sys.exc_info()
client = sentry_sdk.get_client()
event, hint = event_from_exception(
exc_info,
client_options=client.options,
mechanism={"type": "threading", "handled": False},
)
sentry_sdk.capture_event(event, hint=hint)
return exc_info
def run(self):
# type: () -> None
@@ -1474,6 +1530,18 @@ class TimeoutThread(threading.Thread):
integer_configured_timeout = integer_configured_timeout + 1
# Raising Exception after timeout duration is reached
if self.isolation_scope is not None and self.current_scope is not None:
with sentry_sdk.scope.use_isolation_scope(self.isolation_scope):
with sentry_sdk.scope.use_scope(self.current_scope):
try:
raise ServerlessTimeoutWarning(
"WARNING : Function is expected to get timed out. Configured timeout duration = {} seconds.".format(
integer_configured_timeout
)
)
except Exception:
reraise(*self._capture_exception())
raise ServerlessTimeoutWarning(
"WARNING : Function is expected to get timed out. Configured timeout duration = {} seconds.".format(
integer_configured_timeout
@@ -1812,7 +1880,6 @@ try:
from gevent import get_hub as get_gevent_hub
from gevent.monkey import is_module_patched
except ImportError:
# it's not great that the signatures are different, get_hub can't return None
# consider adding an if TYPE_CHECKING to change the signature to Optional[Hub]
def get_gevent_hub(): # type: ignore[misc]
@@ -1888,3 +1955,109 @@ def should_be_treated_as_error(ty, value):
return False
return True
if TYPE_CHECKING:
T = TypeVar("T")
def try_convert(convert_func, value):
# type: (Callable[[Any], T], Any) -> Optional[T]
"""
Attempt to convert from an unknown type to a specific type, using the
given function. Return None if the conversion fails, i.e. if the function
raises an exception.
"""
try:
if isinstance(value, convert_func): # type: ignore
return value
except TypeError:
pass
try:
return convert_func(value)
except Exception:
return None
def safe_serialize(data):
# type: (Any) -> str
"""Safely serialize to a readable string."""
def serialize_item(item):
# type: (Any) -> Union[str, dict[Any, Any], list[Any], tuple[Any, ...]]
if callable(item):
try:
module = getattr(item, "__module__", None)
qualname = getattr(item, "__qualname__", None)
name = getattr(item, "__name__", "anonymous")
if module and qualname:
full_path = f"{module}.{qualname}"
elif module and name:
full_path = f"{module}.{name}"
else:
full_path = name
return f"<function {full_path}>"
except Exception:
return f"<callable {type(item).__name__}>"
elif isinstance(item, dict):
return {k: serialize_item(v) for k, v in item.items()}
elif isinstance(item, (list, tuple)):
return [serialize_item(x) for x in item]
elif hasattr(item, "__dict__"):
try:
attrs = {
k: serialize_item(v)
for k, v in vars(item).items()
if not k.startswith("_")
}
return f"<{type(item).__name__} {attrs}>"
except Exception:
return repr(item)
else:
return item
try:
serialized = serialize_item(data)
return json.dumps(serialized, default=str)
except Exception:
return str(data)
def has_logs_enabled(options):
# type: (Optional[dict[str, Any]]) -> bool
if options is None:
return False
return bool(
options.get("enable_logs", False)
or options["_experiments"].get("enable_logs", False)
)
def get_before_send_log(options):
# type: (Optional[dict[str, Any]]) -> Optional[Callable[[Log, Hint], Optional[Log]]]
if options is None:
return None
return options.get("before_send_log") or options["_experiments"].get(
"before_send_log"
)
def has_metrics_enabled(options):
# type: (Optional[dict[str, Any]]) -> bool
if options is None:
return False
return bool(options["_experiments"].get("enable_metrics", False))
def get_before_send_metric(options):
# type: (Optional[dict[str, Any]]) -> Optional[Callable[[Metric, Hint], Optional[Metric]]]
if options is None:
return None
return options["_experiments"].get("before_send_metric")