Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions scripts/find_raise_from_none.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import ast
import pathlib
from collections import defaultdict


class RaiseFromNoneVisitor(ast.NodeVisitor):
line_numbers = defaultdict(list)

def __init__(self, filename):
self.filename = filename

def visit_Raise(self, node: ast.Raise):
if node.cause is not None:
if isinstance(node.cause, ast.Constant) and node.cause.value is None:
RaiseFromNoneVisitor.line_numbers[self.filename].append(node.lineno)
self.generic_visit(node)


def scan_file(module_path: pathlib.Path):
source = pathlib.Path(module_path).read_text(encoding="utf-8")
tree = ast.parse(source, filename=module_path)

RaiseFromNoneVisitor(module_path).visit(tree)


def walk_package_modules():
for p in pathlib.Path("sentry_sdk").rglob("*.py"):
yield p


def format_detected_raises(line_numbers) -> str:
lines = []
for filepath, line_numbers_in_file in line_numbers.items():
lines_string = ", ".join(f"line {ln}" for ln in sorted(line_numbers_in_file))
lines.append(
f"{filepath}: {len(line_numbers_in_file)} occurrence(s) at {lines_string}"
)
return "\n".join(lines)


def main():
for module_path in walk_package_modules():
scan_file(module_path)

# TODO: Investigate why we suppress exception chains here.
ignored_raises = {
pathlib.Path("sentry_sdk/integrations/asgi.py"): 2,
pathlib.Path("sentry_sdk/integrations/asyncio.py"): 1,
}

raise_from_none_count = {
file: len(occurences)
for file, occurences in RaiseFromNoneVisitor.line_numbers.items()
}
if raise_from_none_count != ignored_raises:
exc = Exception("Detected unexpected raise ... from None.")
exc.add_note(
"Raise ... from None suppresses chained exceptions, removing valuable context."
)
exc.add_note(format_detected_raises(RaiseFromNoneVisitor.line_numbers))
raise exc


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions scripts/populate_tox/tox.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,4 @@ commands =
ruff check tests sentry_sdk
ruff format --check tests sentry_sdk
mypy sentry_sdk
python scripts/find_raise_from_none.py
35 changes: 20 additions & 15 deletions sentry_sdk/ai/monitoring.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import inspect
import sys
from functools import wraps

from sentry_sdk.consts import SPANDATA
import sentry_sdk.utils
from sentry_sdk import start_span
from sentry_sdk.tracing import Span
from sentry_sdk.utils import ContextVar
from sentry_sdk.utils import ContextVar, reraise, capture_internal_exceptions

from typing import TYPE_CHECKING

Expand Down Expand Up @@ -44,13 +45,15 @@ def sync_wrapped(*args: "Any", **kwargs: "Any") -> "Any":
try:
res = f(*args, **kwargs)
except Exception as e:
event, hint = sentry_sdk.utils.event_from_exception(
e,
client_options=sentry_sdk.get_client().options,
mechanism={"type": "ai_monitoring", "handled": False},
)
sentry_sdk.capture_event(event, hint=hint)
raise e from None
exc_info = sys.exc_info()
with capture_internal_exceptions():
event, hint = sentry_sdk.utils.event_from_exception(
e,
client_options=sentry_sdk.get_client().options,
mechanism={"type": "ai_monitoring", "handled": False},
)
sentry_sdk.capture_event(event, hint=hint)
reraise(*exc_info)
finally:
_ai_pipeline_name.set(None)
return res
Expand All @@ -72,13 +75,15 @@ async def async_wrapped(*args: "Any", **kwargs: "Any") -> "Any":
try:
res = await f(*args, **kwargs)
except Exception as e:
event, hint = sentry_sdk.utils.event_from_exception(
e,
client_options=sentry_sdk.get_client().options,
mechanism={"type": "ai_monitoring", "handled": False},
)
sentry_sdk.capture_event(event, hint=hint)
raise e from None
exc_info = sys.exc_info()
with capture_internal_exceptions():
event, hint = sentry_sdk.utils.event_from_exception(
e,
client_options=sentry_sdk.get_client().options,
mechanism={"type": "ai_monitoring", "handled": False},
)
sentry_sdk.capture_event(event, hint=hint)
reraise(*exc_info)
finally:
_ai_pipeline_name.set(None)
return res
Expand Down
14 changes: 10 additions & 4 deletions sentry_sdk/integrations/anthropic.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import sys
from collections.abc import Iterable
from functools import wraps
from typing import TYPE_CHECKING
Expand All @@ -20,6 +21,7 @@
event_from_exception,
package_version,
safe_serialize,
reraise,
)

try:
Expand Down Expand Up @@ -386,8 +388,10 @@ def _execute_sync(f: "Any", *args: "Any", **kwargs: "Any") -> "Any":
try:
result = f(*args, **kwargs)
except Exception as exc:
_capture_exception(exc)
raise exc from None
exc_info = sys.exc_info()
with capture_internal_exceptions():
_capture_exception(exc)
reraise(*exc_info)

return gen.send(result)
except StopIteration as e:
Expand Down Expand Up @@ -422,8 +426,10 @@ async def _execute_async(f: "Any", *args: "Any", **kwargs: "Any") -> "Any":
try:
result = await f(*args, **kwargs)
except Exception as exc:
_capture_exception(exc)
raise exc from None
exc_info = sys.exc_info()
with capture_internal_exceptions():
_capture_exception(exc)
reraise(*exc_info)

return gen.send(result)
except StopIteration as e:
Expand Down
17 changes: 11 additions & 6 deletions sentry_sdk/integrations/cohere.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import sys
from functools import wraps

from sentry_sdk import consts
Expand All @@ -16,7 +17,7 @@
import sentry_sdk
from sentry_sdk.scope import should_send_default_pii
from sentry_sdk.integrations import DidNotEnable, Integration
from sentry_sdk.utils import capture_internal_exceptions, event_from_exception
from sentry_sdk.utils import capture_internal_exceptions, event_from_exception, reraise

try:
from cohere.client import Client
Expand Down Expand Up @@ -151,9 +152,11 @@ def new_chat(*args: "Any", **kwargs: "Any") -> "Any":
try:
res = f(*args, **kwargs)
except Exception as e:
_capture_exception(e)
span.__exit__(None, None, None)
raise e from None
exc_info = sys.exc_info()
with capture_internal_exceptions():
_capture_exception(e)
span.__exit__(None, None, None)
reraise(*exc_info)

with capture_internal_exceptions():
if should_send_default_pii() and integration.include_prompts:
Expand Down Expand Up @@ -247,8 +250,10 @@ def new_embed(*args: "Any", **kwargs: "Any") -> "Any":
try:
res = f(*args, **kwargs)
except Exception as e:
_capture_exception(e)
raise e from None
exc_info = sys.exc_info()
with capture_internal_exceptions():
_capture_exception(e)
reraise(*exc_info)
if (
hasattr(res, "meta")
and hasattr(res.meta, "billed_units")
Expand Down
10 changes: 7 additions & 3 deletions sentry_sdk/integrations/huggingface_hub.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import sys
import inspect
from functools import wraps

Expand All @@ -11,6 +12,7 @@
from sentry_sdk.utils import (
capture_internal_exceptions,
event_from_exception,
reraise,
)

from typing import TYPE_CHECKING
Expand Down Expand Up @@ -126,9 +128,11 @@ def new_huggingface_task(*args: "Any", **kwargs: "Any") -> "Any":
try:
res = f(*args, **kwargs)
except Exception as e:
_capture_exception(e)
span.__exit__(None, None, None)
raise e from None
exc_info = sys.exc_info()
with capture_internal_exceptions():
_capture_exception(e)
span.__exit__(None, None, None)
reraise(*exc_info)

# Output attributes
finish_reason = None
Expand Down
38 changes: 26 additions & 12 deletions sentry_sdk/integrations/openai.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import sys
from functools import wraps

import sentry_sdk
Expand All @@ -16,6 +17,7 @@
capture_internal_exceptions,
event_from_exception,
safe_serialize,
reraise,
)

from typing import TYPE_CHECKING
Expand Down Expand Up @@ -483,8 +485,10 @@ def _execute_sync(f: "Any", *args: "Any", **kwargs: "Any") -> "Any":
try:
result = f(*args, **kwargs)
except Exception as e:
_capture_exception(e)
raise e from None
exc_info = sys.exc_info()
with capture_internal_exceptions():
_capture_exception(e)
reraise(*exc_info)

return gen.send(result)
except StopIteration as e:
Expand Down Expand Up @@ -515,8 +519,10 @@ async def _execute_async(f: "Any", *args: "Any", **kwargs: "Any") -> "Any":
try:
result = await f(*args, **kwargs)
except Exception as e:
_capture_exception(e)
raise e from None
exc_info = sys.exc_info()
with capture_internal_exceptions():
_capture_exception(e)
reraise(*exc_info)

return gen.send(result)
except StopIteration as e:
Expand Down Expand Up @@ -569,8 +575,10 @@ def _execute_sync(f: "Any", *args: "Any", **kwargs: "Any") -> "Any":
try:
result = f(*args, **kwargs)
except Exception as e:
_capture_exception(e, manual_span_cleanup=False)
raise e from None
exc_info = sys.exc_info()
with capture_internal_exceptions():
_capture_exception(e, manual_span_cleanup=False)
reraise(*exc_info)

return gen.send(result)
except StopIteration as e:
Expand Down Expand Up @@ -600,8 +608,10 @@ async def _execute_async(f: "Any", *args: "Any", **kwargs: "Any") -> "Any":
try:
result = await f(*args, **kwargs)
except Exception as e:
_capture_exception(e, manual_span_cleanup=False)
raise e from None
exc_info = sys.exc_info()
with capture_internal_exceptions():
_capture_exception(e, manual_span_cleanup=False)
reraise(*exc_info)

return gen.send(result)
except StopIteration as e:
Expand Down Expand Up @@ -655,8 +665,10 @@ def _execute_sync(f: "Any", *args: "Any", **kwargs: "Any") -> "Any":
try:
result = f(*args, **kwargs)
except Exception as e:
_capture_exception(e)
raise e from None
exc_info = sys.exc_info()
with capture_internal_exceptions():
_capture_exception(e)
reraise(*exc_info)

return gen.send(result)
except StopIteration as e:
Expand Down Expand Up @@ -686,8 +698,10 @@ async def _execute_async(f: "Any", *args: "Any", **kwargs: "Any") -> "Any":
try:
result = await f(*args, **kwargs)
except Exception as e:
_capture_exception(e)
raise e from None
exc_info = sys.exc_info()
with capture_internal_exceptions():
_capture_exception(e)
reraise(*exc_info)

return gen.send(result)
except StopIteration as e:
Expand Down
43 changes: 24 additions & 19 deletions sentry_sdk/integrations/openai_agents/patches/runner.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import sys
from functools import wraps

import sentry_sdk
from sentry_sdk.integrations import DidNotEnable
from sentry_sdk.utils import capture_internal_exceptions, reraise

from ..spans import agent_workflow_span, end_invoke_agent_span
from ..utils import _capture_exception, _record_exception_on_span
Expand Down Expand Up @@ -37,28 +39,31 @@ async def wrapper(*args: "Any", **kwargs: "Any") -> "Any":
try:
run_result = await original_func(*args, **kwargs)
except AgentsException as exc:
_capture_exception(exc)
exc_info = sys.exc_info()
with capture_internal_exceptions():
_capture_exception(exc)

context_wrapper = getattr(exc.run_data, "context_wrapper", None)
if context_wrapper is not None:
invoke_agent_span = getattr(
context_wrapper, "_sentry_agent_span", None
)
context_wrapper = getattr(exc.run_data, "context_wrapper", None)
if context_wrapper is not None:
invoke_agent_span = getattr(
context_wrapper, "_sentry_agent_span", None
)

if (
invoke_agent_span is not None
and invoke_agent_span.timestamp is None
):
_record_exception_on_span(invoke_agent_span, exc)
end_invoke_agent_span(context_wrapper, agent)

raise exc from None
if (
invoke_agent_span is not None
and invoke_agent_span.timestamp is None
):
_record_exception_on_span(invoke_agent_span, exc)
end_invoke_agent_span(context_wrapper, agent)
reraise(*exc_info)
except Exception as exc:
# Invoke agent span is not finished in this case.
# This is much less likely to occur than other cases because
# AgentRunner.run() is "just" a while loop around _run_single_turn.
_capture_exception(exc)
raise exc from None
exc_info = sys.exc_info()
with capture_internal_exceptions():
# Invoke agent span is not finished in this case.
# This is much less likely to occur than other cases because
# AgentRunner.run() is "just" a while loop around _run_single_turn.
_capture_exception(exc)
reraise(*exc_info)

end_invoke_agent_span(run_result.context_wrapper, agent)
return run_result
Expand Down
Loading
Loading