-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
fix(eap): Handle contexts in trace-item attributes #112524
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -20,7 +20,11 @@ | |||||||||
| TraceItemType as ProtoTraceItemType, | ||||||||||
| ) | ||||||||||
| from sentry_protos.snuba.v1.trace_item_attribute_pb2 import AttributeKey | ||||||||||
| from sentry_protos.snuba.v1.trace_item_filter_pb2 import ExistsFilter, OrFilter, TraceItemFilter | ||||||||||
| from sentry_protos.snuba.v1.trace_item_filter_pb2 import ( | ||||||||||
| ExistsFilter, | ||||||||||
| OrFilter, | ||||||||||
| TraceItemFilter, | ||||||||||
| ) | ||||||||||
|
|
||||||||||
| from sentry import features, options | ||||||||||
| from sentry.api.api_owners import ApiOwner | ||||||||||
|
|
@@ -54,7 +58,11 @@ | |||||||||
| from sentry.search.eap.resolver import SearchResolver | ||||||||||
| from sentry.search.eap.spans.definitions import SPAN_DEFINITIONS | ||||||||||
| from sentry.search.eap.trace_metrics.definitions import TRACE_METRICS_DEFINITIONS | ||||||||||
| from sentry.search.eap.types import SearchResolverConfig, SupportedTraceItemType | ||||||||||
| from sentry.search.eap.types import ( | ||||||||||
| AttributeSourceType, | ||||||||||
| SearchResolverConfig, | ||||||||||
| SupportedTraceItemType, | ||||||||||
| ) | ||||||||||
| from sentry.search.eap.utils import ( | ||||||||||
| can_expose_attribute, | ||||||||||
| get_secondary_aliases, | ||||||||||
|
|
@@ -79,6 +87,10 @@ | |||||||||
| POSSIBLE_ATTRIBUTE_TYPES = ["string", "number", "boolean"] | ||||||||||
|
|
||||||||||
|
|
||||||||||
| class ProxyResolvedAttribute(ResolvedAttribute): | ||||||||||
| pass | ||||||||||
|
|
||||||||||
|
|
||||||||||
| class TraceItemAttributeKey(TypedDict): | ||||||||||
| key: str | ||||||||||
| name: str | ||||||||||
|
|
@@ -218,6 +230,7 @@ | |||||||||
| name: str, | ||||||||||
| attr_type: Literal["string", "number", "boolean"], | ||||||||||
| item_type: SupportedTraceItemType, | ||||||||||
| is_proxy: bool = False, | ||||||||||
| ) -> TraceItemAttributeKey: | ||||||||||
| public_key, public_name, attribute_source = translate_internal_to_public_alias( | ||||||||||
| name, attr_type, item_type | ||||||||||
|
|
@@ -226,6 +239,8 @@ | |||||||||
|
|
||||||||||
| if public_key is not None and public_name is not None: | ||||||||||
| pass | ||||||||||
| elif is_proxy: | ||||||||||
| public_key = public_name = name | ||||||||||
| elif attr_type == "number": | ||||||||||
| public_key = f"tags[{name},number]" | ||||||||||
| public_name = name | ||||||||||
|
|
@@ -237,7 +252,11 @@ | |||||||||
| public_name = name | ||||||||||
|
|
||||||||||
| serialized_source: dict[str, str | bool] = { | ||||||||||
| "source_type": attribute_source["source_type"].value | ||||||||||
| "source_type": ( | ||||||||||
| attribute_source["source_type"].value | ||||||||||
| if not is_proxy | ||||||||||
| else AttributeSourceType.SENTRY.value | ||||||||||
| ) | ||||||||||
| } | ||||||||||
| if attribute_source.get("is_transformed_alias"): | ||||||||||
| serialized_source["is_transformed_alias"] = True | ||||||||||
|
|
@@ -378,18 +397,54 @@ | |||||||||
| all_aliased_attributes = [] | ||||||||||
| # our aliases don't exist in the db, so filter over our aliases | ||||||||||
| # virtually page through defined aliases before we hit the db | ||||||||||
| if substring_match and offset <= len(column_definitions.columns): | ||||||||||
| for index, column in enumerate(column_definitions.columns.values()): | ||||||||||
| if ( | ||||||||||
| column.proto_type == attr_type | ||||||||||
| and substring_match in column.public_alias | ||||||||||
| and not column.secondary_alias | ||||||||||
| and not column.private | ||||||||||
| ): | ||||||||||
| all_aliased_attributes.append(column) | ||||||||||
| if offset <= len(column_definitions.columns) + len(column_definitions.contexts): | ||||||||||
| if substring_match: | ||||||||||
| for column in column_definitions.columns.values(): | ||||||||||
| if ( | ||||||||||
| column.proto_type == attr_type | ||||||||||
| and substring_match in column.public_alias | ||||||||||
| and not column.secondary_alias | ||||||||||
| and not column.private | ||||||||||
| ): | ||||||||||
| all_aliased_attributes.append(column) | ||||||||||
| for ( | ||||||||||
| public_label, | ||||||||||
| virtual_context, | ||||||||||
| ) in column_definitions.contexts.items(): | ||||||||||
| if ( | ||||||||||
| substring_match in public_label | ||||||||||
| and virtual_context.search_type is not None | ||||||||||
| and not virtual_context.secondary_alias | ||||||||||
| and constants.TYPE_MAP[virtual_context.search_type] == attr_type | ||||||||||
| ): | ||||||||||
| all_aliased_attributes.append( | ||||||||||
| ProxyResolvedAttribute( | ||||||||||
| public_alias=public_label, | ||||||||||
| internal_name=public_label, | ||||||||||
| search_type=virtual_context.search_type, | ||||||||||
| ) | ||||||||||
| ) | ||||||||||
| else: | ||||||||||
| for ( | ||||||||||
| public_label, | ||||||||||
| virtual_context, | ||||||||||
| ) in column_definitions.contexts.items(): | ||||||||||
| if ( | ||||||||||
| substring_match in public_label | ||||||||||
| and virtual_context.search_type is not None | ||||||||||
| and not virtual_context.secondary_alias | ||||||||||
| and constants.TYPE_MAP[virtual_context.search_type] == attr_type | ||||||||||
| ): | ||||||||||
| all_aliased_attributes.append( | ||||||||||
| ProxyResolvedAttribute( | ||||||||||
| public_alias=public_label, | ||||||||||
| internal_name=public_label, | ||||||||||
| search_type=virtual_context.search_type, | ||||||||||
| ) | ||||||||||
| ) | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Identical context iteration duplicated across if/else branchesLow Severity The context iteration in the Additional Locations (1)Reviewed by Cursor Bugbot for commit 53fd538. Configure here. |
||||||||||
| aliased_attributes = all_aliased_attributes[offset : offset + limit] | ||||||||||
| with sentry_sdk.start_span(op="query", name="attribute_names") as span: | ||||||||||
| if len(aliased_attributes) < limit - 1: | ||||||||||
| if len(aliased_attributes) < limit: | ||||||||||
| offset -= len(all_aliased_attributes) - len(aliased_attributes) | ||||||||||
|
Comment on lines
445
to
448
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: When paginating without a Suggested FixAdjust the offset calculation to correctly account for the items that are not included in Prompt for AI Agent |
||||||||||
| limit -= len(aliased_attributes) | ||||||||||
| rpc_request = TraceItemAttributeNamesRequest( | ||||||||||
|
|
@@ -419,6 +474,7 @@ | |||||||||
| include_internal, | ||||||||||
| substring_match, | ||||||||||
| aliased_attributes, | ||||||||||
| all_aliased_attributes, | ||||||||||
| ) | ||||||||||
|
|
||||||||||
| sentry_sdk.set_context("api_response", {"attributes": attributes}) | ||||||||||
|
|
@@ -433,7 +489,8 @@ | |||||||||
| trace_item_type: SupportedTraceItemType, | ||||||||||
| include_internal: bool, | ||||||||||
| substring_match: str, | ||||||||||
| aliased_attributes: list[ResolvedAttribute], | ||||||||||
| aliased_attributes: list[ResolvedAttribute | ProxyResolvedAttribute], | ||||||||||
| exclude_attributes: list[ResolvedAttribute | ProxyResolvedAttribute], | ||||||||||
| ) -> list[TraceItemAttributeKey]: | ||||||||||
| attribute_keys = {} | ||||||||||
| for attribute in rpc_response.attributes: | ||||||||||
|
|
@@ -457,11 +514,21 @@ | |||||||||
| ) | ||||||||||
|
|
||||||||||
| attribute_keys[attr_key["name"]] = attr_key | ||||||||||
| for aliased_attr in exclude_attributes: | ||||||||||
| attr_key = as_attribute_key( | ||||||||||
| aliased_attr.internal_name, | ||||||||||
| attribute_type, | ||||||||||
| trace_item_type, | ||||||||||
| is_proxy=isinstance(aliased_attr, ProxyResolvedAttribute), | ||||||||||
| ) | ||||||||||
| if attr_key["name"] in attribute_keys: | ||||||||||
| del attribute_keys[attr_key["name"]] | ||||||||||
| for aliased_attr in aliased_attributes: | ||||||||||
| attr_key = as_attribute_key( | ||||||||||
| aliased_attr.internal_name, | ||||||||||
| attribute_type, | ||||||||||
| trace_item_type, | ||||||||||
| is_proxy=isinstance(aliased_attr, ProxyResolvedAttribute), | ||||||||||
| ) | ||||||||||
| attribute_keys[attr_key["name"]] = attr_key | ||||||||||
|
|
||||||||||
|
|
@@ -476,33 +543,42 @@ | |||||||||
| trace_item_type: SupportedTraceItemType, | ||||||||||
| include_internal: bool, | ||||||||||
| substring_match: str, | ||||||||||
| aliased_attributes: list[ResolvedAttribute], | ||||||||||
| aliased_attributes: list[ResolvedAttribute | ProxyResolvedAttribute], | ||||||||||
| exclude_attributes: list[ResolvedAttribute | ProxyResolvedAttribute], | ||||||||||
| ) -> list[TraceItemAttributeKey]: | ||||||||||
| attributes = list( | ||||||||||
| filter( | ||||||||||
| lambda x: ( | ||||||||||
| not is_sentry_convention_replacement_attribute(x["name"], trace_item_type) | ||||||||||
| attribute_keys = {} | ||||||||||
| for attribute in rpc_response.attributes: | ||||||||||
| if attribute.name and can_expose_attribute( | ||||||||||
| attribute.name, | ||||||||||
| trace_item_type, | ||||||||||
| include_internal=include_internal, | ||||||||||
| ): | ||||||||||
| attr_key = as_attribute_key( | ||||||||||
| attribute.name, | ||||||||||
| attribute_type, | ||||||||||
| trace_item_type, | ||||||||||
| ) | ||||||||||
| if ( | ||||||||||
| not is_sentry_convention_replacement_attribute( | ||||||||||
| attr_key["name"], trace_item_type | ||||||||||
| ) | ||||||||||
| # Remove anything where the public alias doesn't match the substring | ||||||||||
| # This can happen when the public alias is different, but that's handled by | ||||||||||
| # aliased_attributes | ||||||||||
| and (substring_match in x["name"] if substring_match else True) | ||||||||||
| ), | ||||||||||
| [ | ||||||||||
| as_attribute_key( | ||||||||||
| attribute.name, | ||||||||||
| attribute_type, | ||||||||||
| trace_item_type, | ||||||||||
| ) | ||||||||||
| for attribute in rpc_response.attributes | ||||||||||
| if attribute.name | ||||||||||
| and can_expose_attribute( | ||||||||||
| attribute.name, | ||||||||||
| trace_item_type, | ||||||||||
| include_internal=include_internal, | ||||||||||
| ) | ||||||||||
| ], | ||||||||||
| and (substring_match in attr_key["name"] if substring_match else True) | ||||||||||
| ): | ||||||||||
| attribute_keys[attr_key["key"]] = attr_key | ||||||||||
| # We need to exclude any aliased attributes here since because of pagination they might have already been seen | ||||||||||
| # earlier | ||||||||||
| for aliased_attr in exclude_attributes: | ||||||||||
| attr_key = as_attribute_key( | ||||||||||
| aliased_attr.internal_name, | ||||||||||
| attribute_type, | ||||||||||
| trace_item_type, | ||||||||||
| is_proxy=isinstance(aliased_attr, ProxyResolvedAttribute), | ||||||||||
| ) | ||||||||||
| ) | ||||||||||
| if attr_key["name"] in attribute_keys: | ||||||||||
| del attribute_keys[attr_key["name"]] | ||||||||||
|
Check warning on line 581 in src/sentry/api/endpoints/organization_trace_item_attributes.py
|
||||||||||
|
Comment on lines
+580
to
+581
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. exclude_attributes loop checks wrong dict key, exclusions never happen for tag attributes The VerificationRead as_attribute_key (lines 244-252) which shows key = f"tags[{name},number]" while name = name. Confirmed attribute_keys is populated with ["key"] at line 570. The membership check at line 580 uses ["name"] which won't match the stored keys for tag attributes. Suggested fix: Use attr_key["key"] consistently for both dict key storage and deletion lookup
Suggested change
Identified by Warden
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix attempt detected (commit 63b88cd) The code was refactored to use a dictionary approach at line 570 with attr_key['key'] as the key, but the exclusion logic at lines 580-581 still incorrectly uses attr_key['name'] for both the membership check and deletion, so the fix was attempted but the core issue persists. The original issue appears unresolved. Please review and try again. Evaluated by Warden
sentry[bot] marked this conversation as resolved.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dictionary keyed by "key" but exclusion uses "name"Medium Severity In Additional Locations (1)Reviewed by Cursor Bugbot for commit 53fd538. Configure here. |
||||||||||
| for aliased_attr in aliased_attributes: | ||||||||||
| if can_expose_attribute( | ||||||||||
| aliased_attr.public_alias, | ||||||||||
|
|
@@ -513,8 +589,11 @@ | |||||||||
| aliased_attr.internal_name, | ||||||||||
| attribute_type, | ||||||||||
| trace_item_type, | ||||||||||
| is_proxy=isinstance(aliased_attr, ProxyResolvedAttribute), | ||||||||||
| ) | ||||||||||
| attributes.append(attr_key) | ||||||||||
| attribute_keys[attr_key["key"]] = attr_key | ||||||||||
| attributes = list(attribute_keys.values()) | ||||||||||
| sentry_sdk.set_context("api_response", {"attributes": attributes}) | ||||||||||
| return attributes | ||||||||||
|
|
||||||||||
|
|
||||||||||
|
|
||||||||||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: When
substring_matchis empty, theelsebranch inquery_trace_attributesfails to iterate overcolumn_definitions.columns, breaking pagination logic.Severity: MEDIUM
Suggested Fix
Update the
elseblock to mirror theifblock's logic. It should iterate throughcolumn_definitions.columnsand append them to theall_aliased_attributeslist, ensuring consistent pagination behavior regardless of whethersubstring_matchis provided.Prompt for AI Agent