Skip to content

✨ [PANA-8578] Assign roles to strings in session replay recordings - #4983

Open
sethfowler-datadog wants to merge 5 commits into
mainfrom
seth.fowler/PANA-8578-support-string-roles
Open

✨ [PANA-8578] Assign roles to strings in session replay recordings#4983
sethfowler-datadog wants to merge 5 commits into
mainfrom
seth.fowler/PANA-8578-support-string-roles

Conversation

@sethfowler-datadog

Copy link
Copy Markdown
Contributor

Motivation

This PR updates the session replay recorder to assign "roles" to all strings it generates. These roles give downstream tooling the information it needs to transform session replay recordings after they're generated; most importantly, the roles are designed to give us the capability to perform additional privacy masking on the server side. The string roles ensure that this kind of transformation can be performed without the need for prohibitively expensive analysis, like DOM reconstruction, and without the need for the backend to understand the entire session replay data format.

Changes

Roles are assigned where each string is produced. serializeNode, serializeAttributes, serializeMutations and SerializationTransaction tag each string as they emit it, so the assignment is structural and can't drift from the code that produces the string. StringRole constants are added to sessionReplayConstants.ts; the schema types themselves landed earlier.

Strings are grouped by role when the final record is generated. ChangeEncoder now collects the strings a record introduces and groups them at flush time; this means that we only need to send one annotation per role, instead of annotating every string individually. Newly-introduced strings have no id until the flush happens; they're initially replaced by a placeholder, and we then replace these placeholders with string table references in a second pass. We track which changes actually include fresh strings; in the common case where no new strings are introduced, these changes can be skipped, minimizing the amount of work we need to do on the second pass.

The string table is logically keyed by (role, string). Two identical strings in different roles can be masked differently and thus can't share an entry.

New domain/record/encoding/ directory holding the string table and the change encoder/decoder: itemIds.ts, changeEncoder.ts, changeDecoder.ts and stringTable.ts moved there, joined by roles.ts and stringIds.ts. This is mostly just an organizational change to centralize the encoding logic, since we're starting to get more of it; the new files are the main things to focus on when reviewing this directory.

Session replay tests now fail when the old string representations are encountered. This is enforced by ChangeDecoder, which is used under the hood by all of the tests to present serialized change records in a human-readable way. ChangeDecoder also gained a keepRoles option which makes it decode string references to RoleAnnotatedStringLiterals instead of plain strings, making it possible to conveniently write tests for string role behavior.

serializeDOMAttributes() returns AttributeAssignment[] instead of a Record<string, string>. This allows it to handle string role assignment for attributes directly and avoids some wasteful transformations that would otherwise be required. (We avoid 1/3 of the allocations we would otherwise do per element on this code path.) The change also fixes a latent bug, although it's admittedly an edge case: an attribute named __proto__ was silently dropped, because assigning it on a plain object went through the inherited setter. Covered by a new test.

Behavior changes worth a look

  • A masked <option value="…"> now serializes no value at all, where it previously serialized value="***". getElementInputValue() already declined to serialize it; the censored DOM attribute was surviving underneath.
  • value, selected and checked now sort after an element's other attributes rather than keeping their DOM position. Attribute order isn't meaningful in the format, but it moves one expectation in serializeNode.snapshot.spec.ts.

Checklist

  • Tested locally
  • Tested on staging (will do so immediately after pushing)
  • Added unit tests for this change.
  • Added e2e/integration tests for this change.
  • Updated documentation and/or relevant AGENTS.md file

@sethfowler-datadog
sethfowler-datadog requested review from a team as code owners August 25, 2026 17:48
@sethfowler-datadog

Copy link
Copy Markdown
Contributor Author

/to-staging

@gh-worker-devflow-routing-ef8351

gh-worker-devflow-routing-ef8351 Bot commented Aug 25, 2026

Copy link
Copy Markdown

View all feedbacks in Devflow UI.

2026-08-25 17:48:44 UTC ℹ️ Start processing command /to-staging


2026-08-25 17:48:51 UTC ℹ️ Branch Integration: starting soon, merge expected in approximately 16m (p90)

Commit c9e4928d35 will soon be integrated into staging-35.


2026-08-25 17:49:10 UTC 🚨 Branch Integration: this merge request has conflicts which couldn't be solved automatically

We couldn't automatically merge the commit c9e4928d35 into staging-35!

To solve the conflicts directly in Github, click here to create a fix pull request.

Alternatively, you can also click here reset the integration branch or use the following Slack command: @devflow reset-branch -r browser-sdk -b staging-35

⚠️ Warning: This action will DELETE ALL COMMITS on the integration branch. This action cannot be undone.

Details

failed to auto-fix conflicts: Failed to get merge content: Failed to merge commits because of conflicting files

@datadog-datadog-prod-us1-2

datadog-datadog-prod-us1-2 Bot commented Aug 25, 2026

Copy link
Copy Markdown

Tests

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

🎯 Code Coverage (details)
Patch Coverage: 77.32%
Overall Coverage: 76.97% (+0.01%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 937ef9e | Docs | View more details | Give us feedback!

@gh-worker-dd-devflow-36fce6

Copy link
Copy Markdown
Contributor

🚂 Branch Integration: starting soon, merge expected in approximately 16m (p90)

Commit c9e4928d35 will soon be integrated into staging-35.

@gh-worker-dd-devflow-36fce6

Copy link
Copy Markdown
Contributor

🚂 Branch Integration

Commit c9e4928d35 has been merged into staging-35 in merge commit 8b883b48a4.

If you need to revert this integration, you can use the following command: /code revert-integration -b staging-35

@cit-pr-commenter-54b7da

Copy link
Copy Markdown

Bundles Sizes Evolution

📦 Bundle Name Base Size Local Size 𝚫 𝚫% Status
Rum 181.52 KiB 181.52 KiB 0 B 0.00%
Rum Profiler 8.43 KiB 8.43 KiB 0 B 0.00%
Rum Recorder 23.15 KiB 25.12 KiB +1.97 KiB +8.51% ⚠️
Logs 57.63 KiB 57.63 KiB 0 B 0.00%
Rum Salesforce N/A 139.59 KiB N/A N/A N/A
Rum Slim 139.59 KiB 139.59 KiB 0 B 0.00%
Worker 22.96 KiB 22.96 KiB 0 B 0.00%
Rum Shopify N/A 205.70 KiB N/A N/A N/A
Rum-shopify Profiler N/A 8.43 KiB N/A N/A N/A
Rum-shopify Recorder N/A 3.72 KiB N/A N/A N/A

⚠️ The increase is particularly high and exceeds 5.00%. Please check the changes.

@gonzalezreal gonzalezreal left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM

@BeltranBulbarellaDD BeltranBulbarellaDD left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All clear on my side!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants