Skip to content

Conversation

@philprime
Copy link
Member

@philprime philprime commented Nov 26, 2025

Description

Fixes #5308

This PR implements getter and setter overrides for the tags property in SentryTransaction to allow full CRUD operations (Read, Add, Modify, Remove, Replace) on transaction tags within beforeSend callbacks.

See this issue comment for a full explanation reproducer.

Problem

When accessing transaction.tags in beforeSend, it only returned tags from the superclass SentryEvent, missing tags set on the underlying SentryTracer. Additionally, modifications to transaction.tags in beforeSend didn't persist because they only affected the event tags, not the tracer tags that are actually serialized.

Solution Approach

We implemented a computed property pattern that:

  1. Getter: Merges tags from both SentryEvent (superclass) and SentryTracer (wrapped tracer), with tracer tags taking precedence

    • Returns a mutable dictionary copy to allow modifications
    • Handles nil/empty cases gracefully
  2. Setter: Replaces all tags by:

    • Clearing all existing tags from the tracer
    • Clearing event tags
    • Setting all new tags on the tracer (since transaction tags belong on the tracer)

Implementation Details

Swift vs Objective-C Behavior

Swift: When code does transaction.tags?["key"] = "value", Swift automatically expands this to get tags, modify copy, and call the setter with the modified copy. This means modifications persist automatically.

Objective-C: Modifying the returned dictionary directly does NOT persist. Users must explicitly call the setter: transaction.tags = modifiedDict.

This difference is documented in code comments to help developers understand the behavior in both languages.

Testing

Added comprehensive tests covering all CRUD operations:

  • Read: Verify merged tags are returned correctly
  • Add: Verify new tags can be added
  • Modify: Verify existing tags can be modified (both tracer and event tags)
  • Remove: Verify tags can be removed (both tracer and event tags)
  • Replace: Verify entire tag dictionary can be replaced

All tests verify behavior both in beforeSend callbacks and in the final serialized output.

Alternatives Considered

1. Custom NSMutableDictionary Subclass

Approach: Create a custom subclass that automatically calls the setter on every mutation.

Why Not Implemented:

  • Performance overhead: The setter does significant work (clears all tags, then sets new ones). Calling it on every mutation would be inefficient, especially with multiple mutations.
  • Complexity: Would need to override many mutation methods (setObject:forKey:, removeObjectForKey:, removeAllObjects, etc.)
  • Memory management: Would require either weak references (risking nil) or strong references (creating retain cycles)
  • Thread safety: Would need synchronization, adding more complexity
  • Architecture mismatch: Tags belong on the tracer, not the event, so this approach doesn't align with the data model

2. Proxy Pattern with Method Swizzling

Approach: Use method swizzling or a proxy to intercept dictionary mutations.

Why Not Implemented:

  • Too complex and fragile
  • Method swizzling can have unintended side effects
  • Doesn't solve the fundamental issue of where tags are stored

3. Direct Tracer Access

Approach: Document that users should access transaction.trace.tags directly.

Why Not Implemented:

  • Breaks the abstraction - users shouldn't need to know about the internal tracer
  • Inconsistent with how other properties work
  • Doesn't solve the problem of reading merged tags

Changes

  • Added tags getter override in SentryTransaction.m that merges event and tracer tags
  • Added tags setter override in SentryTransaction.m that replaces all tags on the tracer
  • Added comprehensive tests in SentryTransactionTests.swift for all tag operations
  • Added tests in SentryClientTests.swift verifying tag operations work in beforeSend callbacks
  • Added documentation comments explaining Swift vs Objective-C behavior differences

Testing

  • All existing tests pass
  • New comprehensive test suite covers all CRUD operations
  • Verified behavior in both Swift and Objective-C contexts
  • Tested with real beforeSend callbacks in sample apps

@codecov
Copy link

codecov bot commented Nov 26, 2025

Codecov Report

❌ Patch coverage is 90.00000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.917%. Comparing base (70ac6c6) to head (0e7f2a4).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
Sources/Sentry/SentryTransaction.m 90.000% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@              Coverage Diff              @@
##              main     #6910       +/-   ##
=============================================
- Coverage   85.894%   84.917%   -0.977%     
=============================================
  Files          453       453               
  Lines        37531     27609     -9922     
  Branches     17430     12125     -5305     
=============================================
- Hits         32237     23445     -8792     
+ Misses        5251      4118     -1133     
- Partials        43        46        +3     
Files with missing lines Coverage Δ
Sources/Sentry/SentryTransaction.m 89.772% <90.000%> (-1.794%) ⬇️

... and 414 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 70ac6c6...0e7f2a4. Read the comment docs.

@philprime philprime closed this Nov 26, 2025
@philprime philprime force-pushed the philprime/beforesend-transactions branch from 47ffab3 to 94f3691 Compare November 26, 2025 15:38
Override tags property in SentryTransaction to merge event tags and tracer tags,
allowing full CRUD operations (Read, Add, Modify, Remove, Replace) in beforeSend.

- Getter merges event tags and tracer tags (tracer takes precedence)
- Setter clears both event and tracer tags, then sets all new tags on tracer
- Added comprehensive tests for all tag operations in beforeSend
- Added tests verifying Swift's automatic setter behavior with dictionary subscript assignment

Fixes #5308
@philprime philprime reopened this Nov 26, 2025
Add documentation note explaining special behavior of tags property
for SentryTransaction instances in beforeSend callbacks:
- Tags property merges event and tracer tags (tracer takes precedence)
- Swift automatically calls setter with dictionary subscript assignment
- Objective-C requires explicit setter call to persist modifications
@philprime philprime self-assigned this Nov 26, 2025
@philprime philprime added the ready-to-merge Use this label to trigger all PR workflows label Nov 26, 2025
@philprime philprime marked this pull request as ready for review November 26, 2025 17:17
@github-actions
Copy link
Contributor

github-actions bot commented Nov 26, 2025

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 1201.85 ms 1253.98 ms 52.13 ms
Size 24.14 KiB 1.01 MiB 1012.89 KiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
26f7b17 1218.47 ms 1253.82 ms 35.35 ms
139db8b 1231.50 ms 1258.19 ms 26.69 ms
134fbdf 1219.71 ms 1240.35 ms 20.64 ms
83bb978 1238.33 ms 1260.04 ms 21.71 ms
331dad6 1210.40 ms 1242.06 ms 31.67 ms
85a741b 1217.02 ms 1239.27 ms 22.25 ms
083e8c5 1227.74 ms 1262.37 ms 34.62 ms
d7461dc 1233.69 ms 1255.29 ms 21.60 ms
939d583 1209.96 ms 1251.09 ms 41.13 ms
5fcb6a1 1198.86 ms 1226.89 ms 28.03 ms

App size

Revision Plain With Sentry Diff
26f7b17 23.75 KiB 960.93 KiB 937.19 KiB
139db8b 23.75 KiB 920.64 KiB 896.89 KiB
134fbdf 23.75 KiB 875.25 KiB 851.50 KiB
83bb978 23.75 KiB 920.64 KiB 896.89 KiB
331dad6 23.75 KiB 928.12 KiB 904.37 KiB
85a741b 23.75 KiB 959.44 KiB 935.69 KiB
083e8c5 23.75 KiB 981.75 KiB 958.00 KiB
d7461dc 23.75 KiB 874.45 KiB 850.70 KiB
939d583 23.75 KiB 1023.82 KiB 1000.07 KiB
5fcb6a1 24.14 KiB 1.01 MiB 1014.60 KiB

Previous results on branch: philprime/beforesend-transactions

Startup times

Revision Plain With Sentry Diff
d9306d2 1232.59 ms 1258.87 ms 26.28 ms
6129c9a 1216.77 ms 1251.13 ms 34.37 ms

App size

Revision Plain With Sentry Diff
d9306d2 24.14 KiB 1.01 MiB 1012.89 KiB
6129c9a 24.14 KiB 1.01 MiB 1013.15 KiB

@philprime philprime enabled auto-merge (squash) November 27, 2025 13:13
Copy link
Contributor

@itaybre itaybre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@philprime philprime merged commit 5db9f93 into main Nov 27, 2025
186 of 190 checks passed
@philprime philprime deleted the philprime/beforesend-transactions branch November 27, 2025 14:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge Use this label to trigger all PR workflows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tags of transactions can't be edited in beforeSend

3 participants