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/integrations/openai/test_openai.py b/tests/integrations/openai/test_openai.py index 1acd33374a..8182c09d12 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,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: @@ -4163,7 +4141,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 +4177,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 +4187,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 +4226,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 +4233,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 +4605,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 +4615,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 +4651,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 +4661,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 +4700,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 +4707,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]) @@ -4987,16 +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", - "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 +4949,9 @@ async def test_ai_client_span_streaming_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() @@ -5050,8 +4997,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 +5004,8 @@ async def test_ai_client_span_streaming_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]) diff --git a/tests/integrations/rust_tracing/test_rust_tracing.py b/tests/integrations/rust_tracing/test_rust_tracing.py index 31d5686c5a..3635e8cb94 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,24 @@ 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, - "sentry.op": "function", - "sentry.origin": "auto.function.rust_tracing.test_record", - "use_memoized": True, - "version": "memoized", - "index": 10, - } + 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 span_after_record["attributes"] == { - "thread.id": mock.ANY, - "thread.name": mock.ANY, - "sentry.op": "function", - "sentry.origin": "auto.function.rust_tracing.test_record", - "use_memoized": "[Filtered]", - "version": "[Filtered]", - "index": "[Filtered]", - } + 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) @@ -796,19 +793,11 @@ 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, - "use_memoized": True, - "version": "memoized", - "index": 10, - } + 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 span_after_record["data"] == { - "thread.id": mock.ANY, - "thread.name": mock.ANY, - "use_memoized": "[Filtered]", - "version": "[Filtered]", - "index": "[Filtered]", - } + assert span_after_record["data"]["use_memoized"] == "[Filtered]" + assert span_after_record["data"]["version"] == "[Filtered]" + assert span_after_record["data"]["index"] == "[Filtered]" 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,