From 945a533e83e3d695d0360cf46547506b9d42f7dd Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Fri, 24 Jul 2026 14:24:49 +0200 Subject: [PATCH] test: Add span streaming DSC tests --- tests/test_dsc.py | 454 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 454 insertions(+) diff --git a/tests/test_dsc.py b/tests/test_dsc.py index c233fa0c5b..7aa4669c60 100644 --- a/tests/test_dsc.py +++ b/tests/test_dsc.py @@ -71,6 +71,64 @@ def test_dsc_head_of_trace(sentry_init, capture_envelopes): assert envelope_trace_header["transaction"] == "foo" +def test_dsc_head_of_trace_span_streaming(sentry_init, capture_envelopes): + """ + Our service is the head of the trace (it starts a new trace) + and sends a segment to Sentry. + """ + sentry_init( + dsn="https://mysecret@o1234.ingest.sentry.io/12312012", + release="myapp@0.0.1", + environment="canary", + traces_sample_rate=1.0, + trace_lifecycle="stream", + transport=TestTransportWithOptions, + ) + envelopes = capture_envelopes() + + # We start a new segment + with sentry_sdk.traces.start_span(name="foo"): + pass + + sentry_sdk.flush() + + assert len(envelopes) == 1 + + segment_envelope = envelopes[0] + envelope_trace_header = segment_envelope.headers["trace"] + + assert "trace_id" in envelope_trace_header + assert type(envelope_trace_header["trace_id"]) == str + + assert "public_key" in envelope_trace_header + assert type(envelope_trace_header["public_key"]) == str + assert envelope_trace_header["public_key"] == "mysecret" + + assert "org_id" in envelope_trace_header + assert type(envelope_trace_header["org_id"]) == str + assert envelope_trace_header["org_id"] == "1234" + + assert "sample_rate" in envelope_trace_header + assert type(envelope_trace_header["sample_rate"]) == str + assert envelope_trace_header["sample_rate"] == "1.0" + + assert "sampled" in envelope_trace_header + assert type(envelope_trace_header["sampled"]) == str + assert envelope_trace_header["sampled"] == "true" + + assert "release" in envelope_trace_header + assert type(envelope_trace_header["release"]) == str + assert envelope_trace_header["release"] == "myapp@0.0.1" + + assert "environment" in envelope_trace_header + assert type(envelope_trace_header["environment"]) == str + assert envelope_trace_header["environment"] == "canary" + + assert "transaction" in envelope_trace_header + assert type(envelope_trace_header["transaction"]) == str + assert envelope_trace_header["transaction"] == "foo" + + def test_dsc_head_of_trace_uses_custom_org_id(sentry_init, capture_envelopes): """ Our service is the head of the trace (it starts a new trace) @@ -100,6 +158,40 @@ def test_dsc_head_of_trace_uses_custom_org_id(sentry_init, capture_envelopes): assert envelope_trace_header["org_id"] == "9999" +def test_dsc_head_of_trace_uses_custom_org_id_span_streaming( + sentry_init, capture_envelopes +): + """ + Our service is the head of the trace (it starts a new trace) + and sends a segment to Sentry. + """ + sentry_init( + dsn="https://mysecret@o1234.ingest.sentry.io/12312012", + org_id="9999", + release="myapp@0.0.1", + environment="canary", + traces_sample_rate=1.0, + trace_lifecycle="stream", + transport=TestTransportWithOptions, + ) + envelopes = capture_envelopes() + + # We start a new segment + with sentry_sdk.traces.start_span(name="foo"): + pass + + sentry_sdk.flush() + + assert len(envelopes) == 1 + + segment_envelope = envelopes[0] + envelope_trace_header = segment_envelope.headers["trace"] + + assert "org_id" in envelope_trace_header + assert type(envelope_trace_header["org_id"]) == str + assert envelope_trace_header["org_id"] == "9999" + + def test_dsc_continuation_of_trace(sentry_init, capture_envelopes): """ Another service calls our service and passes tracing information to us. @@ -171,6 +263,80 @@ def test_dsc_continuation_of_trace(sentry_init, capture_envelopes): assert envelope_trace_header["transaction"] == "bar" +def test_dsc_continuation_of_trace_span_streaming(sentry_init, capture_envelopes): + """ + Another service calls our service and passes tracing information to us. + Our service is continuing the trace and sends a segment to Sentry. + """ + sentry_init( + dsn="https://mysecret@o1234.ingest.sentry.io/12312012", + release="myapp@0.0.1", + environment="canary", + traces_sample_rate=1.0, + trace_lifecycle="stream", + transport=TestTransportWithOptions, + ) + envelopes = capture_envelopes() + + # This is what the upstream service sends us + sentry_trace = "771a43a4192642f0b136d5159a501700-1234567890abcdef-1" + baggage = ( + "other-vendor-value-1=foo;bar;baz, " + "sentry-trace_id=771a43a4192642f0b136d5159a501700, " + "sentry-public_key=frontendpublickey, " + "sentry-sample_rate=0.01337, " + "sentry-sampled=true, " + "sentry-release=myfrontend@1.2.3, " + "sentry-environment=bird, " + "sentry-transaction=bar, " + "other-vendor-value-2=foo;bar;" + ) + incoming_http_headers = { + "HTTP_SENTRY_TRACE": sentry_trace, + "HTTP_BAGGAGE": baggage, + } + + # We continue the incoming trace and start a new segment + sentry_sdk.continue_trace(incoming_http_headers) + with sentry_sdk.traces.start_span(name="foo"): + pass + + sentry_sdk.flush() + + assert len(envelopes) == 1 + + segment_envelope = envelopes[0] + envelope_trace_header = segment_envelope.headers["trace"] + + assert "trace_id" in envelope_trace_header + assert type(envelope_trace_header["trace_id"]) == str + assert envelope_trace_header["trace_id"] == "771a43a4192642f0b136d5159a501700" + + assert "public_key" in envelope_trace_header + assert type(envelope_trace_header["public_key"]) == str + assert envelope_trace_header["public_key"] == "frontendpublickey" + + assert "sample_rate" in envelope_trace_header + assert type(envelope_trace_header["sample_rate"]) == str + assert envelope_trace_header["sample_rate"] == "1.0" + + assert "sampled" in envelope_trace_header + assert type(envelope_trace_header["sampled"]) == str + assert envelope_trace_header["sampled"] == "true" + + assert "release" in envelope_trace_header + assert type(envelope_trace_header["release"]) == str + assert envelope_trace_header["release"] == "myfrontend@1.2.3" + + assert "environment" in envelope_trace_header + assert type(envelope_trace_header["environment"]) == str + assert envelope_trace_header["environment"] == "bird" + + assert "transaction" in envelope_trace_header + assert type(envelope_trace_header["transaction"]) == str + assert envelope_trace_header["transaction"] == "bar" + + def test_dsc_continuation_of_trace_sample_rate_changed_in_traces_sampler( sentry_init, capture_envelopes ): @@ -250,6 +416,88 @@ def my_traces_sampler(sampling_context): assert envelope_trace_header["transaction"] == "bar" +def test_dsc_continuation_of_trace_sample_rate_changed_in_traces_sampler_span_streaming( + sentry_init, capture_envelopes +): + """ + Another service calls our service and passes tracing information to us. + Our service is continuing the trace, but modifies the sample rate. + The DSC propagated further should contain the updated sample rate. + """ + + def my_traces_sampler(sampling_context): + return 0.25 + + sentry_init( + dsn="https://mysecret@o1234.ingest.sentry.io/12312012", + release="myapp@0.0.1", + environment="canary", + traces_sampler=my_traces_sampler, + trace_lifecycle="stream", + transport=TestTransportWithOptions, + ) + envelopes = capture_envelopes() + + # This is what the upstream service sends us + sentry_trace = "771a43a4192642f0b136d5159a501700-1234567890abcdef-1" + baggage = ( + "other-vendor-value-1=foo;bar;baz, " + "sentry-trace_id=771a43a4192642f0b136d5159a501700, " + "sentry-public_key=frontendpublickey, " + "sentry-sample_rate=1.0, " + "sentry-sampled=true, " + "sentry-release=myfrontend@1.2.3, " + "sentry-environment=bird, " + "sentry-transaction=bar, " + "other-vendor-value-2=foo;bar;" + ) + incoming_http_headers = { + "HTTP_SENTRY_TRACE": sentry_trace, + "HTTP_BAGGAGE": baggage, + } + + # We continue the incoming trace and start a new segment + with mock.patch("sentry_sdk.tracing_utils.Random.randrange", return_value=125000): + sentry_sdk.continue_trace(incoming_http_headers) + with sentry_sdk.traces.start_span(name="foo"): + pass + + sentry_sdk.flush() + + assert len(envelopes) == 1 + + segment_envelope = envelopes[0] + envelope_trace_header = segment_envelope.headers["trace"] + + assert "trace_id" in envelope_trace_header + assert type(envelope_trace_header["trace_id"]) == str + assert envelope_trace_header["trace_id"] == "771a43a4192642f0b136d5159a501700" + + assert "public_key" in envelope_trace_header + assert type(envelope_trace_header["public_key"]) == str + assert envelope_trace_header["public_key"] == "frontendpublickey" + + assert "sample_rate" in envelope_trace_header + assert type(envelope_trace_header["sample_rate"]) == str + assert envelope_trace_header["sample_rate"] == "0.25" + + assert "sampled" in envelope_trace_header + assert type(envelope_trace_header["sampled"]) == str + assert envelope_trace_header["sampled"] == "true" + + assert "release" in envelope_trace_header + assert type(envelope_trace_header["release"]) == str + assert envelope_trace_header["release"] == "myfrontend@1.2.3" + + assert "environment" in envelope_trace_header + assert type(envelope_trace_header["environment"]) == str + assert envelope_trace_header["environment"] == "bird" + + assert "transaction" in envelope_trace_header + assert type(envelope_trace_header["transaction"]) == str + assert envelope_trace_header["transaction"] == "bar" + + def test_dsc_issue(sentry_init, capture_envelopes): """ Our service is a standalone service that does not have tracing enabled. Just uses Sentry for error reporting. @@ -300,6 +548,57 @@ def test_dsc_issue(sentry_init, capture_envelopes): assert "transaction" not in envelope_trace_header +def test_dsc_issue_span_streaming(sentry_init, capture_envelopes): + """ + Our service is a standalone service that does not have tracing enabled. Just uses Sentry for error reporting. + """ + sentry_init( + dsn="https://mysecret@o1234.ingest.sentry.io/12312012", + release="myapp@0.0.1", + environment="canary", + trace_lifecycle="stream", + transport=TestTransportWithOptions, + ) + envelopes = capture_envelopes() + + # No segment is started, just an error is captured + try: + 1 / 0 + except ZeroDivisionError as exp: + sentry_sdk.capture_exception(exp) + + assert len(envelopes) == 1 + + error_envelope = envelopes[0] + + envelope_trace_header = error_envelope.headers["trace"] + + assert "trace_id" in envelope_trace_header + assert type(envelope_trace_header["trace_id"]) == str + + assert "public_key" in envelope_trace_header + assert type(envelope_trace_header["public_key"]) == str + assert envelope_trace_header["public_key"] == "mysecret" + + assert "org_id" in envelope_trace_header + assert type(envelope_trace_header["org_id"]) == str + assert envelope_trace_header["org_id"] == "1234" + + assert "sample_rate" not in envelope_trace_header + + assert "sampled" not in envelope_trace_header + + assert "release" in envelope_trace_header + assert type(envelope_trace_header["release"]) == str + assert envelope_trace_header["release"] == "myapp@0.0.1" + + assert "environment" in envelope_trace_header + assert type(envelope_trace_header["environment"]) == str + assert envelope_trace_header["environment"] == "canary" + + assert "transaction" not in envelope_trace_header + + def test_dsc_issue_with_tracing(sentry_init, capture_envelopes): """ Our service has tracing enabled and an error occurs in an transaction. @@ -361,6 +660,70 @@ def test_dsc_issue_with_tracing(sentry_init, capture_envelopes): assert envelope_trace_header["transaction"] == "foo" +def test_dsc_issue_with_tracing_span_streaming(sentry_init, capture_envelopes): + """ + Our service has tracing enabled and an error occurs in a segment. + Envelopes containing errors also have the same DSC as the segment envelopes. + """ + sentry_init( + dsn="https://mysecret@o1234.ingest.sentry.io/12312012", + release="myapp@0.0.1", + environment="canary", + traces_sample_rate=1.0, + trace_lifecycle="stream", + transport=TestTransportWithOptions, + ) + envelopes = capture_envelopes() + + # We start a new segment and an error occurs + with sentry_sdk.traces.start_span(name="foo"): + try: + 1 / 0 + except ZeroDivisionError as exp: + sentry_sdk.capture_exception(exp) + + sentry_sdk.flush() + + assert len(envelopes) == 2 + + error_envelope, segment_envelope = envelopes + + assert error_envelope.headers["trace"] == segment_envelope.headers["trace"] + + envelope_trace_header = error_envelope.headers["trace"] + + assert "trace_id" in envelope_trace_header + assert type(envelope_trace_header["trace_id"]) == str + + assert "public_key" in envelope_trace_header + assert type(envelope_trace_header["public_key"]) == str + assert envelope_trace_header["public_key"] == "mysecret" + + assert "org_id" in envelope_trace_header + assert type(envelope_trace_header["org_id"]) == str + assert envelope_trace_header["org_id"] == "1234" + + assert "sample_rate" in envelope_trace_header + assert envelope_trace_header["sample_rate"] == "1.0" + assert type(envelope_trace_header["sample_rate"]) == str + + assert "sampled" in envelope_trace_header + assert type(envelope_trace_header["sampled"]) == str + assert envelope_trace_header["sampled"] == "true" + + assert "release" in envelope_trace_header + assert type(envelope_trace_header["release"]) == str + assert envelope_trace_header["release"] == "myapp@0.0.1" + + assert "environment" in envelope_trace_header + assert type(envelope_trace_header["environment"]) == str + assert envelope_trace_header["environment"] == "canary" + + assert "transaction" in envelope_trace_header + assert type(envelope_trace_header["transaction"]) == str + assert envelope_trace_header["transaction"] == "foo" + + @pytest.mark.parametrize( "traces_sample_rate", [ @@ -447,3 +810,94 @@ def test_dsc_issue_twp(sentry_init, capture_envelopes, traces_sample_rate): assert "transaction" in envelope_trace_header assert type(envelope_trace_header["transaction"]) == str assert envelope_trace_header["transaction"] == "bar" + + +@pytest.mark.parametrize( + "traces_sample_rate", + [ + 0, # no traces will be started, but if incoming traces will be continued (by our instrumentations, not happening in this test) + None, # no tracing at all. This service will never create segments. + ], +) +def test_dsc_issue_twp_span_streaming( + sentry_init, capture_envelopes, traces_sample_rate +): + """ + Our service does not have tracing enabled, but we receive tracing information from an upstream service. + Error envelopes still contain a DSC. This is called "tracing without performance" or TWP for short. + + This way if I have three services A, B, and C, and A and C have tracing enabled, but B does not, + we still can see the full trace in Sentry, and associate errors send by service B to Sentry. + (This test would be service B in this scenario) + """ + sentry_init( + dsn="https://mysecret@o1234.ingest.sentry.io/12312012", + release="myapp@0.0.1", + environment="canary", + traces_sample_rate=traces_sample_rate, + trace_lifecycle="stream", + transport=TestTransportWithOptions, + ) + envelopes = capture_envelopes() + + # This is what the upstream service sends us + sentry_trace = "771a43a4192642f0b136d5159a501700-1234567890abcdef-1" + baggage = ( + "other-vendor-value-1=foo;bar;baz, " + "sentry-trace_id=771a43a4192642f0b136d5159a501700, " + "sentry-public_key=frontendpublickey, " + "sentry-sample_rate=0.01337, " + "sentry-sampled=true, " + "sentry-release=myfrontend@1.2.3, " + "sentry-environment=bird, " + "sentry-transaction=bar, " + "other-vendor-value-2=foo;bar;" + ) + incoming_http_headers = { + "HTTP_SENTRY_TRACE": sentry_trace, + "HTTP_BAGGAGE": baggage, + } + + # We continue the trace (meaning: saving the incoming trace information on the scope) + # but in this test, we do not start a segment. + sentry_sdk.continue_trace(incoming_http_headers) + + # No segment is started, just an error is captured + try: + 1 / 0 + except ZeroDivisionError as exp: + sentry_sdk.capture_exception(exp) + + assert len(envelopes) == 1 + + error_envelope = envelopes[0] + + envelope_trace_header = error_envelope.headers["trace"] + + assert "trace_id" in envelope_trace_header + assert type(envelope_trace_header["trace_id"]) == str + assert envelope_trace_header["trace_id"] == "771a43a4192642f0b136d5159a501700" + + assert "public_key" in envelope_trace_header + assert type(envelope_trace_header["public_key"]) == str + assert envelope_trace_header["public_key"] == "frontendpublickey" + + assert "sample_rate" in envelope_trace_header + assert type(envelope_trace_header["sample_rate"]) == str + assert envelope_trace_header["sample_rate"] == "0.01337" + + assert "sampled" in envelope_trace_header + assert type(envelope_trace_header["sampled"]) == str + assert envelope_trace_header["sampled"] == "true" + + assert "release" in envelope_trace_header + assert type(envelope_trace_header["release"]) == str + assert envelope_trace_header["release"] == "myfrontend@1.2.3" + + assert "environment" in envelope_trace_header + assert type(envelope_trace_header["environment"]) == str + assert envelope_trace_header["environment"] == "bird" + + assert "transaction" in envelope_trace_header + assert type(envelope_trace_header["transaction"]) == str + assert envelope_trace_header["transaction"] == "bar"