diff --git a/sentry_sdk/integrations/opentelemetry/potel_span_processor.py b/sentry_sdk/integrations/opentelemetry/potel_span_processor.py index 1d27642d1e..1736fcd25e 100644 --- a/sentry_sdk/integrations/opentelemetry/potel_span_processor.py +++ b/sentry_sdk/integrations/opentelemetry/potel_span_processor.py @@ -179,7 +179,7 @@ def _root_span_to_transaction_event(self, span): transaction_name, transaction_source = extract_transaction_name_source(span) span_data = extract_span_data(span) - (_, description, _, http_status, _) = span_data + (_, description, status, http_status, _) = span_data trace_context = get_trace_context(span, span_data=span_data) contexts = {"trace": trace_context} @@ -241,6 +241,9 @@ def _span_to_json(self, span): } ) + if status: + span_json.setdefault("tags", {})["status"] = status + if parent_span_id: span_json["parent_span_id"] = parent_span_id diff --git a/sentry_sdk/integrations/opentelemetry/sampler.py b/sentry_sdk/integrations/opentelemetry/sampler.py index 0997048532..5fa41d28fc 100644 --- a/sentry_sdk/integrations/opentelemetry/sampler.py +++ b/sentry_sdk/integrations/opentelemetry/sampler.py @@ -34,8 +34,8 @@ def get_parent_sampled(parent_context, trace_id): # Only inherit sample rate if `traceId` is the same if is_span_context_valid and parent_context.trace_id == trace_id: # this is getSamplingDecision in JS - if parent_context.trace_flags.sampled: - return True + if parent_context.trace_flags.sampled is not None: + return parent_context.trace_flags.sampled dsc_sampled = parent_context.trace_state.get(TRACESTATE_SAMPLED_KEY) if dsc_sampled == "true": diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index fbe258fb8a..12b6c5aed6 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -786,14 +786,6 @@ def span(self, span): # type: (Optional[Span]) -> None """Set current tracing span.""" self._span = span - # XXX: this differs from the implementation in JS, there Scope.setSpan - # does not set Scope._transactionName. - if isinstance(span, Transaction): - transaction = span - if transaction.name: - self._transaction = transaction.name - if transaction.source: - self._transaction_info["source"] = transaction.source @property def profile(self): diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index becf7979e2..18b18ba8ef 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -163,6 +163,7 @@ class TransactionKwargs(SpanKwargs, total=False): } DEFAULT_SPAN_ORIGIN = "manual" +DEFAULT_SPAN_NAME = "" tracer = otel_trace.get_tracer(__name__) @@ -1249,7 +1250,7 @@ def __init__( # OTel timestamps have nanosecond precision start_timestamp = convert_to_otel_timestamp(start_timestamp) - span_name = name or description or op or "" + span_name = name or description or op or DEFAULT_SPAN_NAME # Prepopulate some attrs so that they're accessible in traces_sampler attributes = attributes or {} @@ -1398,7 +1399,9 @@ def span_id(self): @property def is_valid(self): # type: () -> bool - return self._otel_span.get_span_context().is_valid + return self._otel_span.get_span_context().is_valid and isinstance( + self._otel_span, ReadableSpan + ) @property def sampled(self): diff --git a/tests/tracing/test_integration_tests.py b/tests/tracing/test_integration_tests.py index e27dbea901..3a4bef77fb 100644 --- a/tests/tracing/test_integration_tests.py +++ b/tests/tracing/test_integration_tests.py @@ -1,5 +1,3 @@ -import weakref -import gc import re import pytest import random @@ -7,12 +5,11 @@ import sentry_sdk from sentry_sdk import ( capture_message, + continue_trace, start_span, - start_transaction, ) from sentry_sdk.consts import SPANSTATUS from sentry_sdk.transport import Transport -from sentry_sdk.tracing import Transaction @pytest.mark.parametrize("sample_rate", [0.0, 1.0]) @@ -20,8 +17,8 @@ def test_basic(sentry_init, capture_events, sample_rate): sentry_init(traces_sample_rate=sample_rate) events = capture_events() - with start_transaction(name="hi") as transaction: - transaction.set_status(SPANSTATUS.OK) + with start_span(name="hi") as root_span: + root_span.set_status(SPANSTATUS.OK) with pytest.raises(ZeroDivisionError): with start_span(op="foo", name="foodesc"): 1 / 0 @@ -39,21 +36,23 @@ def test_basic(sentry_init, capture_events, sample_rate): span1, span2 = event["spans"] parent_span = event assert span1["tags"]["status"] == "internal_error" + assert span1["status"] == "internal_error" assert span1["op"] == "foo" assert span1["description"] == "foodesc" assert "status" not in span2.get("tags", {}) assert span2["op"] == "bar" assert span2["description"] == "bardesc" assert parent_span["transaction"] == "hi" - assert "status" not in event["tags"] + assert "status" not in event.get("tags", {}) assert event["contexts"]["trace"]["status"] == "ok" else: assert not events -@pytest.mark.parametrize("sampled", [True, False, None]) @pytest.mark.parametrize("sample_rate", [0.0, 1.0]) -def test_continue_from_headers(sentry_init, capture_envelopes, sampled, sample_rate): +def test_continue_trace( + sentry_init, capture_envelopes, sample_rate, SortedBaggage +): # noqa:N803 """ Ensure data is actually passed along via headers, and that they are read correctly. @@ -62,55 +61,41 @@ def test_continue_from_headers(sentry_init, capture_envelopes, sampled, sample_r envelopes = capture_envelopes() # make a parent transaction (normally this would be in a different service) - with start_transaction(name="hi", sampled=True if sample_rate == 0 else None): - with start_span() as old_span: - old_span.sampled = sampled - headers = dict( - sentry_sdk.get_current_scope().iter_trace_propagation_headers(old_span) - ) - headers["baggage"] = ( - "other-vendor-value-1=foo;bar;baz, " - "sentry-trace_id=771a43a4192642f0b136d5159a501700, " - "sentry-public_key=49d0f7386ad645858ae85020e393bef3, " - "sentry-sample_rate=0.01337, sentry-user_id=Amelie, " - "other-vendor-value-2=foo;bar;" - ) + with start_span(name="hi"): + with start_span(name="inner") as old_span: + headers = dict(old_span.iter_headers()) + assert headers["sentry-trace"] + assert headers["baggage"] # child transaction, to prove that we can read 'sentry-trace' header data correctly - child_transaction = Transaction.continue_from_headers(headers, name="WRONG") - assert child_transaction is not None - assert child_transaction.parent_sampled == sampled - assert child_transaction.trace_id == old_span.trace_id - assert child_transaction.same_process_as_parent is False - assert child_transaction.parent_span_id == old_span.span_id - assert child_transaction.span_id != old_span.span_id - - baggage = child_transaction._baggage - assert baggage - assert not baggage.mutable - assert baggage.sentry_items == { - "public_key": "49d0f7386ad645858ae85020e393bef3", - "trace_id": "771a43a4192642f0b136d5159a501700", - "user_id": "Amelie", - "sample_rate": "0.01337", - } - - # add child transaction to the scope, to show that the captured message will - # be tagged with the trace id (since it happens while the transaction is - # open) - with start_transaction(child_transaction): - # change the transaction name from "WRONG" to make sure the change - # is reflected in the final data - sentry_sdk.get_current_scope().transaction = "ho" - capture_message("hello") + with continue_trace(headers): + with start_span(name="WRONG") as child_root_span: + assert child_root_span is not None + assert child_root_span.sampled == (sample_rate == 1.0) + if child_root_span.sampled: + assert child_root_span.parent_span_id == old_span.span_id + assert child_root_span.trace_id == old_span.trace_id + assert child_root_span.span_id != old_span.span_id + + baggage = child_root_span.get_baggage() + assert baggage.serialize() == SortedBaggage(headers["baggage"]) + + # change the transaction name from "WRONG" to make sure the change + # is reflected in the final data + sentry_sdk.get_current_scope().set_transaction_name("ho") + # to show that the captured message will be tagged with the trace id + # (since it happens while the transaction is open) + capture_message("hello") # in this case the child transaction won't be captured - if sampled is False or (sample_rate == 0 and sampled is None): - trace1, message = envelopes + # but message follows twp spec + if sample_rate == 0.0: + (message,) = envelopes message_payload = message.get_event() - trace1_payload = trace1.get_transaction_event() - - assert trace1_payload["transaction"] == "hi" + assert message_payload["transaction"] == "ho" + assert ( + child_root_span.trace_id == message_payload["contexts"]["trace"]["trace_id"] + ) else: trace1, message, trace2 = envelopes trace1_payload = trace1.get_transaction_event() @@ -123,24 +108,22 @@ def test_continue_from_headers(sentry_init, capture_envelopes, sampled, sample_r assert ( trace1_payload["contexts"]["trace"]["trace_id"] == trace2_payload["contexts"]["trace"]["trace_id"] - == child_transaction.trace_id + == child_root_span.trace_id == message_payload["contexts"]["trace"]["trace_id"] ) assert trace2.headers["trace"] == baggage.dynamic_sampling_context() - assert trace2.headers["trace"] == { - "public_key": "49d0f7386ad645858ae85020e393bef3", - "trace_id": "771a43a4192642f0b136d5159a501700", - "user_id": "Amelie", - "sample_rate": "0.01337", - } assert message_payload["message"] == "hello" @pytest.mark.parametrize("sample_rate", [0.5, 1.0]) def test_dynamic_sampling_head_sdk_creates_dsc( - sentry_init, capture_envelopes, sample_rate, monkeypatch + sentry_init, + capture_envelopes, + sample_rate, + monkeypatch, + SortedBaggage, # noqa: N803 ): sentry_init(traces_sample_rate=sample_rate, release="foo") envelopes = capture_envelopes() @@ -148,31 +131,20 @@ def test_dynamic_sampling_head_sdk_creates_dsc( # make sure transaction is sampled for both cases monkeypatch.setattr(random, "random", lambda: 0.1) - transaction = Transaction.continue_from_headers({}, name="Head SDK tx") - - # will create empty mutable baggage - baggage = transaction._baggage - assert baggage - assert baggage.mutable - assert baggage.sentry_items == {} - assert baggage.third_party_items == "" - - with start_transaction(transaction): - with start_span(op="foo", name="foodesc"): - pass + with continue_trace({}): + with start_span(name="Head SDK tx"): + with start_span(op="foo", name="foodesc") as span: + baggage = span.get_baggage() - # finish will create a new baggage entry - baggage = transaction._baggage - trace_id = transaction.trace_id + trace_id = span.trace_id assert baggage - assert not baggage.mutable assert baggage.third_party_items == "" assert baggage.sentry_items == { "environment": "production", "release": "foo", "sample_rate": str(sample_rate), - "sampled": "true" if transaction.sampled else "false", + "sampled": "true" if span.sampled else "false", "transaction": "Head SDK tx", "trace_id": trace_id, } @@ -184,9 +156,9 @@ def test_dynamic_sampling_head_sdk_creates_dsc( "sentry-transaction=Head%%20SDK%%20tx," "sentry-sample_rate=%s," "sentry-sampled=%s" - % (trace_id, sample_rate, "true" if transaction.sampled else "false") + % (trace_id, sample_rate, "true" if span.sampled else "false") ) - assert baggage.serialize() == expected_baggage + assert baggage.serialize() == SortedBaggage(expected_baggage) (envelope,) = envelopes assert envelope.headers["trace"] == baggage.dynamic_sampling_context() @@ -194,41 +166,12 @@ def test_dynamic_sampling_head_sdk_creates_dsc( "environment": "production", "release": "foo", "sample_rate": str(sample_rate), - "sampled": "true" if transaction.sampled else "false", + "sampled": "true" if span.sampled else "false", "transaction": "Head SDK tx", "trace_id": trace_id, } -@pytest.mark.parametrize( - "args,expected_refcount", - [({"traces_sample_rate": 1.0}, 100), ({"traces_sample_rate": 0.0}, 0)], -) -def test_memory_usage(sentry_init, capture_events, args, expected_refcount): - sentry_init(**args) - - references = weakref.WeakSet() - - with start_transaction(name="hi"): - for i in range(100): - with start_span(op="helloworld", name="hi {}".format(i)) as span: - - def foo(): - pass - - references.add(foo) - span.set_tag("foo", foo) - pass - - del foo - del span - - # required only for pypy (cpython frees immediately) - gc.collect() - - assert len(references) == expected_refcount - - def test_transactions_do_not_go_through_before_send(sentry_init, capture_events): def before_send(event, hint): raise RuntimeError("should not be called") @@ -236,7 +179,7 @@ def before_send(event, hint): sentry_init(traces_sample_rate=1.0, before_send=before_send) events = capture_events() - with start_transaction(name="/"): + with start_span(name="/"): pass assert len(events) == 1 @@ -254,7 +197,7 @@ def capture_event(self, event): sentry_init(traces_sample_rate=1, transport=CustomTransport()) events = capture_events() - with start_transaction(name="hi"): + with start_span(name="hi"): with start_span(op="bar", name="bardesc"): pass @@ -264,14 +207,14 @@ def capture_event(self, event): def test_trace_propagation_meta_head_sdk(sentry_init): sentry_init(traces_sample_rate=1.0, release="foo") - transaction = Transaction.continue_from_headers({}, name="Head SDK tx") meta = None span = None - with start_transaction(transaction): - with start_span(op="foo", name="foodesc") as current_span: - span = current_span - meta = sentry_sdk.get_current_scope().trace_propagation_meta() + with continue_trace({}): + with start_span(name="Head SDK tx") as root_span: + with start_span(op="foo", name="foodesc") as current_span: + span = current_span + meta = sentry_sdk.get_current_scope().trace_propagation_meta() ind = meta.find(">") + 1 sentry_trace, baggage = meta[:ind], meta[ind:] @@ -282,4 +225,4 @@ def test_trace_propagation_meta_head_sdk(sentry_init): assert 'meta name="baggage"' in baggage baggage_content = re.findall('content="([^"]*)"', baggage)[0] - assert baggage_content == transaction.get_baggage().serialize() + assert baggage_content == root_span.get_baggage().serialize() diff --git a/tests/tracing/test_misc.py b/tests/tracing/test_misc.py index 46ac8ad10f..0d12acc617 100644 --- a/tests/tracing/test_misc.py +++ b/tests/tracing/test_misc.py @@ -1,67 +1,43 @@ import pytest -import gc -import uuid -import os -from unittest import mock from unittest.mock import MagicMock import sentry_sdk -from sentry_sdk import start_span, start_transaction, set_measurement +from sentry_sdk import start_span, set_measurement, get_current_scope from sentry_sdk.consts import MATCH_ALL -from sentry_sdk.tracing import Span, Transaction from sentry_sdk.tracing_utils import should_propagate_trace from sentry_sdk.utils import Dsn -def test_span_trimming(sentry_init, capture_events): - sentry_init(traces_sample_rate=1.0, _experiments={"max_spans": 3}) - events = capture_events() - - with start_transaction(name="hi"): - for i in range(10): - with start_span(op="foo{}".format(i)): - pass - - (event,) = events - - assert len(event["spans"]) == 3 - - span1, span2, span3 = event["spans"] - assert span1["op"] == "foo0" - assert span2["op"] == "foo1" - assert span3["op"] == "foo2" - - def test_transaction_naming(sentry_init, capture_events): sentry_init(traces_sample_rate=1.0) events = capture_events() # default name in event if no name is passed - with start_transaction() as transaction: + with start_span(): pass assert len(events) == 1 - assert events[0]["transaction"] == "" + assert events[0]["transaction"] == "" # the name can be set once the transaction's already started - with start_transaction() as transaction: - transaction.name = "name-known-after-transaction-started" + with start_span() as span: + span.name = "name-known-after-transaction-started" assert len(events) == 2 assert events[1]["transaction"] == "name-known-after-transaction-started" # passing in a name works, too - with start_transaction(name="a"): + with start_span(name="a"): pass assert len(events) == 3 assert events[2]["transaction"] == "a" -def test_transaction_data(sentry_init, capture_events): +def test_root_span_data(sentry_init, capture_events): sentry_init(traces_sample_rate=1.0) events = capture_events() - with start_transaction(name="test-transaction"): - span_or_tx = sentry_sdk.get_current_span() - span_or_tx.set_data("foo", "bar") + with start_span(name="test-root-span"): + root_span = sentry_sdk.get_current_span() + root_span.set_data("foo", "bar") with start_span(op="test-span") as span: span.set_data("spanfoo", "spanbar") @@ -82,175 +58,15 @@ def test_transaction_data(sentry_init, capture_events): assert span_data.items() >= {"spanfoo": "spanbar"}.items() -def test_start_transaction(sentry_init): - sentry_init(traces_sample_rate=1.0) - - # you can have it start a transaction for you - result1 = start_transaction( - name="/interactions/other-dogs/new-dog", op="greeting.sniff" - ) - assert isinstance(result1, Transaction) - assert result1.name == "/interactions/other-dogs/new-dog" - assert result1.op == "greeting.sniff" - - # or you can pass it an already-created transaction - preexisting_transaction = Transaction( - name="/interactions/other-dogs/new-dog", op="greeting.sniff" - ) - result2 = start_transaction(preexisting_transaction) - assert result2 is preexisting_transaction - - -def test_finds_transaction_on_scope(sentry_init): - sentry_init(traces_sample_rate=1.0) - - transaction = start_transaction(name="dogpark") - - scope = sentry_sdk.get_current_scope() - - # See note in Scope class re: getters and setters of the `transaction` - # property. For the moment, assigning to scope.transaction merely sets the - # transaction name, rather than putting the transaction on the scope, so we - # have to assign to _span directly. - scope._span = transaction - - # Reading scope.property, however, does what you'd expect, and returns the - # transaction on the scope. - assert scope.transaction is not None - assert isinstance(scope.transaction, Transaction) - assert scope.transaction.name == "dogpark" - - # If the transaction is also set as the span on the scope, it can be found - # by accessing _span, too. - assert scope._span is not None - assert isinstance(scope._span, Transaction) - assert scope._span.name == "dogpark" - - -def test_finds_transaction_when_descendent_span_is_on_scope( - sentry_init, -): - sentry_init(traces_sample_rate=1.0) - - transaction = start_transaction(name="dogpark") - child_span = transaction.start_child(op="sniffing") - - scope = sentry_sdk.get_current_scope() - scope._span = child_span - - # this is the same whether it's the transaction itself or one of its - # decedents directly attached to the scope - assert scope.transaction is not None - assert isinstance(scope.transaction, Transaction) - assert scope.transaction.name == "dogpark" - - # here we see that it is in fact the span on the scope, rather than the - # transaction itself - assert scope._span is not None - assert isinstance(scope._span, Span) - assert scope._span.op == "sniffing" - - -def test_finds_orphan_span_on_scope(sentry_init): - # this is deprecated behavior which may be removed at some point (along with - # the start_span function) - sentry_init(traces_sample_rate=1.0) - - span = start_span(op="sniffing") - - scope = sentry_sdk.get_current_scope() - scope._span = span - - assert scope._span is not None - assert isinstance(scope._span, Span) - assert scope._span.op == "sniffing" - - -def test_finds_non_orphan_span_on_scope(sentry_init): - sentry_init(traces_sample_rate=1.0) - - transaction = start_transaction(name="dogpark") - child_span = transaction.start_child(op="sniffing") - - scope = sentry_sdk.get_current_scope() - scope._span = child_span - - assert scope._span is not None - assert isinstance(scope._span, Span) - assert scope._span.op == "sniffing" - - -def test_circular_references(monkeypatch, sentry_init, request): - # TODO: We discovered while writing this test about transaction/span - # reference cycles that there's actually also a circular reference in - # `serializer.py`, between the functions `_serialize_node` and - # `_serialize_node_impl`, both of which are defined inside of the main - # `serialize` function, and each of which calls the other one. For now, in - # order to avoid having those ref cycles give us a false positive here, we - # can mock out `serialize`. In the long run, though, we should probably fix - # that. (Whenever we do work on fixing it, it may be useful to add - # - # gc.set_debug(gc.DEBUG_LEAK) - # request.addfinalizer(lambda: gc.set_debug(~gc.DEBUG_LEAK)) - # - # immediately after the initial collection below, so we can see what new - # objects the garbage collector has to clean up once `transaction.finish` is - # called and the serializer runs.) - monkeypatch.setattr( - sentry_sdk.client, - "serialize", - mock.Mock( - return_value=None, - ), - ) - - # In certain versions of python, in some environments (specifically, python - # 3.4 when run in GH Actions), we run into a `ctypes` bug which creates - # circular references when `uuid4()` is called, as happens when we're - # generating event ids. Mocking it with an implementation which doesn't use - # the `ctypes` function lets us avoid having false positives when garbage - # collecting. See https://bugs.python.org/issue20519. - monkeypatch.setattr( - uuid, - "uuid4", - mock.Mock( - return_value=uuid.UUID(bytes=os.urandom(16)), - ), - ) - - gc.disable() - request.addfinalizer(gc.enable) - +def test_finds_spans_on_scope(sentry_init): sentry_init(traces_sample_rate=1.0) - # Make sure that we're starting with a clean slate before we start creating - # transaction/span reference cycles - gc.collect() - - dogpark_transaction = start_transaction(name="dogpark") - sniffing_span = dogpark_transaction.start_child(op="sniffing") - wagging_span = dogpark_transaction.start_child(op="wagging") - - # At some point, you have to stop sniffing - there are balls to chase! - so finish - # this span while the dogpark transaction is still open - sniffing_span.finish() + with start_span(name="dogpark") as root_span: + assert get_current_scope().span == root_span - # The wagging, however, continues long past the dogpark, so that span will - # NOT finish before the transaction ends. (Doing it in this order proves - # that both finished and unfinished spans get their cycles broken.) - dogpark_transaction.finish() - - # Eventually you gotta sleep... - wagging_span.finish() - - # assuming there are no cycles by this point, these should all be able to go - # out of scope and get their memory deallocated without the garbage - # collector having anything to do - del sniffing_span - del wagging_span - del dogpark_transaction - - assert gc.collect() == 0 + with start_span(name="child") as child_span: + assert get_current_scope().span == child_span + assert child_span.root_span == root_span def test_set_measurement(sentry_init, capture_events): @@ -258,21 +74,19 @@ def test_set_measurement(sentry_init, capture_events): events = capture_events() - transaction = start_transaction(name="measuring stuff") + with start_span(name="measuring stuff") as span: - with pytest.raises(TypeError): - transaction.set_measurement() + with pytest.raises(TypeError): + span.set_measurement() - with pytest.raises(TypeError): - transaction.set_measurement("metric.foo") + with pytest.raises(TypeError): + span.set_measurement("metric.foo") - transaction.set_measurement("metric.foo", 123) - transaction.set_measurement("metric.bar", 456, unit="second") - transaction.set_measurement("metric.baz", 420.69, unit="custom") - transaction.set_measurement("metric.foobar", 12, unit="percent") - transaction.set_measurement("metric.foobar", 17.99, unit="percent") - - transaction.finish() + span.set_measurement("metric.foo", 123) + span.set_measurement("metric.bar", 456, unit="second") + span.set_measurement("metric.baz", 420.69, unit="custom") + span.set_measurement("metric.foobar", 12, unit="percent") + span.set_measurement("metric.foobar", 17.99, unit="percent") (event,) = events assert event["measurements"]["metric.foo"] == {"value": 123, "unit": ""} @@ -286,7 +100,7 @@ def test_set_measurement_public_api(sentry_init, capture_events): events = capture_events() - with start_transaction(name="measuring stuff"): + with start_span(name="measuring stuff"): set_measurement("metric.foo", 123) set_measurement("metric.bar", 456, unit="second") @@ -374,68 +188,3 @@ def test_should_propagate_trace_to_sentry( client.transport.parsed_dsn = Dsn(dsn) assert should_propagate_trace(client, url) == expected_propagation_decision - - -def test_start_transaction_updates_scope_name_source(sentry_init): - sentry_init(traces_sample_rate=1.0) - - scope = sentry_sdk.get_current_scope() - - with start_transaction(name="foobar", source="route"): - assert scope._transaction == "foobar" - assert scope._transaction_info == {"source": "route"} - - -@pytest.mark.parametrize("sampled", (True, None)) -def test_transaction_dropped_debug_not_started(sentry_init, sampled): - sentry_init(enable_tracing=True) - - tx = Transaction(sampled=sampled) - - with mock.patch("sentry_sdk.tracing.logger") as mock_logger: - with tx: - pass - - mock_logger.debug.assert_any_call( - "Discarding transaction because it was not started with sentry_sdk.start_transaction" - ) - - with pytest.raises(AssertionError): - # We should NOT see the "sampled = False" message here - mock_logger.debug.assert_any_call( - "Discarding transaction because sampled = False" - ) - - -def test_transaction_dropped_sampled_false(sentry_init): - sentry_init(enable_tracing=True) - - tx = Transaction(sampled=False) - - with mock.patch("sentry_sdk.tracing.logger") as mock_logger: - with sentry_sdk.start_transaction(tx): - pass - - mock_logger.debug.assert_any_call("Discarding transaction because sampled = False") - - with pytest.raises(AssertionError): - # We should not see the "not started" message here - mock_logger.debug.assert_any_call( - "Discarding transaction because it was not started with sentry_sdk.start_transaction" - ) - - -def test_transaction_not_started_warning(sentry_init): - sentry_init(enable_tracing=True) - - tx = Transaction() - - with mock.patch("sentry_sdk.tracing.logger") as mock_logger: - with tx: - pass - - mock_logger.debug.assert_any_call( - "Transaction was entered without being started with sentry_sdk.start_transaction." - "The transaction will not be sent to Sentry. To fix, start the transaction by" - "passing it to sentry_sdk.start_transaction." - ) diff --git a/tests/tracing/test_propagation.py b/tests/tracing/test_propagation.py deleted file mode 100644 index 730bf2672b..0000000000 --- a/tests/tracing/test_propagation.py +++ /dev/null @@ -1,40 +0,0 @@ -import sentry_sdk -import pytest - - -def test_standalone_span_iter_headers(sentry_init): - sentry_init(enable_tracing=True) - - with sentry_sdk.start_span(op="test") as span: - with pytest.raises(StopIteration): - # We should not have any propagation headers - next(span.iter_headers()) - - -def test_span_in_span_iter_headers(sentry_init): - sentry_init(enable_tracing=True) - - with sentry_sdk.start_span(op="test"): - with sentry_sdk.start_span(op="test2") as span_inner: - with pytest.raises(StopIteration): - # We should not have any propagation headers - next(span_inner.iter_headers()) - - -def test_span_in_transaction(sentry_init): - sentry_init(enable_tracing=True) - - with sentry_sdk.start_transaction(op="test"): - with sentry_sdk.start_span(op="test2") as span: - # Ensure the headers are there - next(span.iter_headers()) - - -def test_span_in_span_in_transaction(sentry_init): - sentry_init(enable_tracing=True) - - with sentry_sdk.start_transaction(op="test"): - with sentry_sdk.start_span(op="test2"): - with sentry_sdk.start_span(op="test3") as span_inner: - # Ensure the headers are there - next(span_inner.iter_headers()) diff --git a/tests/tracing/test_sampling.py b/tests/tracing/test_sampling.py index 8ef362a1e8..db5a545b5c 100644 --- a/tests/tracing/test_sampling.py +++ b/tests/tracing/test_sampling.py @@ -5,7 +5,7 @@ import pytest import sentry_sdk -from sentry_sdk import start_span, start_transaction, capture_exception +from sentry_sdk import start_span, capture_exception from sentry_sdk.utils import logger @@ -41,26 +41,12 @@ def test_no_double_sampling(sentry_init, capture_events): sentry_init(traces_sample_rate=1.0, sample_rate=0.0) events = capture_events() - with start_transaction(name="/"): + with start_span(name="/"): pass assert len(events) == 1 -@pytest.mark.parametrize("sampling_decision", [True, False]) -def test_get_transaction_and_span_from_scope_regardless_of_sampling_decision( - sentry_init, sampling_decision -): - sentry_init(traces_sample_rate=1.0) - - with start_transaction(name="/", sampled=sampling_decision): - with start_span(op="child-span"): - with start_span(op="child-child-span"): - scope = sentry_sdk.get_current_scope() - assert scope.span.op == "child-child-span" - assert scope.transaction.name == "/" - - @pytest.mark.parametrize( "traces_sample_rate,expected_decision", [(0.0, False), (0.25, False), (0.75, True), (1.00, True)], @@ -73,7 +59,7 @@ def test_uses_traces_sample_rate_correctly( sentry_init(traces_sample_rate=traces_sample_rate) with mock.patch.object(random, "random", return_value=0.5): - transaction = start_transaction(name="dogpark") + transaction = start_span(name="dogpark") assert transaction.sampled is expected_decision @@ -89,8 +75,8 @@ def test_uses_traces_sampler_return_value_correctly( sentry_init(traces_sampler=mock.Mock(return_value=traces_sampler_return_value)) with mock.patch.object(random, "random", return_value=0.5): - transaction = start_transaction(name="dogpark") - assert transaction.sampled is expected_decision + with start_span(name="dogpark") as span: + assert span.sampled is expected_decision @pytest.mark.parametrize("traces_sampler_return_value", [True, False]) @@ -99,8 +85,8 @@ def test_tolerates_traces_sampler_returning_a_boolean( ): sentry_init(traces_sampler=mock.Mock(return_value=traces_sampler_return_value)) - transaction = start_transaction(name="dogpark") - assert transaction.sampled is traces_sampler_return_value + with start_span(name="dogpark") as span: + assert span.sampled is traces_sampler_return_value @pytest.mark.parametrize("sampling_decision", [True, False]) @@ -110,8 +96,8 @@ def test_only_captures_transaction_when_sampled_is_true( sentry_init(traces_sampler=mock.Mock(return_value=sampling_decision)) events = capture_events() - transaction = start_transaction(name="dogpark") - transaction.finish() + with start_span(name="dogpark"): + pass assert len(events) == (1 if sampling_decision else 0) @@ -132,9 +118,9 @@ def test_prefers_traces_sampler_to_traces_sample_rate( traces_sampler=traces_sampler, ) - transaction = start_transaction(name="dogpark") - assert traces_sampler.called is True - assert transaction.sampled is traces_sampler_return_value + with start_span(name="dogpark") as span: + assert traces_sampler.called is True + assert span.sampled is traces_sampler_return_value @pytest.mark.parametrize("parent_sampling_decision", [True, False]) @@ -168,8 +154,8 @@ def test_traces_sampler_doesnt_overwrite_explicitly_passed_sampling_decision( traces_sampler = mock.Mock(return_value=not explicit_decision) sentry_init(traces_sampler=traces_sampler) - transaction = start_transaction(name="dogpark", sampled=explicit_decision) - assert transaction.sampled is explicit_decision + with start_span(name="dogpark", sampled=explicit_decision) as span: + assert span.sampled is explicit_decision @pytest.mark.parametrize("parent_sampling_decision", [True, False]) @@ -190,9 +176,7 @@ def test_inherits_parent_sampling_decision_when_traces_sampler_undefined( with mock.patch.object(random, "random", return_value=mock_random_value): with sentry_sdk.continue_trace({"sentry-trace": sentry_trace_header}): with start_span(name="dogpark") as span: - pass - - assert span.sampled is parent_sampling_decision + assert span.sampled is parent_sampling_decision @pytest.mark.parametrize("parent_sampling_decision", [True, False]) @@ -222,11 +206,10 @@ def test_passes_attributes_from_start_span_to_traces_sampler( traces_sampler = mock.Mock() sentry_init(traces_sampler=traces_sampler) - start_transaction(attributes={"dogs": "yes", "cats": "maybe"}) - - traces_sampler.assert_any_call( - DictionaryContaining({"dogs": "yes", "cats": "maybe"}) - ) + with start_span(attributes={"dogs": "yes", "cats": "maybe"}): + traces_sampler.assert_any_call( + DictionaryContaining({"dogs": "yes", "cats": "maybe"}) + ) def test_sample_rate_affects_errors(sentry_init, capture_events): @@ -261,9 +244,11 @@ def test_warns_and_sets_sampled_to_false_on_invalid_traces_sampler_return_value( sentry_init(traces_sampler=mock.Mock(return_value=traces_sampler_return_value)) with mock.patch.object(logger, "warning", mock.Mock()): - transaction = start_transaction(name="dogpark") - logger.warning.assert_any_call(StringContaining("Given sample rate is invalid")) - assert transaction.sampled is False + with start_span(name="dogpark") as span: + logger.warning.assert_any_call( + StringContaining("Given sample rate is invalid") + ) + assert span.sampled is False @pytest.mark.parametrize( @@ -288,9 +273,8 @@ def test_records_lost_event_only_if_traces_sample_rate_enabled( sentry_init(traces_sample_rate=traces_sample_rate) record_lost_event_calls = capture_record_lost_event_calls() - transaction = start_transaction(name="dogpark") - assert transaction.sampled is sampled_output - transaction.finish() + with start_span(name="dogpark") as span: + assert span.sampled is sampled_output # Use Counter because order of calls does not matter assert Counter(record_lost_event_calls) == Counter(expected_record_lost_event_calls) @@ -318,9 +302,8 @@ def test_records_lost_event_only_if_traces_sampler_enabled( sentry_init(traces_sampler=traces_sampler) record_lost_event_calls = capture_record_lost_event_calls() - transaction = start_transaction(name="dogpark") - assert transaction.sampled is sampled_output - transaction.finish() + with start_span(name="dogpark") as span: + assert span.sampled is sampled_output # Use Counter because order of calls does not matter assert Counter(record_lost_event_calls) == Counter(expected_record_lost_event_calls) diff --git a/tests/tracing/test_span_name.py b/tests/tracing/test_span_name.py index 9c1768990a..d7d3772727 100644 --- a/tests/tracing/test_span_name.py +++ b/tests/tracing/test_span_name.py @@ -1,27 +1,11 @@ -import pytest - import sentry_sdk -def test_start_span_description(sentry_init, capture_events): - sentry_init(traces_sample_rate=1.0) - events = capture_events() - - with sentry_sdk.start_transaction(name="hi"): - with pytest.deprecated_call(): - with sentry_sdk.start_span(op="foo", description="span-desc"): - ... - - (event,) = events - - assert event["spans"][0]["description"] == "span-desc" - - def test_start_span_name(sentry_init, capture_events): sentry_init(traces_sample_rate=1.0) events = capture_events() - with sentry_sdk.start_transaction(name="hi"): + with sentry_sdk.start_span(name="hi"): with sentry_sdk.start_span(op="foo", name="span-name"): ... @@ -30,26 +14,11 @@ def test_start_span_name(sentry_init, capture_events): assert event["spans"][0]["description"] == "span-name" -def test_start_child_description(sentry_init, capture_events): - sentry_init(traces_sample_rate=1.0) - events = capture_events() - - with sentry_sdk.start_transaction(name="hi"): - with pytest.deprecated_call(): - with sentry_sdk.start_span(op="foo", description="span-desc") as span: - with span.start_child(op="bar", description="child-desc"): - ... - - (event,) = events - - assert event["spans"][-1]["description"] == "child-desc" - - def test_start_child_name(sentry_init, capture_events): sentry_init(traces_sample_rate=1.0) events = capture_events() - with sentry_sdk.start_transaction(name="hi"): + with sentry_sdk.start_span(name="hi"): with sentry_sdk.start_span(op="foo", name="span-name") as span: with span.start_child(op="bar", name="child-name"): ... diff --git a/tests/tracing/test_span_origin.py b/tests/tracing/test_span_origin.py index 16635871b3..649f704b1b 100644 --- a/tests/tracing/test_span_origin.py +++ b/tests/tracing/test_span_origin.py @@ -1,11 +1,11 @@ -from sentry_sdk import start_transaction, start_span +from sentry_sdk import start_span def test_span_origin_manual(sentry_init, capture_events): sentry_init(traces_sample_rate=1.0) events = capture_events() - with start_transaction(name="hi"): + with start_span(name="hi"): with start_span(op="foo", name="bar"): pass @@ -20,11 +20,11 @@ def test_span_origin_custom(sentry_init, capture_events): sentry_init(traces_sample_rate=1.0) events = capture_events() - with start_transaction(name="hi"): + with start_span(name="hi"): with start_span(op="foo", name="bar", origin="foo.foo2.foo3"): pass - with start_transaction(name="ho", origin="ho.ho2.ho3"): + with start_span(name="ho", origin="ho.ho2.ho3"): with start_span(op="baz", name="qux", origin="baz.baz2.baz3"): pass