From ea43e8719826590f1e0c4f0b9c8e0020e413ca9c Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Wed, 29 Oct 2025 09:19:10 +0100 Subject: [PATCH 1/8] feat(spans): is_remote -> is_segment --- src/sentry/spans/consumers/process/factory.py | 7 ++++++- src/sentry/spans/consumers/process_segments/convert.py | 1 + tests/sentry/spans/consumers/process/__init__.py | 2 +- tests/sentry/spans/consumers/process/test_consumer.py | 4 ++-- .../spans/consumers/process_segments/test_convert.py | 2 +- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/sentry/spans/consumers/process/factory.py b/src/sentry/spans/consumers/process/factory.py index 9020f7dbf5c09e..cd92ad0bc82394 100644 --- a/src/sentry/spans/consumers/process/factory.py +++ b/src/sentry/spans/consumers/process/factory.py @@ -202,7 +202,12 @@ def process_batch( project_id=val["project_id"], payload=payload.value, end_timestamp=cast(float, val["end_timestamp"]), - is_segment_span=bool(val.get("parent_span_id") is None or val.get("is_remote")), + # TODO(INGEST-612): Remove "is_remote" as soon as Relay writes "is_segment". + is_segment_span=bool( + val.get("parent_span_id") is None + or val.get("is_segment") + or val.get("is_remote") + ), ) spans.append(span) diff --git a/src/sentry/spans/consumers/process_segments/convert.py b/src/sentry/spans/consumers/process_segments/convert.py index 114d3510242bf5..dd641d8c7b3440 100644 --- a/src/sentry/spans/consumers/process_segments/convert.py +++ b/src/sentry/spans/consumers/process_segments/convert.py @@ -15,6 +15,7 @@ "end_timestamp": "sentry.end_timestamp_precise", "event_id": "sentry.event_id", "hash": "sentry.hash", + # TODO(INGEST-612): Remove "is_remote" once Relay writes this attribute. "is_remote": "sentry.is_remote", "kind": "sentry.kind", "name": "sentry.name", diff --git a/tests/sentry/spans/consumers/process/__init__.py b/tests/sentry/spans/consumers/process/__init__.py index 0c5dbc7344f8d9..e35a9c2e99f3bd 100644 --- a/tests/sentry/spans/consumers/process/__init__.py +++ b/tests/sentry/spans/consumers/process/__init__.py @@ -3,7 +3,7 @@ def build_mock_span(project_id, *, span_op=None, is_segment=False, attributes=None, **kwargs): span: SpanEvent = { - "is_remote": is_segment, + "is_segment": is_segment, "parent_span_id": None, "project_id": project_id, "organization_id": 1, diff --git a/tests/sentry/spans/consumers/process/test_consumer.py b/tests/sentry/spans/consumers/process/test_consumer.py index fd7f3108aa1f1d..02571dc1c3c1a6 100644 --- a/tests/sentry/spans/consumers/process/test_consumer.py +++ b/tests/sentry/spans/consumers/process/test_consumer.py @@ -135,7 +135,7 @@ def add_commit(offsets, force=False): "received": 1699999999.0, "name": "test-span", "status": "ok", - "is_remote": False, + "is_segment": False, } # Set the field to None span_data[field_to_set_none] = None @@ -202,7 +202,7 @@ def add_commit(offsets, force=False): "received": 1699999999.0, "name": "test-span", "status": "ok", - "is_remote": False, + "is_segment": False, } step.submit( diff --git a/tests/sentry/spans/consumers/process_segments/test_convert.py b/tests/sentry/spans/consumers/process_segments/test_convert.py index 270ff43459bd16..865d74829c17ae 100644 --- a/tests/sentry/spans/consumers/process_segments/test_convert.py +++ b/tests/sentry/spans/consumers/process_segments/test_convert.py @@ -14,7 +14,7 @@ ############################################### SPAN_KAFKA_MESSAGE: SpanEvent = { - "is_remote": True, + "is_segment": True, "attributes": { "http.status_code": {"value": "200", "type": "string"}, "my.array.field": {"value": [1, 2, ["nested", "array"]], "type": "array"}, From 7e0f53736aa0175e9f22c8b5909135b054e726ed Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Wed, 29 Oct 2025 09:40:21 +0100 Subject: [PATCH 2/8] attributes[sentry.is_segment] -> is_segment --- src/sentry/spans/buffer.py | 5 +---- src/sentry/spans/consumers/process_segments/convert.py | 1 + src/sentry/spans/consumers/process_segments/enrichment.py | 2 +- src/sentry/spans/consumers/process_segments/shim.py | 1 - src/sentry/spans/consumers/process_segments/types.py | 1 - src/sentry/spans/grouping/strategy/base.py | 2 +- tests/sentry/spans/consumers/process/__init__.py | 2 +- 7 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/sentry/spans/buffer.py b/src/sentry/spans/buffer.py index d6048b1853fe92..57b2683775c6f7 100644 --- a/src/sentry/spans/buffer.py +++ b/src/sentry/spans/buffer.py @@ -438,10 +438,7 @@ def flush_segments(self, now: int) -> dict[SegmentKey, FlushedSegment]: } is_segment = segment_span_id == span["span_id"] - span.setdefault("attributes", {})["sentry.is_segment"] = { - "type": "boolean", - "value": is_segment, - } + span["is_segment"] = is_segment if is_segment: has_root_span = True diff --git a/src/sentry/spans/consumers/process_segments/convert.py b/src/sentry/spans/consumers/process_segments/convert.py index dd641d8c7b3440..b7ff5e3716898f 100644 --- a/src/sentry/spans/consumers/process_segments/convert.py +++ b/src/sentry/spans/consumers/process_segments/convert.py @@ -17,6 +17,7 @@ "hash": "sentry.hash", # TODO(INGEST-612): Remove "is_remote" once Relay writes this attribute. "is_remote": "sentry.is_remote", + "is_segment": "sentry.is_segment", "kind": "sentry.kind", "name": "sentry.name", "parent_span_id": "sentry.parent_span_id", diff --git a/src/sentry/spans/consumers/process_segments/enrichment.py b/src/sentry/spans/consumers/process_segments/enrichment.py index 601e4a0e14adb7..b2736cb92a6d9d 100644 --- a/src/sentry/spans/consumers/process_segments/enrichment.py +++ b/src/sentry/spans/consumers/process_segments/enrichment.py @@ -60,7 +60,7 @@ def _find_segment_span(spans: list[SpanEvent]) -> SpanEvent | None: # Iterate backwards since we usually expect the segment span to be at the end. for span in reversed(spans): - if attribute_value(span, "sentry.is_segment"): + if span.get("is_segment"): return span return None diff --git a/src/sentry/spans/consumers/process_segments/shim.py b/src/sentry/spans/consumers/process_segments/shim.py index 3092d1b2fdea24..42b76d3b5dffc2 100644 --- a/src/sentry/spans/consumers/process_segments/shim.py +++ b/src/sentry/spans/consumers/process_segments/shim.py @@ -33,7 +33,6 @@ def make_compatible(span: SpanEvent) -> CompatibleSpan: "sentry_tags": _sentry_tags(span.get("attributes") or {}), "op": get_span_op(span), "exclusive_time": attribute_value(span, "sentry.exclusive_time_ms"), - "is_segment": bool(attribute_value(span, "sentry.is_segment")), } return ret diff --git a/src/sentry/spans/consumers/process_segments/types.py b/src/sentry/spans/consumers/process_segments/types.py index 0cf93f19d918d0..4e277fe64a6929 100644 --- a/src/sentry/spans/consumers/process_segments/types.py +++ b/src/sentry/spans/consumers/process_segments/types.py @@ -31,7 +31,6 @@ class CompatibleSpan(SpanEvent, total=True): exclusive_time: float op: str sentry_tags: dict[str, str] - is_segment: bool # Added by `SpanGroupingResults.write_to_spans` in `_enrich_spans` hash: NotRequired[str] diff --git a/src/sentry/spans/grouping/strategy/base.py b/src/sentry/spans/grouping/strategy/base.py index 3e6549fd2c8359..cf1ae185faef56 100644 --- a/src/sentry/spans/grouping/strategy/base.py +++ b/src/sentry/spans/grouping/strategy/base.py @@ -57,7 +57,7 @@ def get_standalone_span_group(self, span: Span) -> str: # compatibility with transaction events, but fall back to default # fingerprinting if the span doesn't have a transaction. if ( - attribute_value(span, "sentry.is_segment") + span.get("is_segment") and (transaction := attribute_value(span, "sentry.transaction")) is not None ): result = Hash() diff --git a/tests/sentry/spans/consumers/process/__init__.py b/tests/sentry/spans/consumers/process/__init__.py index e35a9c2e99f3bd..0c5dbc7344f8d9 100644 --- a/tests/sentry/spans/consumers/process/__init__.py +++ b/tests/sentry/spans/consumers/process/__init__.py @@ -3,7 +3,7 @@ def build_mock_span(project_id, *, span_op=None, is_segment=False, attributes=None, **kwargs): span: SpanEvent = { - "is_segment": is_segment, + "is_remote": is_segment, "parent_span_id": None, "project_id": project_id, "organization_id": 1, From 9ce2d02310fa3fcbe2b7f4c2851780a69a74f9d4 Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Wed, 29 Oct 2025 10:06:19 +0100 Subject: [PATCH 3/8] Trust is_segment --- src/sentry/spans/consumers/process_segments/convert.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sentry/spans/consumers/process_segments/convert.py b/src/sentry/spans/consumers/process_segments/convert.py index b7ff5e3716898f..fa161dc8521e4a 100644 --- a/src/sentry/spans/consumers/process_segments/convert.py +++ b/src/sentry/spans/consumers/process_segments/convert.py @@ -17,7 +17,6 @@ "hash": "sentry.hash", # TODO(INGEST-612): Remove "is_remote" once Relay writes this attribute. "is_remote": "sentry.is_remote", - "is_segment": "sentry.is_segment", "kind": "sentry.kind", "name": "sentry.name", "parent_span_id": "sentry.parent_span_id", @@ -59,6 +58,10 @@ def convert_span_to_item(span: CompatibleSpan) -> TraceItem: except ValueError: pass + # For `is_segment`, we trust the value written by `flush_segments` over a pre-existing attribute: + if (is_segment := span.get("is_segment")) is not None: + attributes["sentry.is_segment"] = is_segment + for field_name, attribute_name in FIELD_TO_ATTRIBUTE.items(): attribute = span.get(field_name) # type:ignore[assignment] if attribute is not None: From a874d8aa66c3c1ee22086b3c088d57c687da942d Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Thu, 30 Oct 2025 08:07:02 +0100 Subject: [PATCH 4/8] anyvalue, bump schema version --- pyproject.toml | 2 +- src/sentry/spans/consumers/process_segments/convert.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f39cd10f42e24e..22fe7c425654b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,7 @@ dependencies = [ # [end] jsonschema format validators "sentry-arroyo>=2.29.7", "sentry-forked-email-reply-parser>=0.5.12.post1", - "sentry-kafka-schemas>=2.1.11", + "sentry-kafka-schemas>=2.1.12", "sentry-ophio>=1.1.3", "sentry-protos>=0.4.2", "sentry-redis-tools>=0.5.0", diff --git a/src/sentry/spans/consumers/process_segments/convert.py b/src/sentry/spans/consumers/process_segments/convert.py index fa161dc8521e4a..81ed5653823bbf 100644 --- a/src/sentry/spans/consumers/process_segments/convert.py +++ b/src/sentry/spans/consumers/process_segments/convert.py @@ -60,7 +60,7 @@ def convert_span_to_item(span: CompatibleSpan) -> TraceItem: # For `is_segment`, we trust the value written by `flush_segments` over a pre-existing attribute: if (is_segment := span.get("is_segment")) is not None: - attributes["sentry.is_segment"] = is_segment + attributes["sentry.is_segment"] = _anyvalue(is_segment) for field_name, attribute_name in FIELD_TO_ATTRIBUTE.items(): attribute = span.get(field_name) # type:ignore[assignment] From 10713491d852d1187fc2b4b5f56cc613bf13c303 Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Thu, 30 Oct 2025 14:09:40 +0100 Subject: [PATCH 5/8] 2.1.13 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5c28a177b3140b..bbf409c2596a2b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,7 @@ dependencies = [ # [end] jsonschema format validators "sentry-arroyo>=2.30.0", "sentry-forked-email-reply-parser>=0.5.12.post1", - "sentry-kafka-schemas>=2.1.12", + "sentry-kafka-schemas>=2.1.13", "sentry-ophio>=1.1.3", "sentry-protos>=0.4.2", "sentry-redis-tools>=0.5.0", From 4fdc4b6f67ceef4cc5ff4fb6984fd50cd14b6abe Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Thu, 30 Oct 2025 14:24:41 +0100 Subject: [PATCH 6/8] Update src/sentry/spans/consumers/process_segments/convert.py --- src/sentry/spans/consumers/process_segments/convert.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sentry/spans/consumers/process_segments/convert.py b/src/sentry/spans/consumers/process_segments/convert.py index 81ed5653823bbf..66caecb25f6671 100644 --- a/src/sentry/spans/consumers/process_segments/convert.py +++ b/src/sentry/spans/consumers/process_segments/convert.py @@ -15,7 +15,7 @@ "end_timestamp": "sentry.end_timestamp_precise", "event_id": "sentry.event_id", "hash": "sentry.hash", - # TODO(INGEST-612): Remove "is_remote" once Relay writes this attribute. + # TODO(INGEST-612): Remove "is_remote" once Relay has stopped writing it. "is_remote": "sentry.is_remote", "kind": "sentry.kind", "name": "sentry.name", From 2677c44f7fe44fb35aa5b457c576c8bc5e2d92f8 Mon Sep 17 00:00:00 2001 From: "getsantry[bot]" <66042841+getsantry[bot]@users.noreply.github.com> Date: Thu, 30 Oct 2025 13:25:51 +0000 Subject: [PATCH 7/8] :snowflake: re-freeze requirements --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index d2da7ac0cce18d..3fa4641e0d6791 100644 --- a/uv.lock +++ b/uv.lock @@ -2152,7 +2152,7 @@ requires-dist = [ { name = "rfc3986-validator", specifier = ">=0.1.1" }, { name = "sentry-arroyo", specifier = ">=2.30.0" }, { name = "sentry-forked-email-reply-parser", specifier = ">=0.5.12.post1" }, - { name = "sentry-kafka-schemas", specifier = ">=2.1.11" }, + { name = "sentry-kafka-schemas", specifier = ">=2.1.13" }, { name = "sentry-ophio", specifier = ">=1.1.3" }, { name = "sentry-protos", specifier = ">=0.4.2" }, { name = "sentry-redis-tools", specifier = ">=0.5.0" }, @@ -2324,7 +2324,7 @@ wheels = [ [[package]] name = "sentry-kafka-schemas" -version = "2.1.11" +version = "2.1.13" source = { registry = "https://pypi.devinfra.sentry.io/simple" } dependencies = [ { name = "fastjsonschema", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, @@ -2335,7 +2335,7 @@ dependencies = [ { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, ] wheels = [ - { url = "https://pypi.devinfra.sentry.io/wheels/sentry_kafka_schemas-2.1.11-py2.py3-none-any.whl", hash = "sha256:38686ac1f612cecc49fe5b34e0fe6e16e9e8fe43a147900feee4e265d307a307" }, + { url = "https://pypi.devinfra.sentry.io/wheels/sentry_kafka_schemas-2.1.13-py2.py3-none-any.whl", hash = "sha256:edfdbc2b9ec557b642c1b000fb4be80b329758d93f7dbc62a6461cb2abd19475" }, ] [[package]] From d5a42668750a5ac19203371f21a3c65eb4473253 Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Fri, 31 Oct 2025 09:02:24 +0100 Subject: [PATCH 8/8] update tests --- src/sentry/testutils/issue_detection/span_builder.py | 2 +- tests/sentry/spans/consumers/process/__init__.py | 3 +-- tests/sentry/spans/consumers/process/test_consumer.py | 2 +- tests/sentry/spans/consumers/process_segments/test_convert.py | 3 +-- .../sentry/spans/consumers/process_segments/test_enrichment.py | 2 +- tests/sentry/spans/test_buffer.py | 2 +- 6 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/sentry/testutils/issue_detection/span_builder.py b/src/sentry/testutils/issue_detection/span_builder.py index b731e8a06a5d06..00e58efa646507 100644 --- a/src/sentry/testutils/issue_detection/span_builder.py +++ b/src/sentry/testutils/issue_detection/span_builder.py @@ -82,8 +82,8 @@ def build_v2(self) -> dict[str, Any]: "start_timestamp": self.start_timestamp, "timestamp": self.timestamp, "same_process_as_parent": self.same_process_as_parent, + "is_segment": self.is_segment, "attributes": { - "sentry.is_segment": {"value": self.is_segment}, "sentry.op": {"value": self.op}, "sentry.description": {"value": self.description}, **{k: {"value": v} for (k, v) in (self.tags or {}).items()}, diff --git a/tests/sentry/spans/consumers/process/__init__.py b/tests/sentry/spans/consumers/process/__init__.py index 0c5dbc7344f8d9..47bd13548d0234 100644 --- a/tests/sentry/spans/consumers/process/__init__.py +++ b/tests/sentry/spans/consumers/process/__init__.py @@ -3,14 +3,13 @@ def build_mock_span(project_id, *, span_op=None, is_segment=False, attributes=None, **kwargs): span: SpanEvent = { - "is_remote": is_segment, + "is_segment": is_segment, "parent_span_id": None, "project_id": project_id, "organization_id": 1, "received": 1707953019.044972, "retention_days": 90, "attributes": { - "sentry.is_segment": {"value": is_segment, "type": "boolean"}, "sentry.duration": {"value": 0.107, "type": "double"}, "sentry.environment": {"value": "development", "type": "string"}, "sentry.release": { diff --git a/tests/sentry/spans/consumers/process/test_consumer.py b/tests/sentry/spans/consumers/process/test_consumer.py index 02571dc1c3c1a6..793d5a1c7aab5a 100644 --- a/tests/sentry/spans/consumers/process/test_consumer.py +++ b/tests/sentry/spans/consumers/process/test_consumer.py @@ -81,9 +81,9 @@ def add_commit(offsets, force=False): "spans": [ { "attributes": { - "sentry.is_segment": {"type": "boolean", "value": True}, "sentry.segment.id": {"type": "string", "value": "aaaaaaaaaaaaaaaa"}, }, + "is_segment": True, "project_id": 12, "span_id": "aaaaaaaaaaaaaaaa", "trace_id": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", diff --git a/tests/sentry/spans/consumers/process_segments/test_convert.py b/tests/sentry/spans/consumers/process_segments/test_convert.py index 865d74829c17ae..056935fc9547a0 100644 --- a/tests/sentry/spans/consumers/process_segments/test_convert.py +++ b/tests/sentry/spans/consumers/process_segments/test_convert.py @@ -127,7 +127,6 @@ def test_convert_span_to_item() -> None: "sentry.duration_ms": AnyValue(int_value=152), "sentry.end_timestamp_precise": AnyValue(double_value=1721319572.768806), "sentry.environment": AnyValue(string_value="development"), - "sentry.is_remote": AnyValue(bool_value=True), "sentry.is_segment": AnyValue(bool_value=True), "sentry.name": AnyValue(string_value="endpoint"), "sentry.normalized_description": AnyValue(string_value="normalized_description"), @@ -167,7 +166,7 @@ def test_convert_span_to_item() -> None: def test_convert_falsy_fields() -> None: message: SpanEvent = {**SPAN_KAFKA_MESSAGE} - message["attributes"]["sentry.is_segment"] = {"type": "boolean", "value": False} # type: ignore[index] + message["is_segment"] = False item = convert_span_to_item(cast(CompatibleSpan, message)) diff --git a/tests/sentry/spans/consumers/process_segments/test_enrichment.py b/tests/sentry/spans/consumers/process_segments/test_enrichment.py index f9bdcba29fab35..6c9ec9071d7a04 100644 --- a/tests/sentry/spans/consumers/process_segments/test_enrichment.py +++ b/tests/sentry/spans/consumers/process_segments/test_enrichment.py @@ -458,9 +458,9 @@ def _mock_performance_issue_span(is_segment, attributes, **fields) -> SpanEvent: "received": 1707953019.044972, "retention_days": 90, "segment_id": "a49b42af9fb69da0", + "is_segment": is_segment, "attributes": { **attributes, - "sentry.is_segment": {"type": "boolean", "value": is_segment}, "sentry.description": {"type": "string", "value": "OrganizationNPlusOne"}, }, "span_id": "a49b42af9fb69da0", diff --git a/tests/sentry/spans/test_buffer.py b/tests/sentry/spans/test_buffer.py index 7ac8d7376114a0..f975bd61117684 100644 --- a/tests/sentry/spans/test_buffer.py +++ b/tests/sentry/spans/test_buffer.py @@ -45,9 +45,9 @@ def _output_segment(span_id: bytes, segment_id: bytes, is_segment: bool) -> Outp return OutputSpan( payload={ "span_id": span_id.decode("ascii"), + "is_segment": is_segment, "attributes": { "sentry.segment.id": {"type": "string", "value": segment_id.decode("ascii")}, - "sentry.is_segment": {"type": "boolean", "value": is_segment}, }, } )