Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Introduce Transaction and Hub.start_transaction #747

Merged
merged 1 commit into from Jun 29, 2020

Conversation

rhcarvalho
Copy link
Contributor

@rhcarvalho rhcarvalho commented Jun 26, 2020

This aligns the tracing implementation with the current JS tracing implementation, up to a certain extent.

Hub.start_transaction or start_transaction are meant to be used when starting transactions, replacing most uses of Hub.start_span / start_span.

Spans are typically created from their parent transactions via transaction.start_child, or start_span relying on the transaction being in the current scope.

It is okay to start a transaction without a name and set it later.
Sometimes the proper name is not known until after the transaction has started.

We could fail the transaction if it has no name when calling the finish method. Instead, set a default name that will prompt users to give a name to their transactions. This is the same behavior as implemented in JS.

@rhcarvalho
Copy link
Contributor Author

I don't know what's wrong with the docs generation job https://travis-ci.com/github/getsentry/sentry-python/jobs/354722815

@rhcarvalho rhcarvalho marked this pull request as ready for review June 27, 2020 13:14
Copy link
Member

@AbhiPrasad AbhiPrasad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Figured I would start reviewing these PRs to get more familiar with this SDK, but I'll leave Markus to give the final ✅

# This is for backwards compatibility with releases before
# start_transaction existed, to allow for a smoother transition.
if isinstance(span, Transaction) or "transaction" in kwargs:
deprecation_msg = (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need the redundant check here for isinstance(span, Transaction) or "transaction" in kwargs, we should just extract deprecation_msg to be a top level constant

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intention here was to have a self-contained block with the TODO note, so it is clearer what it applies to.

# appending an existing span to a new transaction created to
# contain the span.

# TODO: consider removing this in a future release.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an aside for another PR: we should have a standard deprecated comment somewhere like we do in JS, so we can easily see what to remove in each major version.

sentry_sdk/scope.py Show resolved Hide resolved
sentry_sdk/tracing.py Show resolved Hide resolved
#
# Would be clearer if Hub was not involved:
# transaction.name = "new_name"
# with transaction: # __enter__ => start, __exit__ => finish
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't we still have to attach the transaction to the current hub, even with with transaction:? That way, I'd much prefer to be explicit using hub.start_transaction then hide what it's doing here.

# XXX: should we always set transaction.hub = self?
# In a multi-hub program, what does it mean to write
# hub1.start_transaction(Transaction(hub=hub2))
# ? Should the transaction be on hub1 or hub2?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this is tricky. I think if we keep our current implementation, the transaction should be on hub1 and we should not allow setting hub through kwargs. If we decide to change the API, we can revisit this.

Copy link
Member

@untitaker untitaker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would shift each of those discussions into github issues if you want to continue them

name = kwargs.pop("transaction")
return self.start_transaction(name=name, **kwargs)

# XXX: this is not very useful because
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is there to be consistent with start_transaction I suppose.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, start_transaction doesn't exist outside this PR. In one of its usages, the previous start_span would also just return the input (sampling decision work moved to transactions only).

Keeping it for backwards-compat.

if span is not None:
return span.start_child(**kwargs)

# XXX: returning a detached span here means whatever span tree built
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes the API simpler to use as the caller does not have to check whether they are in a transaction. I don't really see why you call this not useful.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can hide an entire instrumented span tree if one is not careful to guarantee a transaction is in the scope when calling hub.start_span(op="...").

You told me today about a use case for intentionally omitting an entire instrumented span tree, I still keeping wrap my head around that. In other parts of the Unified API we explicitly prefer to avoid doing things when not necessary -- e.g. no-op transport/client when SDK has no DSN, callbacks are not called, etc.

I'll move these discussions into issues.

sentry_sdk/scope.py Show resolved Hide resolved
# hub1.start_transaction(Transaction(hub=hub2))
# ? Should the transaction be on hub1 or hub2?

# XXX: it is strange that both start_span and start_transaction take
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw i think those discussions should not be done as comments in the sourcecode

@rhcarvalho
Copy link
Contributor Author

I don't know what's wrong with the docs generation job https://travis-ci.com/github/getsentry/sentry-python/jobs/354722815

Circular imports.

Removed the SpanLike type, making the total diff smaller and solving the circular import.

diff --git a/sentry_sdk/_types.py b/sentry_sdk/_types.py
index 7c1aaa6..8e5427c 100644
--- a/sentry_sdk/_types.py
+++ b/sentry_sdk/_types.py
@@ -12,7 +12,7 @@ if MYPY:
     from typing import Optional
     from typing import Tuple
     from typing import Type
-    from typing import Union
+
     from typing_extensions import Literal
 
     ExcInfo = Tuple[
@@ -37,7 +37,3 @@ if MYPY:
     ]
     SessionStatus = Literal["ok", "exited", "crashed", "abnormal"]
     EndpointType = Literal["store", "envelope"]
-
-    from sentry_sdk.tracing import Span, Transaction  # noqa
-
-    SpanLike = Union[Span, Transaction]
diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py
index 0c082c4..f928063 100644
--- a/sentry_sdk/scope.py
+++ b/sentry_sdk/scope.py
@@ -26,7 +26,7 @@ if MYPY:
         Type,
     )
 
-    from sentry_sdk.tracing import SpanLike
+    from sentry_sdk.tracing import Span
     from sentry_sdk.sessions import Session
 
     F = TypeVar("F", bound=Callable[..., Any])
@@ -114,7 +114,7 @@ class Scope(object):
         self.clear_breadcrumbs()
         self._should_capture = True
 
-        self._span = None  # type: Optional[SpanLike]
+        self._span = None  # type: Optional[Span]
         self._session = None  # type: Optional[Session]
         self._force_auto_session_tracking = None  # type: Optional[bool]
 
@@ -181,13 +181,13 @@ class Scope(object):
 
     @property
     def span(self):
-        # type: () -> Optional[SpanLike]
+        # type: () -> Optional[Span]
         """Get/set current tracing span or transaction."""
         return self._span
 
     @span.setter
     def span(self, span):
-        # type: (Optional[SpanLike]) -> None
+        # type: (Optional[Span]) -> None
         self._span = span
         # XXX: this differs from the implementation in JS, there Scope.setSpan
         # does not set Scope._transactionName.
diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py
index 05abcf6..d390c6b 100644
--- a/sentry_sdk/tracing.py
+++ b/sentry_sdk/tracing.py
@@ -25,9 +25,6 @@ if MYPY:
     from typing import Dict
     from typing import List
     from typing import Tuple
-    from typing import Type
-
-    from sentry_sdk._types import SpanLike
 
 _traceparent_header_format_re = re.compile(
     "^[ \t]*"  # whitespace
@@ -225,7 +222,7 @@ class Span(object):
 
     @classmethod
     def continue_from_environ(
-        cls,  # type: Type[SpanLike]
+        cls,
         environ,  # type: typing.Mapping[str, str]
         **kwargs  # type: Any
     ):
@@ -239,7 +236,7 @@ class Span(object):
 
     @classmethod
     def continue_from_headers(
-        cls,  # type: Type[SpanLike]
+        cls,
         headers,  # type: typing.Mapping[str, str]
         **kwargs  # type: Any
     ):
@@ -261,7 +258,7 @@ class Span(object):
 
     @classmethod
     def from_traceparent(
-        cls,  # type: Type[SpanLike]
+        cls,
         traceparent,  # type: Optional[str]
         **kwargs  # type: Any
     ):

This aligns the tracing implementation with the current JS
tracing implementation, up to a certain extent.

Hub.start_transaction or start_transaction are meant to be used when
starting transactions, replacing most uses of Hub.start_span /
start_span.

Spans are typically created from their parent transactions via
transaction.start_child, or start_span relying on the transaction being
in the current scope.

It is okay to start a transaction without a name and set it later.
Sometimes the proper name is not known until after the transaction has
started.

We could fail the transaction if it has no name when calling the finish
method. Instead, set a default name that will prompt users to give a
name to their transactions. This is the same behavior as implemented in
JS.

Span.continue_from_headers, Span.continue_from_environ,
Span.from_traceparent and the equivalent methods on Transaction always
return a Transaction and take kwargs to set attributes on the new
Transaction.

Rename Span.new_span to Span.start_child (and Transaction.start_child)a,
aligning with JS / tracing API spec. The old name is kept for backwards
compatibility.

Co-authored-by: Markus Unterwaditzer <markus@unterwaditzer.net>
@rhcarvalho rhcarvalho force-pushed the rhcarvalho/tracing-transaction branch from 7c94965 to 9389f49 Compare June 29, 2020 15:05
@rhcarvalho
Copy link
Contributor Author

Removed some commentary, will move the discussion into new issues where appropriate.

@rhcarvalho rhcarvalho merged commit ab3da08 into master Jun 29, 2020
@rhcarvalho rhcarvalho deleted the rhcarvalho/tracing-transaction branch June 29, 2020 15:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants