Skip to content

fix: skip unwanted stackframe on custom errors for faulty browsers #3555

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

Open
wants to merge 46 commits into
base: main
Choose a base branch
from

Conversation

leomelki
Copy link
Member

@leomelki leomelki commented May 22, 2025

Motivation

Custom error stacktrace fix
Some browsers (safari/firefox) add the error constructor as a frame in the stacktrace.
In order to normalize the stacktrace, we need to remove it.

Changes

core/src/tools/stackTrace/computeStackTrace.ts was changed :

  • Added a detection of faulty browsers (relying on the runtime behaviour and not in heuristics not an empirical list of browser versions)
  • Added a detection of custom errors verifying if the error's constructor is a class (the behaviour difference only affects classes and not other objects)

test/e2e/scenario/rum/errors.scenario.ts was changed:

  • added 2 e2e tests to check for the fixed behaviour on custom errors with and without custom names.

Test instructions

Trigger a custom error in Chrome and in Firefox. They should now have the same stacktrace :

class CustomError extends Error {
  constructor(message) {
    super(message)
    this.name = 'CustomError'
  }
}
throw new CustomError('Custom error')

Tests done in staging

image
image

Checklist

  • Tested locally
  • Tested on staging
  • Added unit tests for this change.
  • Added e2e/integration tests for this change.

@codecov-commenter
Copy link

codecov-commenter commented May 22, 2025

Codecov Report

Attention: Patch coverage is 62.50000% with 9 lines in your changes missing coverage. Please review.

Project coverage is 92.01%. Comparing base (61824b8) to head (204bef7).
Report is 51 commits behind head on main.

Files with missing lines Patch % Lines
...ges/core/src/tools/stackTrace/computeStackTrace.ts 62.50% 9 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3555      +/-   ##
==========================================
- Coverage   92.11%   92.01%   -0.11%     
==========================================
  Files         317      317              
  Lines        8082     8105      +23     
  Branches     1827     1835       +8     
==========================================
+ Hits         7445     7458      +13     
- Misses        637      647      +10     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@leomelki
Copy link
Member Author

/to-staging

@dd-devflow
Copy link
Contributor

dd-devflow bot commented May 26, 2025

View all feedbacks in Devflow UI.

2025-05-26 09:21:22 UTC ℹ️ Start processing command /to-staging
If you need support, contact us on Slack #devflow!


2025-05-26 09:22:08 UTC 🚨 Devflow

failed to update GIT_BASE_BRANCH: activity error (type: github.GithubService_GetFile, scheduledEventID: 31, startedEventID: 32, identity: 1@github-worker-8c57d5bd9-ql2c7@): GET https://api.github.com/repos/DataDog/browser-sdk/contents/.gitlab-ci.yml?ref=6f9271f5a31cd3bcc4ddfa8b2a5755fa2cf336fe: 504 We couldn't respond to your request in time. Sorry about that. Please try resubmitting your request and contact us if the problem persists. [] (type: ErrorResponse, retryable: true)

Details
child workflow execution error (type: devflow.Devflow_ToStaging, workflowID: 87ebd210-b208-4357-9819-e53bf0ea4fd0_35, runID: ece73205-7a71-49a0-86a8-44bd9d269bf2, initiatedEventID: 35, startedEventID: 36): child workflow execution error (type: devflow.Devflow_IntegratePullRequest, workflowID: ece73205-7a71-49a0-86a8-44bd9d269bf2_16, runID: 3d6b6caa-7bf7-474e-b305-2f7133bc0062, initiatedEventID: 16, startedEventID: 17): child workflow execution error (type: devflow.Devflow_Integrate, workflowID: 3d6b6caa-7bf7-474e-b305-2f7133bc0062_23, runID: 748a0383-fe00-4c56-bf51-a9ba4f6e739f, initiatedEventID: 23, startedEventID: 24): failed to update GIT_BASE_BRANCH: activity error (type: github.GithubService_GetFile, scheduledEventID: 31, startedEventID: 32, identity: 1@github-worker-8c57d5bd9-ql2c7@): GET https://api.github.com/repos/DataDog/browser-sdk/contents/.gitlab-ci.yml?ref=6f9271f5a31cd3bcc4ddfa8b2a5755fa2cf336fe: 504 We couldn't respond to your request in time. Sorry about that. Please try resubmitting your request and contact us if the problem persists. [] (type: ErrorResponse, retryable: true) (type: wrapError, retryable: true): activity error (type: github.GithubService_GetFile, scheduledEventID: 31, startedEventID: 32, identity: 1@github-worker-8c57d5bd9-ql2c7@): GET https://api.github.com/repos/DataDog/browser-sdk/contents/.gitlab-ci.yml?ref=6f9271f5a31cd3bcc4ddfa8b2a5755fa2cf336fe: 504 We couldn't respond to your request in time. Sorry about that. Please try resubmitting your request and contact us if the problem persists. [] (type: ErrorResponse, retryable: true)

If you need support, contact us on Slack #devflow with those details!

@leomelki
Copy link
Member Author

/to-staging

@dd-devflow
Copy link
Contributor

dd-devflow bot commented May 26, 2025

View all feedbacks in Devflow UI.

2025-05-26 09:23:06 UTC ℹ️ Start processing command /to-staging


2025-05-26 09:23:35 UTC ℹ️ Branch Integration: starting soon, merge expected in approximately 0s (p90)

Commit b4b96ffdcc will soon be integrated into staging-22.


2025-05-26 09:42:27 UTC ℹ️ Branch Integration: This commit was successfully integrated

Commit b4b96ffdcc has been merged into staging-22 in merge commit c5b4e1612c.

Check out the triggered pipeline on Gitlab 🦊

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

dd-mergequeue bot added a commit that referenced this pull request May 26, 2025
…to staging-22

Integrated commit sha: b4b96ff

Co-authored-by: leomelki <[email protected]>
@DataDog DataDog deleted a comment from dd-devflow bot May 26, 2025
@DataDog DataDog deleted a comment from dd-devflow bot May 26, 2025
@leomelki
Copy link
Member Author

/to-staging

@dd-devflow
Copy link
Contributor

dd-devflow bot commented May 26, 2025

View all feedbacks in Devflow UI.

2025-05-26 11:45:34 UTC ℹ️ Start processing command /to-staging


2025-05-26 11:45:41 UTC ℹ️ Branch Integration: starting soon, merge expected in approximately 18m58s (p90)

Commit b8edf0b10e will soon be integrated into staging-22.


2025-05-26 12:04:31 UTC ℹ️ Branch Integration: This commit was successfully integrated

Commit b8edf0b10e has been merged into staging-22 in merge commit cc692c8e83.

Check out the triggered pipeline on Gitlab 🦊

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

dd-mergequeue bot added a commit that referenced this pull request May 26, 2025
…to staging-22

Integrated commit sha: b8edf0b

Co-authored-by: leomelki <[email protected]>
@leomelki leomelki marked this pull request as ready for review May 26, 2025 13:14
@leomelki leomelki requested a review from a team as a code owner May 26, 2025 13:14
Comment on lines 216 to 221
let WRONGLY_REPORTING_CUSTOM_ERRORS: boolean | undefined

function isWronglyReportingCustomErrors() {
if (WRONGLY_REPORTING_CUSTOM_ERRORS !== undefined) {
return WRONGLY_REPORTING_CUSTOM_ERRORS
}
Copy link

@skimi skimi May 27, 2025

Choose a reason for hiding this comment

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

Could we initialize this when the file loads and avoid the if (WRONGLY_REPORTING_CUSTOM_ERRORS !== undefined)?

And WRONGLY_REPORTING_CUSTOM_ERRORS could be used directly, skipping isWronglyReportingCustomErrors

Suggested change
let WRONGLY_REPORTING_CUSTOM_ERRORS: boolean | undefined
function isWronglyReportingCustomErrors() {
if (WRONGLY_REPORTING_CUSTOM_ERRORS !== undefined) {
return WRONGLY_REPORTING_CUSTOM_ERRORS
}
const WRONGLY_REPORTING_CUSTOM_ERRORS = (function isWronglyReportingCustomErrors() {
...
})();

Copy link
Member Author

@leomelki leomelki May 27, 2025

Choose a reason for hiding this comment

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

I tried but the linter doesn't like it :

CallExpression can have side effects when the module is evaluated. Maybe move it in a function declaration?eslint(local-rules/disallow-side-effects)

I can change the code to do this outside of a function but it think it would be way less clean

}
}

const [customError, normalError] = [DatadogTestCustomError, Error].map((errConstructor) => new errConstructor()) // so that both errors should exactly have the same stacktrace
Copy link
Member

Choose a reason for hiding this comment

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

praise: smart!

@@ -48,6 +48,33 @@ export function computeStackTrace(ex: unknown): StackTrace {
})
}

if (stack.length > 0 && isWronglyReportingCustomErrors()) {
// if we are wrongly reporting custom errors
if (ex instanceof Error && isErrorCustomError(ex)) {
Copy link
Member

Choose a reason for hiding this comment

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

suggestion: the isErrorCustomError(ex) check should not be needed here
suggestion: merge the two if

Copy link
Member Author

Choose a reason for hiding this comment

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

done ✅ (with the iserrorcustomerror to isnonnativeclassprototype function change)

Comment on lines 56 to 74
const constructors: string[] = []

// go through each inherited constructor
let cstr: object | undefined = ex
while ((cstr = Object.getPrototypeOf(cstr)) !== Error.prototype && cstr) {
const errorConstructorName = cstr.constructor?.name || UNKNOWN_FUNCTION
constructors.push(errorConstructorName)
}

// traverse the stacktrace in reverse order because the stacktrace starts with the last inherited constructor
// we check constructor names to ensure we remove the correct frame (and there isn't a weird unsupported environment behavior)
for (let i = constructors.length - 1; i >= 0; i--) {
if (stack[0]?.func === constructors[i]) {
// if the first stack frame is the custom error constructor
stack.shift() // remove it
} else {
break
}
}
Copy link
Member

Choose a reason for hiding this comment

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

suggestion:
Can we have something like this?

  if (isWronglyReportingCustomErrors() && ex instanceof Error) {
    let currentPrototype = Object.getPrototypeOf(ex)
    while (
      currentPrototype &&
      isNonNativeClassPrototype(currentPrototype) &&
      stack.length > 0 &&
      stack[0].func === (currentPrototype.constructor.name || UNKNOWN_FUNCTION)
    ) {
      stack.shift()
      currentPrototype = Object.getPrototypeOf(currentPrototype)
    }
  }

with

function isNonNativeClassPrototype(prototype: object) {
  return String(prototype.constructor).startsWith('class ')
}

Copy link
Member Author

Choose a reason for hiding this comment

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

moving the currentPrototype assignment in the while loop would increase the build size slightly as we would have to initialize the variable. Is this necessary ?

Copy link
Member

@BenoitZugmeyer BenoitZugmeyer Jun 6, 2025

Choose a reason for hiding this comment

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

Ok, WDYT of:

  if (isWronglyReportingCustomErrors() && ex instanceof Error) {
    let currentPrototype = ex
    while (
      (currentPrototype = Object.getPrototypeOf(currentPrototype)) &&
      isNonNativeClassPrototype(currentPrototype) &&
      stack.length > 0 &&
      stack[0].func === (currentPrototype.constructor.name || UNKNOWN_FUNCTION)
    ) {
      stack.shift()
    }
  }

It's a bit more cryptic but... I'd be fine with it.

Copy link

cit-pr-commenter bot commented Jun 3, 2025

Bundles Sizes Evolution

📦 Bundle Name Base Size Local Size 𝚫 𝚫% Status
Rum 0 B 145.98 KiB 145.98 KiB N/A%
Rum Recorder 0 B 18.03 KiB 18.03 KiB N/A%
Rum Profiler 0 B 4.73 KiB 4.73 KiB N/A%
Logs 0 B 50.88 KiB 50.88 KiB N/A%
Rum Slim 0 B 105.62 KiB 105.62 KiB N/A%
Worker 0 B 23.59 KiB 23.59 KiB N/A%
🚀 CPU Performance
Action Name Base Average Cpu Time (ms) Local Average Cpu Time (ms) 𝚫
addglobalcontext N/A 0.007 0.007
addaction N/A 0.024 0.024
addtiming N/A 0.005 0.005
adderror N/A 0.024 0.024
startstopsessionreplayrecording N/A 0.001 0.001
startview N/A 0.005 0.005
logmessage N/A 0.030 0.030
🧠 Memory Performance
Action Name Base Consumption Memory (bytes) Local Consumption Memory (bytes) 𝚫 (bytes)
addglobalcontext NaN KiB 26.41 KiB NaN KiB
addaction NaN KiB 57.12 KiB NaN KiB
addtiming NaN KiB 24.69 KiB NaN KiB
adderror NaN KiB 60.23 KiB NaN KiB
startstopsessionreplayrecording NaN KiB 24.45 KiB NaN KiB
startview NaN KiB 430.62 KiB NaN KiB
logmessage NaN KiB 58.03 KiB NaN KiB

🔗 RealWorld

@leomelki
Copy link
Member Author

leomelki commented Jun 3, 2025

Just found an INSANE behaviour from WebKit (not Firefox) :

When a custom error class does not have a constructor defined, it is not shown in the parsed stacktrace.

Why ?

Because WebKit does not report its stackframe origin (line/file). Thus, the parsing fails.

@leomelki leomelki force-pushed the leomelki/ERRORT-5209_custom-error-stacktrace-fix branch 3 times, most recently from dfd486b to 734d990 Compare June 3, 2025 14:16
@leomelki leomelki force-pushed the leomelki/ERRORT-5209_custom-error-stacktrace-fix branch from 734d990 to 79239b7 Compare June 3, 2025 14:30
@leomelki
Copy link
Member Author

leomelki commented Jun 6, 2025

@BenoitZugmeyer

Can you add a comment to clarify this?

Done in f6346fd

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.

4 participants