Upgrade Dynatrace RUM: script-tag loading, runtime config, fetch tooling - #425
Draft
redbugz wants to merge 34 commits into
Draft
Upgrade Dynatrace RUM: script-tag loading, runtime config, fetch tooling#425redbugz wants to merge 34 commits into
redbugz wants to merge 34 commits into
Conversation
- Add new inline scripts for all three environments (int, beta, prod) with agent version 10337260504112723 - Update dynatrace.ejs with frontier_snow_dynatraceNewRUM feature flag for gradual rollout - When new flag is ON: use 1.329+ version with enhanced data collection (owasp=1, uxrgce=1) - When new flag is OFF: use existing RUM version (backward compatible) - All three mechanisms supported: asyncCS-script, asyncCS-inline, global-cdn - New CDN URLs use SRI integrity hashes for added security - Existing frontier_snow_dynatraceRUM flag unchanged (still controls mechanism selection) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Add fetch-dynatrace-scripts.js helper for automated future RUM updates - Fetches latest scripts from Dynatrace API - Auto-writes inline scripts to _inline_*_new.ejs files - Displays CDN URLs and integrity hashes in copy-paste format - Add DYNATRACE_RUM_UPDATE.md with complete update procedures - Documents API setup, entity IDs, and testing procedures - Enables maintainers to automate RUM upgrades in the future Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Report the active loading mechanism (asyncCS-script, asyncCS-inline, or global-cdn) as a custom metric to Dynatrace. This enables direct DQL queries to correlate loading mechanism with Core Web Vitals performance (LCP, CLS, page load time). - Add window.dtrum.reportCustomMetric call to emit rum_loading_mechanism value - Bump version to 8.17.0-alpha.1 for telemetry feature Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
The RUM agent needs the data-dtconfig attribute (which includes reportUrl) to send beacons directly to the Dynatrace tenant instead of back through the app. Previously, only src and integrity were being extracted from the Dynatrace API response. Changes: - Extract and preserve data-dtconfig from complete script tags - Add cdnConfigNew object in dynatrace.ejs with per-environment configs - Apply data-dtconfig attribute to asyncCS-script and global-cdn mechanisms - Print config extraction confirmation in fetch script This fixes the 405 POST errors by ensuring RUM beacons are routed to the correct Dynatrace backend endpoint (reportUrl), not to the app. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Includes full data-dtconfig with proper reportUrl for direct beacon delivery to Dynatrace tenant. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
The new RUM script expects config in JSON format (data-config, data-appconfig, data-envconfig) which the fetch script provides in the inline script files. External script tags cannot reliably read data-* attributes, so we use the inline approach for both asyncCS-script and global-cdn mechanisms when new RUM is enabled. This fixes the initialization error: 'DT_RUM will not initialize: None of the supported configuration sources contain the required properties [revision, environmentId, app]' Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
…ease - Reflect version bump to 8.17.0-alpha.2 in package-lock.json - Make fetch-dynatrace-scripts.js executable Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Fixes RUM initialization by using inline script approach with proper JSON config format for new RUM. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Report rum_loading_mechanism metric immediately after each RUM script loads, rather than at the end of page. This ensures the custom metric is captured in the first page summary event before any beacons are sent. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Fixes telemetry timing to report RUM loading mechanism before first page summary beacon. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
asyncCS-script should load the script asynchronously to not block page rendering. Add missing async attribute to old RUM Edge CDN script. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This reverts commit 29ce2e3.
- Update fetch-dynatrace-scripts.js to call /api/v2/rum/inlineCode endpoint for actual JavaScript code - Write actual minified inline code to _inline_*_new.ejs files (300-460KB) instead of script tags - Fix dynatrace.ejs to properly differentiate the three mechanisms: * asyncCS-script: async external from CDN (Edge for old, Dynatrace for new) * asyncCS-inline: full inline embedding (300-460KB for new, ~35KB for old) * global-cdn: synchronous external from Dynatrace CDN - Each mechanism now clearly uses distinct loading patterns for telemetry differentiation This enables accurate performance comparison of mechanisms via Dynatrace telemetry. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Fetch and display: - /api/v2/rum/javaScriptTag (simple tag, 157 chars) - /api/v2/rum/oneAgentJavaScriptTagWithSri (SRI tag, ~1KB) - /api/v2/rum/inlineCode (inline code, 300-460KB) Shows both CDN URLs for comparison: basic variant vs new SRI variant. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Extract env values into separate helper function for reusability - Update dynatrace.ejs with per-env fallback pattern using locals.dynatrace - Generate dynatrace-rum-config.json for Snow to publish to CDN - Snow can inject fresh values at runtime via locals.dynatrace override - Clearer next steps in console output This enables decoupling: react-scripts has built-in fallback values, but Snow can override with latest values from dynatrace-rum-config.json CDN file. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Update dynatrace.ejs to support Snow providing fresh config values via res.locals.dynatrace, with fallback to hardcoded values. This enables: - Old Snow: transparently uses hardcoded fallback values - New Snow: overrides with fresh values from CDN config file - Old react-scripts + new Snow: new RUM works with stale but functional config - New react-scripts + old Snow: new RUM works with publish-time config For asyncCS-inline, also check for Snow-provided inline script before falling back to included file. This completes the progressive enhancement architecture where react-scripts carries current values at publish time, and Snow can override them dynamically without breaking older app/runtime combinations. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Script now publishes dynatrace-rum-config.json to S3 after updating local files. Uses AWS CLI (aws s3 cp) instead of SDK, which: - Leverages existing AWS credential infrastructure (~/.aws/credentials or env vars) - No additional Node dependencies needed - Simpler setup: just 'aws configure' or set AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY - Works with standard AWS credential chain (no custom keychain storage) Configuration: - S3_PUBLISH_BUCKET: S3 bucket for config file (default: frontier-rum-config) - S3_PUBLISH_REGION: AWS region (default: us-east-1) - Both can be overridden via environment variables Keychain setup now only stores Dynatrace API token (AWS uses standard auth). Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Simplify the configuration property names published to S3 and used by Snow: - cdnUrlsNew → cdnUrls - cdnIntegrityNew → cdnIntegrity - cdnConfigNew → cdnConfig - inlineScriptNew → inlineScript Updates: - fetch-dynatrace-scripts.js: generate cleaner property names - dynatrace.ejs: read from locals.dynatrace with cleaner keys - dynatrace-rum-config.json: updated config structure The 'New' postfix was redundant since these are already scoped to the new RUM. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Snow servers and other services can now fetch dynatrace-rum-config.json without requiring AWS credentials or special access. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
…ents Convert template literals to string concatenation in include() calls for asyncCS-inline, asyncCS-script, and global-cdn fallback branches. EJS parser cannot handle backticks inside template tags. Also consolidate reporting mechanism calls to reduce duplication.
EJS requires <%- %> (with hyphen) to output included file content. Control-flow syntax <% %> cannot execute include() function calls.
Revert to <% include(...) %> syntax without hyphen, matching the pattern in layout.ejs. Remove leading './' from path since include() resolves relative to views directory.
EJS include directive requires a static identifier or variable, not inline function calls. Pre-compute dynamic paths using string concatenation, then pass the variable to include for proper resolution.
…ents Convert template literals to string concatenation in include() calls for asyncCS-inline, asyncCS-script, and global-cdn fallback branches. EJS parser cannot handle backticks inside template tags. Use <%- include() %> with string concatenation to support dynamic path calculation.
Log which treatment and code path is taken during dynatrace initialization to confirm we're running the latest deployed version.
Replace optional chaining (?.) with standard property access checks. Pre-compute include paths as variables to avoid string concatenation in include() calls, which may confuse EJS parser.
The new RUM agent's minified JS contains the "<%" sequence, which EJS
include() misreads as an unterminated scriptlet ("Could not find matching
close tag"), causing a 500. Load the new agent exclusively via <script>
tags (SRI tag or _complete.js) instead of inlining, and remap each
treatment to a Dynatrace snippet format:
asyncCS-script -> OneAgent JS tag + SRI, async
asyncCS-inline -> OneAgent JS tag + SRI, sync (no longer inlines)
global-cdn -> _complete.js, async
- Rewrite fetch-dynatrace-scripts.js: drop /inlineCode fetch and
_inline_*_new.ejs generation; add cdnCompleteUrls; robust fallback
rewriter that preserves the locals-first progressive-enhancement guard
- Delete generated _inline_*_new.ejs; old-RUM _inline_*.ejs untouched
- Refresh CDN config to agent 10337260526121002; update docs and changelog
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…um-upgrade # Conflicts: # CHANGELOG-FRONTIER.md # package-lock.json # packages/react-scripts/package.json
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n fix - Preflight 'aws sts get-caller-identity' before any Dynatrace work; warn when AWS_PROFILE is unset and print SSO-aware setup guidance on failure (fail fast) - Rely on the standard AWS_PROFILE / credential chain (aws children inherit env) - Print the downloaded RUM agent version at the end of the run - Align KEYCHAIN_SERVICE_DYNATRACE with the stored keychain item + fix setup doc Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Consume the latest RUM release via fetch-dynatrace-scripts.js: refresh the dynatrace.ejs publish-time fallbacks and dynatrace-rum-config.json (also republished to S3). Supersedes agent 10337260526121002. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Drop the per-render '[dynatrace.ejs] Loaded' server log (redundant with the existing sendSessionProperties RUM telemetry; avoids prod log spam) - Sync package-lock.json self-version to 8.17.0 to match package.json (final release) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR upgrades Dynatrace RUM to a newer agent version and switches new-RUM loading to <script> tags (avoiding EJS include() compilation failures), adds runtime-config fallbacks via locals.dynatrace.*, and introduces tooling/docs to automate future RUM updates and publishing of a config artifact.
Changes:
- Reworked
dynatrace.ejsto support dual-flag rollout (mechanism vs version) and load new RUM exclusively via script tags with SRI/config support. - Added a fetch/publish automation script plus a generated
dynatrace-rum-config.jsonartifact for Snow runtime injection. - Bumped
@fs/react-scriptsto 8.17.0 and documented/released the change.
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/react-scripts/layout/views/partials/dynatrace.ejs | Adds new-RUM script-tag branches, locals-first runtime config, and dual-flag gating. |
| packages/react-scripts/scripts/fetch-dynatrace-scripts.js | New automation script to fetch Dynatrace tags, update EJS fallbacks, generate config JSON, and publish to S3. |
| packages/react-scripts/scripts/dynatrace-rum-config.json | Generated per-env CDN URL/SRI/config values for Snow to consume at runtime. |
| packages/react-scripts/scripts/DYNATRACE_RUM_UPDATE.md | Operator runbook for fetching/publishing and validating treatment mappings. |
| packages/react-scripts/package.json | Version bump to 8.17.0. |
| package-lock.json | Lockfile version update for the react-scripts package entry. |
| CHANGELOG-FRONTIER.md | Release notes for 8.17.0 Dynatrace RUM changes. |
Comments suppressed due to low confidence (3)
packages/react-scripts/scripts/DYNATRACE_RUM_UPDATE.md:81
- This relative link points to ./partials/dynatrace.ejs, but the actual file lives under layout/views/partials/dynatrace.ejs. As written, the link is broken.
Two flags drive the experiment (see [dynatrace.ejs](./partials/dynatrace.ejs)):
packages/react-scripts/scripts/DYNATRACE_RUM_UPDATE.md:184
- These relative links are incorrect from the scripts/ directory. dynatrace.ejs is under layout/views/partials/, and fetch-dynatrace-scripts.js is the current file (same directory), so the links should be updated to avoid 404s in the runbook.
- [dynatrace.ejs](./partials/dynatrace.ejs) - Main RUM configuration file
- [fetch-dynatrace-scripts.js](./fetch-dynatrace-scripts.js) - The fetch script itself
packages/react-scripts/scripts/DYNATRACE_RUM_UPDATE.md:73
- The example snippet treats cdnIntegrityNew as a single string, but dynatrace.ejs defines cdnIntegrityNew as an environment-keyed object (and integrity values may differ per env). The example should reflect the actual shape.
```javascript
const cdnUrlsNew = {
int: '...', // Copy from script output
beta: '...', // Copy from script output
prod: '...' // Copy from script output
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+52
to
+65
| ### Step 2: Update CDN URLs in dynatrace.ejs | ||
|
|
||
| The script output will display CDN URLs in this format: | ||
|
|
||
| ``` | ||
| CDN URLs for dynatrace.ejs (cdnUrlsNew object): | ||
|
|
||
| int: 'https://js-cdn.dynatrace.com/...', | ||
| beta: 'https://js-cdn.dynatrace.com/...', | ||
| prod: 'https://js-cdn.dynatrace.com/...', | ||
|
|
||
| Integrity hash for all environments: | ||
| cdnIntegrityNew = 'sha256-...' | ||
| ``` |
Comment on lines
+93
to
+97
| <script type="text/javascript"> | ||
| if (typeof window !== 'undefined' && window.dtrum) { | ||
| window.dtrum.sendSessionProperties({rum_loading_mechanism: "<%= dynatraceFlag.treatment %>"}); | ||
| } | ||
| </script> |
Comment on lines
+269
to
+287
| async function publishToS3(config) { | ||
| try { | ||
| const configJson = JSON.stringify(config); | ||
| const tempFile = path.join(__dirname, '.dynatrace-rum-config-temp.json'); | ||
| fs.writeFileSync(tempFile, configJson, 'utf8'); | ||
|
|
||
| // Use AWS CLI for S3 upload (inherits this process's env → honors AWS_PROFILE / default chain) | ||
| const awsCmd = `aws s3 cp "${tempFile}" s3://${S3_PUBLISH_BUCKET}/dynatrace-rum-config.json --region ${S3_PUBLISH_REGION} --metadata "generated=$(date +%s)" --cache-control "max-age=300" --acl public-read`; | ||
|
|
||
| execSync(awsCmd, { stdio: 'inherit' }); | ||
| fs.unlinkSync(tempFile); | ||
|
|
||
| console.log(` ✅ Published dynatrace-rum-config.json to S3 (s3://${S3_PUBLISH_BUCKET}/dynatrace-rum-config.json)`); | ||
| } catch (error) { | ||
| console.error(` ❌ Failed to publish to S3: ${error.message}`); | ||
| printAwsSetupInstructions(); | ||
| throw error; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Upgrades Dynatrace RUM to the latest agent (build
10341260622154106) and loads it exclusively via<script>tags to fix an EJS 500, adds runtime config injection with baked-in fallbacks, and adds a script to automate future RUM upgrades. Ships as@fs/react-scripts8.17.0.Reasoned Changes
New RUM loaded exclusively via script tags (dynatrace.ejs)
What: Rewrote the three treatment branches so that when
frontier_snow_dynatraceNewRUMis on, each mechanism emits a<script>tag (SRI async, SRI sync, or_complete.jsasync) instead of an EJSinclude()of the agent.Why: The 1.341-era agent's minified JS contains the EJS open-delimiter (
<%) inside a string, soinclude()made EJS 500 with "Could not find matching close tag" on every page. Loading only via script tags sidesteps the EJS compile step and avoids re-shipping 300–460 KB per view.asyncCS-inlinewas remapped to a synchronous SRI tag (name kept for old-RUM compatibility) since the new RUM has no small-bootstrap format.Runtime config injection with baked-in fallbacks
What: Each new-RUM value reads
locals.dynatrace.*first, falling back to publish-time constants; addsscripts/dynatrace-rum-config.json.Why: RUM app IDs / integrity /
data-dtconfigrotate Dynatrace-side with no way to refresh without a react-scripts release. This lets Snow supply fresh values at runtime (from the S3 config, consumed by fs-webdev/snow#446) while fallbacks keep older Snow deploys working. Two independent flags separate mechanism rollout from version rollout; with the version flag off, behavior is unchanged.Release automation for future RUM upgrades (fetch-dynatrace-scripts.js)
What: Node script that calls the Dynatrace tag endpoints per env, rewrites the
dynatrace.ejsfallbacks, writes the config JSON, and publishes it to S3.Why: No repeatable path existed to consume new RUM releases. Fails fast with an
aws sts get-caller-identitypreflight + SSO-aware guidance, warns whenAWS_PROFILEis unset, prints the downloaded agent version, and reads the token from the macOS keychain.Release metadata and operator documentation
What: Version bump to 8.17.0,
CHANGELOG-FRONTIER.md8.17.0 entry, andscripts/DYNATRACE_RUM_UPDATE.mdrunbook.Why: Ships as the
@fs/react-scriptsrelease Snow consumes. The runbook documents the non-obviousasyncCS-inlineremap so future operators don't "fix" it back to an inline include.Related PRs
dynatrace-rum-config.jsonthis PR publishes and thelocals.dynatrace.*valuesdynatrace.ejsreads.layout.ejsinclude conversion (merged fromfrontierMaster) targets.Risk Assessment
Residual risk is server-side render behavior in
dynatrace.ejsand the template-rewriting regex in the fetch script.replaceFallback— matches the fallback body with{[^}]+}, stopping at the first}; a futuredata-dtconfigvalue with a literal/decoded}would truncate the rewrite. Values are URL-encoded today, so low-probability, but worth knowing.asyncCS-inline(new) — emits a synchronous SRI<script>; if the baked-inintegrityfallback ever drifts from the CDN asset andlocals.dynatraceis absent (older Snow deploy), the browser blocks it under SRI and RUM stops silently. Mitigated by thelocals.dynatraceoverride (snow#446).Testing
.ejstemplates compile under EJS 3.1.10 (the version this must survive) via a standaloneejs.compileharness — the exact regression class behind the original 500.locals.dynatrace.*overrides fallbacks, old-RUM paths unchanged.fetch-dynatrace-scripts.jssyntax-checked; AWS preflight fail-fast path exercised; ran end-to-end against Dynatrace to produce the committed config (agent10341260622154106, published to S3).npm testis a no-op); verification was manual/scripted.