From 92a369862cdef7f0ff38ae84c0145b38d21f5851 Mon Sep 17 00:00:00 2001 From: Daniel Szoke Date: Fri, 10 May 2024 16:44:04 +0200 Subject: [PATCH 1/2] feat(scope): Add last_event_id to Scope Fixes #3049 --- sentry_sdk/scope.py | 28 +++++++++++++++++++++++++++- tests/test_scope.py | 21 +++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index e55b4ea3c7..e298a6682b 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -185,6 +185,7 @@ class Scope(object): "_propagation_context", "client", "_type", + "_last_event_id", ) def __init__(self, ty=None, client=None): @@ -207,6 +208,9 @@ def __init__(self, ty=None, client=None): incoming_trace_information = self._load_trace_data_from_env() self.generate_propagation_context(incoming_data=incoming_trace_information) + # self._last_event_id is only applicable to isolation scopes + self._last_event_id = None # type: Optional[str] + def __copy__(self): # type: () -> Scope """ @@ -308,6 +312,23 @@ def get_global_scope(cls): return _global_scope + @classmethod + def last_event_id(cls): + # type: () -> Optional[str] + """ + .. versionadded:: 2.2.0 + + Returns event ID of the event most recently captured by the isolation scope, or None if no event + has been captured. We do not consider events that are dropped, e.g. by a before_send hook. + Transactions also are not considered events in this context. + + The event corresponding to the returned event ID is NOT guaranteed to actually be sent to Sentry; + whether the event is sent depends on the transport. The event could be sent later or not at all. + Even a sent event could fail to arrive in Sentry due to network issues, exhausted quotas, or + various other reasons. + """ + return cls.get_isolation_scope()._last_event_id + def _merge_scopes(self, additional_scope=None, additional_scope_kwargs=None): # type: (Optional[Scope], Optional[Dict[str, Any]]) -> Scope """ @@ -1089,7 +1110,12 @@ def capture_event(self, event, hint=None, scope=None, **scope_kwargs): """ scope = self._merge_scopes(scope, scope_kwargs) - return Scope.get_client().capture_event(event=event, hint=hint, scope=scope) + event_id = Scope.get_client().capture_event(event=event, hint=hint, scope=scope) + + if event_id is not None and event.get("type") != "transaction": + self.get_isolation_scope()._last_event_id = event_id + + return event_id def capture_message(self, message, level=None, scope=None, **scope_kwargs): # type: (str, Optional[LogLevelStr], Optional[Scope], Any) -> Optional[str] diff --git a/tests/test_scope.py b/tests/test_scope.py index 6162a8da2f..bc67cbe63a 100644 --- a/tests/test_scope.py +++ b/tests/test_scope.py @@ -822,3 +822,24 @@ def test_set_tags(): "tag2": "updated", "tag3": "new", }, "Updating tags with empty dict changed tags" + + +def test_last_event_id(sentry_init): + sentry_init(enable_tracing=True) + + assert Scope.last_event_id() is None + + sentry_sdk.capture_exception(Exception("test")) + + assert Scope.last_event_id() is not None + + +def test_last_event_id_transaction(sentry_init): + sentry_init(enable_tracing=True) + + assert Scope.last_event_id() is None + + with sentry_sdk.start_transaction(name="test"): + pass + + assert Scope.last_event_id() is None, "Transaction should not set last_event_id" From 0d7a6f7f8a2eb662a5a9ddfb5576e62291ed569f Mon Sep 17 00:00:00 2001 From: Daniel Szoke Date: Wed, 15 May 2024 16:58:34 +0200 Subject: [PATCH 2/2] feat: Add `last_event_id` to top-level api ref #3049 --- sentry_sdk/__init__.py | 1 + sentry_sdk/api.py | 11 +++++++++++ tests/test_basics.py | 22 ++++++++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/sentry_sdk/__init__.py b/sentry_sdk/__init__.py index 1b646992ff..94d97a87d8 100644 --- a/sentry_sdk/__init__.py +++ b/sentry_sdk/__init__.py @@ -33,6 +33,7 @@ "get_traceparent", "is_initialized", "isolation_scope", + "last_event_id", "new_scope", "push_scope", "set_context", diff --git a/sentry_sdk/api.py b/sentry_sdk/api.py index 37c81afcc5..ba042c0a9f 100644 --- a/sentry_sdk/api.py +++ b/sentry_sdk/api.py @@ -59,6 +59,7 @@ def overload(x): "get_traceparent", "is_initialized", "isolation_scope", + "last_event_id", "new_scope", "push_scope", "set_context", @@ -332,6 +333,16 @@ def start_transaction( ) +@scopemethod +def last_event_id(): + # type: () -> Optional[str] + """ + See :py:meth:`sentry_sdk.Scope.last_event_id` documentation regarding + this method's limitations. + """ + return Scope.last_event_id() + + def set_measurement(name, value, unit=""): # type: (str, float, MeasurementUnit) -> None transaction = Scope.get_current_scope().transaction diff --git a/tests/test_basics.py b/tests/test_basics.py index bf42634710..aeb8488a0f 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -15,6 +15,7 @@ capture_exception, capture_message, start_transaction, + last_event_id, add_breadcrumb, Hub, Scope, @@ -778,3 +779,24 @@ def test_classmethod_tracing(sentry_init): with patch_start_tracing_child() as fake_start_child: assert instance_or_class.class_(1) == (TracingTestClass, 1) assert fake_start_child.call_count == 1 + + +def test_last_event_id(sentry_init): + sentry_init(enable_tracing=True) + + assert last_event_id() is None + + capture_exception(Exception("test")) + + assert last_event_id() is not None + + +def test_last_event_id_transaction(sentry_init): + sentry_init(enable_tracing=True) + + assert last_event_id() is None + + with start_transaction(name="test"): + pass + + assert last_event_id() is None, "Transaction should not set last_event_id"