From 53516dff479603e50bf3ce28f79b7f5e2162c7b3 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Mon, 29 Jun 2026 13:13:36 +0200 Subject: [PATCH 1/5] feat: Set sentry.trace_lifecycle attr in span streaming --- sentry_sdk/traces.py | 1 + tests/tracing/test_span_streaming.py | 1 + 2 files changed, 2 insertions(+) diff --git a/sentry_sdk/traces.py b/sentry_sdk/traces.py index eaa4c67cb5..5ee7e8460b 100644 --- a/sentry_sdk/traces.py +++ b/sentry_sdk/traces.py @@ -275,6 +275,7 @@ def __init__( self._active: bool = active self._attributes: "Attributes" = { "sentry.origin": "manual", + "sentry.trace_lifecycle": "stream", } if attributes: diff --git a/tests/tracing/test_span_streaming.py b/tests/tracing/test_span_streaming.py index d379bbc9d7..7229eb6a05 100644 --- a/tests/tracing/test_span_streaming.py +++ b/tests/tracing/test_span_streaming.py @@ -1722,6 +1722,7 @@ def test_default_attributes(sentry_init, capture_envelopes): "sentry.dist": {"value": "1.0", "type": "string"}, "sentry.origin": {"value": "manual", "type": "string"}, "sentry.sdk.integrations": {"value": mock.ANY, "type": "array"}, + "sentry.trace_lifecycle": {"value": "stream", "type": "string"}, "process.runtime.name": { "type": "string", "value": mock.ANY, From 533c45c897f9e57da40ee3bb7048b0e31aa57bb0 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Mon, 29 Jun 2026 13:27:06 +0200 Subject: [PATCH 2/5] dont check irrelevant attrs --- tests/integrations/openai/test_openai.py | 16 ++---------- .../rust_tracing/test_rust_tracing.py | 25 ++++++------------- 2 files changed, 10 insertions(+), 31 deletions(-) diff --git a/tests/integrations/openai/test_openai.py b/tests/integrations/openai/test_openai.py index 1acd33374a..cc67ed5ac5 100644 --- a/tests/integrations/openai/test_openai.py +++ b/tests/integrations/openai/test_openai.py @@ -4987,16 +4987,6 @@ async def test_ai_client_span_streaming_responses_async_api( "sentry.environment": "production", "sentry.op": "gen_ai.responses", "sentry.origin": "auto.ai.openai", - "process.runtime.name": mock.ANY, - "process.runtime.version": mock.ANY, - "sentry.release": mock.ANY, - "sentry.sdk.name": "sentry.python", - "sentry.sdk.version": mock.ANY, - "sentry.segment.id": mock.ANY, - "sentry.segment.name": "openai tx", - "server.address": mock.ANY, - "thread.id": mock.ANY, - "thread.name": mock.ANY, } if expected_system_instructions is not None: @@ -5004,7 +4994,7 @@ async def test_ai_client_span_streaming_responses_async_api( expected_system_instructions ) - assert spans[0]["attributes"] == expected_data + assert expected_data < spans[0]["attributes"] else: events = capture_events() @@ -5050,8 +5040,6 @@ async def test_ai_client_span_streaming_responses_async_api( "gen_ai.usage.total_tokens": 30, "gen_ai.request.model": "gpt-4o", "gen_ai.response.text": "hello world", - "thread.id": mock.ANY, - "thread.name": mock.ANY, } if expected_system_instructions is not None: @@ -5059,7 +5047,7 @@ async def test_ai_client_span_streaming_responses_async_api( expected_system_instructions ) - assert spans[0]["data"] == expected_data + assert expected_data < spans[0]["data"] @pytest.mark.parametrize("span_streaming", [True, False]) diff --git a/tests/integrations/rust_tracing/test_rust_tracing.py b/tests/integrations/rust_tracing/test_rust_tracing.py index 31d5686c5a..8b86aa2df3 100644 --- a/tests/integrations/rust_tracing/test_rust_tracing.py +++ b/tests/integrations/rust_tracing/test_rust_tracing.py @@ -1,6 +1,5 @@ from string import Template from typing import Dict -from unittest import mock import pytest @@ -761,26 +760,22 @@ def test_include_tracing_fields( span_after_record = sentry_sdk.traces.get_current_span()._to_json() if tracing_fields_expected: - assert span_after_record["attributes"] == { - "thread.id": mock.ANY, - "thread.name": mock.ANY, + assert { "sentry.op": "function", "sentry.origin": "auto.function.rust_tracing.test_record", "use_memoized": True, "version": "memoized", "index": 10, - } + } < span_after_record["attributes"] else: - assert span_after_record["attributes"] == { - "thread.id": mock.ANY, - "thread.name": mock.ANY, + assert { "sentry.op": "function", "sentry.origin": "auto.function.rust_tracing.test_record", "use_memoized": "[Filtered]", "version": "[Filtered]", "index": "[Filtered]", - } + } < span_after_record["attributes"] else: with start_transaction(): rust_tracing.new_span(RustTracingLevel.Info, 3) @@ -796,19 +791,15 @@ def test_include_tracing_fields( span_after_record = sentry_sdk.get_current_span().to_json() if tracing_fields_expected: - assert span_after_record["data"] == { - "thread.id": mock.ANY, - "thread.name": mock.ANY, + assert { "use_memoized": True, "version": "memoized", "index": 10, - } + } < span_after_record["data"] else: - assert span_after_record["data"] == { - "thread.id": mock.ANY, - "thread.name": mock.ANY, + assert { "use_memoized": "[Filtered]", "version": "[Filtered]", "index": "[Filtered]", - } + } < span_after_record["data"] From adaf93f7a7a871881027a375e28c016946ad1159 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Mon, 29 Jun 2026 13:35:47 +0200 Subject: [PATCH 3/5] . --- tests/integrations/openai/test_openai.py | 6 ++- .../rust_tracing/test_rust_tracing.py | 46 +++++++++---------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/tests/integrations/openai/test_openai.py b/tests/integrations/openai/test_openai.py index cc67ed5ac5..25639303b9 100644 --- a/tests/integrations/openai/test_openai.py +++ b/tests/integrations/openai/test_openai.py @@ -4994,7 +4994,8 @@ async def test_ai_client_span_streaming_responses_async_api( expected_system_instructions ) - assert expected_data < spans[0]["attributes"] + for attr, value in expected_data.items(): + assert spans[0]["attributes"][attr] == value else: events = capture_events() @@ -5047,7 +5048,8 @@ async def test_ai_client_span_streaming_responses_async_api( expected_system_instructions ) - assert expected_data < spans[0]["data"] + for attr, value in expected_data.items(): + assert spans[0]["data"][attr] == value @pytest.mark.parametrize("span_streaming", [True, False]) diff --git a/tests/integrations/rust_tracing/test_rust_tracing.py b/tests/integrations/rust_tracing/test_rust_tracing.py index 8b86aa2df3..3635e8cb94 100644 --- a/tests/integrations/rust_tracing/test_rust_tracing.py +++ b/tests/integrations/rust_tracing/test_rust_tracing.py @@ -760,22 +760,24 @@ def test_include_tracing_fields( span_after_record = sentry_sdk.traces.get_current_span()._to_json() if tracing_fields_expected: - assert { - "sentry.op": "function", - "sentry.origin": "auto.function.rust_tracing.test_record", - "use_memoized": True, - "version": "memoized", - "index": 10, - } < span_after_record["attributes"] + assert span_after_record["attributes"]["sentry.op"] == "function" + assert ( + span_after_record["attributes"]["sentry.origin"] + == "auto.function.rust_tracing.test_record" + ) + assert span_after_record["attributes"]["use_memoized"] is True + assert span_after_record["attributes"]["version"] == "memoized" + assert span_after_record["attributes"]["index"] == 10 else: - assert { - "sentry.op": "function", - "sentry.origin": "auto.function.rust_tracing.test_record", - "use_memoized": "[Filtered]", - "version": "[Filtered]", - "index": "[Filtered]", - } < span_after_record["attributes"] + assert span_after_record["attributes"]["sentry.op"] == "function" + assert ( + span_after_record["attributes"]["sentry.origin"] + == "auto.function.rust_tracing.test_record" + ) + assert span_after_record["attributes"]["use_memoized"] == "[Filtered]" + assert span_after_record["attributes"]["version"] == "[Filtered]" + assert span_after_record["attributes"]["index"] == "[Filtered]" else: with start_transaction(): rust_tracing.new_span(RustTracingLevel.Info, 3) @@ -791,15 +793,11 @@ def test_include_tracing_fields( span_after_record = sentry_sdk.get_current_span().to_json() if tracing_fields_expected: - assert { - "use_memoized": True, - "version": "memoized", - "index": 10, - } < span_after_record["data"] + assert span_after_record["data"]["use_memoized"] is True + assert span_after_record["data"]["version"] == "memoized" + assert span_after_record["data"]["index"] == 10 else: - assert { - "use_memoized": "[Filtered]", - "version": "[Filtered]", - "index": "[Filtered]", - } < span_after_record["data"] + assert span_after_record["data"]["use_memoized"] == "[Filtered]" + assert span_after_record["data"]["version"] == "[Filtered]" + assert span_after_record["data"]["index"] == "[Filtered]" From 01a97977c4acf4f61287b5ad29bccd024a72a021 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Mon, 29 Jun 2026 13:53:26 +0200 Subject: [PATCH 4/5] . --- tests/integrations/openai/test_openai.py | 107 +++++++---------------- 1 file changed, 30 insertions(+), 77 deletions(-) diff --git a/tests/integrations/openai/test_openai.py b/tests/integrations/openai/test_openai.py index 25639303b9..8c2e100712 100644 --- a/tests/integrations/openai/test_openai.py +++ b/tests/integrations/openai/test_openai.py @@ -3818,7 +3818,7 @@ def test_ai_client_span_responses_api_no_pii( spans = [item.payload for item in items] assert len(spans) == 2 - assert spans[0]["attributes"] == { + expected_attributes = { "gen_ai.operation.name": "responses", "gen_ai.request.max_tokens": 100, "gen_ai.request.temperature": 0.7, @@ -3832,24 +3832,18 @@ def test_ai_client_span_responses_api_no_pii( "gen_ai.usage.output_tokens": 10, "gen_ai.usage.output_tokens.reasoning": 8, "gen_ai.usage.total_tokens": 30, - "process.runtime.name": mock.ANY, - "process.runtime.version": mock.ANY, - "sentry.environment": "production", "sentry.op": "gen_ai.responses", "sentry.origin": "auto.ai.openai", - "sentry.release": mock.ANY, - "sentry.sdk.name": "sentry.python", - "sentry.sdk.version": mock.ANY, - "sentry.segment.id": mock.ANY, "sentry.segment.name": "openai tx", - "server.address": mock.ANY, - "thread.id": mock.ANY, - "thread.name": mock.ANY, } + for attr, value in expected_attributes.items(): + assert spans[0]["attributes"][attr] == value + assert "gen_ai.system_instructions" not in spans[0]["attributes"] assert "gen_ai.request.messages" not in spans[0]["attributes"] assert "gen_ai.response.text" not in spans[0]["attributes"] + elif stream_gen_ai_spans: items = capture_items("span") @@ -3866,7 +3860,7 @@ def test_ai_client_span_responses_api_no_pii( spans = [item.payload for item in items] assert len(spans) == 1 - assert spans[0]["attributes"] == { + expected_attributes = { "gen_ai.operation.name": "responses", "gen_ai.request.max_tokens": 100, "gen_ai.request.temperature": 0.7, @@ -3880,21 +3874,14 @@ def test_ai_client_span_responses_api_no_pii( "gen_ai.usage.output_tokens": 10, "gen_ai.usage.output_tokens.reasoning": 8, "gen_ai.usage.total_tokens": 30, - "process.runtime.name": mock.ANY, - "process.runtime.version": mock.ANY, - "sentry.environment": "production", "sentry.op": "gen_ai.responses", "sentry.origin": "auto.ai.openai", - "sentry.release": mock.ANY, - "sentry.sdk.name": "sentry.python", - "sentry.sdk.version": mock.ANY, - "sentry.segment.id": mock.ANY, "sentry.segment.name": "openai tx", - "server.address": mock.ANY, - "thread.id": mock.ANY, - "thread.name": mock.ANY, } + for attr, value in expected_attributes.items(): + assert spans[0]["attributes"][attr] == value + assert "gen_ai.system_instructions" not in spans[0]["attributes"] assert "gen_ai.request.messages" not in spans[0]["attributes"] assert "gen_ai.response.text" not in spans[0]["attributes"] @@ -3917,7 +3904,7 @@ def test_ai_client_span_responses_api_no_pii( assert len(spans) == 1 assert spans[0]["op"] == "gen_ai.responses" assert spans[0]["origin"] == "auto.ai.openai" - assert spans[0]["data"] == { + expected_data = { "gen_ai.operation.name": "responses", "gen_ai.request.max_tokens": 100, "gen_ai.request.temperature": 0.7, @@ -3931,10 +3918,11 @@ def test_ai_client_span_responses_api_no_pii( "gen_ai.usage.output_tokens": 10, "gen_ai.usage.output_tokens.reasoning": 8, "gen_ai.usage.total_tokens": 30, - "thread.id": mock.ANY, - "thread.name": mock.ANY, } + for key, value in expected_data.items(): + assert spans[0]["data"][key] == value + assert "gen_ai.system_instructions" not in spans[0]["data"] assert "gen_ai.request.messages" not in spans[0]["data"] assert "gen_ai.response.text" not in spans[0]["data"] @@ -4143,19 +4131,7 @@ def test_ai_client_span_responses_api( "gen_ai.request.messages": safe_serialize(expected_request_messages), "gen_ai.request.model": "gpt-4o", "gen_ai.response.text": "the model response", - "process.runtime.name": mock.ANY, - "process.runtime.version": mock.ANY, - "sentry.environment": "production", - "sentry.op": "gen_ai.responses", - "sentry.origin": "auto.ai.openai", - "sentry.release": mock.ANY, - "sentry.sdk.name": "sentry.python", - "sentry.sdk.version": mock.ANY, - "sentry.segment.id": mock.ANY, "sentry.segment.name": "openai tx", - "server.address": mock.ANY, - "thread.id": mock.ANY, - "thread.name": mock.ANY, } if expected_system_instructions is not None: @@ -4163,7 +4139,9 @@ def test_ai_client_span_responses_api( expected_system_instructions ) - assert spans[0]["attributes"] == expected_data + for attr, value in expected_data.items(): + assert spans[0]["attributes"][attr] == value + elif stream_gen_ai_spans: items = capture_items("span") @@ -4197,19 +4175,9 @@ def test_ai_client_span_responses_api( "gen_ai.request.messages": safe_serialize(expected_request_messages), "gen_ai.request.model": "gpt-4o", "gen_ai.response.text": "the model response", - "process.runtime.name": mock.ANY, - "process.runtime.version": mock.ANY, - "sentry.environment": "production", "sentry.op": "gen_ai.responses", "sentry.origin": "auto.ai.openai", - "sentry.release": mock.ANY, - "sentry.sdk.name": "sentry.python", - "sentry.sdk.version": mock.ANY, - "sentry.segment.id": mock.ANY, "sentry.segment.name": "openai tx", - "server.address": mock.ANY, - "thread.id": mock.ANY, - "thread.name": mock.ANY, } if expected_system_instructions is not None: @@ -4217,7 +4185,9 @@ def test_ai_client_span_responses_api( expected_system_instructions ) - assert spans[0]["attributes"] == expected_data + for attr, value in expected_data.items(): + assert spans[0]["attributes"][attr] == value + else: events = capture_events() @@ -4254,8 +4224,6 @@ def test_ai_client_span_responses_api( "gen_ai.request.messages": safe_serialize(expected_request_messages[-1:]), "gen_ai.request.model": "gpt-4o", "gen_ai.response.text": "the model response", - "thread.id": mock.ANY, - "thread.name": mock.ANY, } if expected_system_instructions is not None: @@ -4263,7 +4231,8 @@ def test_ai_client_span_responses_api( expected_system_instructions ) - assert spans[0]["data"] == expected_data + for attr, value in expected_data.items(): + assert spans[0]["data"][attr] == value @pytest.mark.parametrize("span_streaming", [True, False]) @@ -4634,19 +4603,9 @@ async def test_ai_client_span_responses_async_api( "gen_ai.usage.output_tokens.reasoning": 8, "gen_ai.usage.total_tokens": 30, "gen_ai.response.text": "the model response", - "process.runtime.name": mock.ANY, - "process.runtime.version": mock.ANY, - "sentry.environment": "production", "sentry.op": "gen_ai.responses", "sentry.origin": "auto.ai.openai", - "sentry.release": mock.ANY, - "sentry.sdk.name": "sentry.python", - "sentry.sdk.version": mock.ANY, - "sentry.segment.id": mock.ANY, "sentry.segment.name": "openai tx", - "server.address": mock.ANY, - "thread.id": mock.ANY, - "thread.name": mock.ANY, } if expected_system_instructions is not None: @@ -4654,7 +4613,9 @@ async def test_ai_client_span_responses_async_api( expected_system_instructions ) - assert spans[0]["attributes"] == expected_data + for attr, value in expected_data.items(): + assert spans[0]["attributes"][attr] == value + elif stream_gen_ai_spans: items = capture_items("span") @@ -4688,19 +4649,9 @@ async def test_ai_client_span_responses_async_api( "gen_ai.usage.output_tokens.reasoning": 8, "gen_ai.usage.total_tokens": 30, "gen_ai.response.text": "the model response", - "process.runtime.name": mock.ANY, - "process.runtime.version": mock.ANY, - "sentry.environment": "production", "sentry.op": "gen_ai.responses", "sentry.origin": "auto.ai.openai", - "sentry.release": mock.ANY, - "sentry.sdk.name": "sentry.python", - "sentry.sdk.version": mock.ANY, - "sentry.segment.id": mock.ANY, "sentry.segment.name": "openai tx", - "server.address": mock.ANY, - "thread.id": mock.ANY, - "thread.name": mock.ANY, } if expected_system_instructions is not None: @@ -4708,7 +4659,9 @@ async def test_ai_client_span_responses_async_api( expected_system_instructions ) - assert spans[0]["attributes"] == expected_data + for attr, value in expected_data.items(): + assert spans[0]["attributes"][attr] == value + else: events = capture_events() @@ -4745,8 +4698,6 @@ async def test_ai_client_span_responses_async_api( "gen_ai.usage.output_tokens.reasoning": 8, "gen_ai.usage.total_tokens": 30, "gen_ai.response.text": "the model response", - "thread.id": mock.ANY, - "thread.name": mock.ANY, } if expected_system_instructions is not None: @@ -4754,7 +4705,8 @@ async def test_ai_client_span_responses_async_api( expected_system_instructions ) - assert spans[0]["data"] == expected_data + for attr, value in expected_data.items(): + assert spans[0]["data"][attr] == value @pytest.mark.parametrize("span_streaming", [True, False]) @@ -4996,6 +4948,7 @@ async def test_ai_client_span_streaming_responses_async_api( for attr, value in expected_data.items(): assert spans[0]["attributes"][attr] == value + else: events = capture_events() From da787414746e9299b43b413fd0d7fa14ae5026b6 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Mon, 29 Jun 2026 14:01:16 +0200 Subject: [PATCH 5/5] . --- tests/integrations/openai/test_openai.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/integrations/openai/test_openai.py b/tests/integrations/openai/test_openai.py index 8c2e100712..8182c09d12 100644 --- a/tests/integrations/openai/test_openai.py +++ b/tests/integrations/openai/test_openai.py @@ -4131,6 +4131,8 @@ def test_ai_client_span_responses_api( "gen_ai.request.messages": safe_serialize(expected_request_messages), "gen_ai.request.model": "gpt-4o", "gen_ai.response.text": "the model response", + "sentry.op": "gen_ai.responses", + "sentry.origin": "auto.ai.openai", "sentry.segment.name": "openai tx", } @@ -4939,6 +4941,7 @@ async def test_ai_client_span_streaming_responses_async_api( "sentry.environment": "production", "sentry.op": "gen_ai.responses", "sentry.origin": "auto.ai.openai", + "sentry.segment.name": "openai tx", } if expected_system_instructions is not None: