Skip to content
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

[231] Add Source Map support #565

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

Conversation

andy-root
Copy link


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@andy-root
Copy link
Author

RUM Source Maps

@andy-root
Copy link
Author

Apply source maps to error stacks. Enabling this feature will make fetch calls for javascript source and map files. If these files are cross domain then CORS headers must be included in their responses. If credentials or other headers are required then an optional fetch function is included.

Error events will get handled by plugins that handle errors, these include Fetch, Error and Xhr. Before each plugin records an event it will convert the Error Event to a JS Error Event. This error processing is were the Rum Event is created and the error stack is truncated. This makes for a good location to process the error stack. If the RUM client config has sourceMapsEnabled then the error's stack will be parsed and replaced with a stack written from source maps.

@williazz williazz self-requested a review June 26, 2024 21:35
@@ -674,6 +719,69 @@ describe('JsErrorPlugin tests', () => {
plugin.disable();

// Assert
await waitForTick();
Copy link
Contributor

Choose a reason for hiding this comment

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

Question: thanks for adding these. Were these causing test flakiness?

Copy link
Author

Choose a reason for hiding this comment

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

This was needed because the tests were finishing before the record method has called resulting in failed tests. This is a result of changing recordHttpEventWithError() to an async method. This was the recommend way I found on jest to wait for async methods to finish. https://jestjs.io/docs/tutorial-async

ajax: fetchFunction
} as StackTrace.StackTraceOptions);
error.stack = stackFrames.join(' \n ');
console.log('appendErrorSourceMaps done');
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: please delete the console.logs

Copy link
Author

Choose a reason for hiding this comment

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

missed one, ok

if (error && error.stack) {
try {
const stackFrames = await StackTrace.fromError(error, {
ajax: fetchFunction
Copy link
Contributor

Choose a reason for hiding this comment

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

Issue: What does this ajax option do? I don't see it in documentation.

    export interface StackTraceOptions {
        filter?: (stackFrame: StackFrame) => boolean;
        sourceCache?: SourceCache;
        offline?: boolean;
    }

https://github.com/stacktracejs/stacktrace.js/blob/710bba1118d396466ee342a30b3dfd19ecbda8b5/stacktrace-js.d.ts#L12-L16

Copy link
Contributor

Choose a reason for hiding this comment

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

Question: assuming ajax option works as intended, does that mean an http request is made on every single JSError? If so, is the result cached?

Copy link
Contributor

Choose a reason for hiding this comment

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

Question: how does stacktrace-js identify local source maps?

Copy link
Author

Choose a reason for hiding this comment

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

stacktrace-js uses stacktrace-gps to make the fetch calls. The options object is passed directly to new StackTraceGPS(), https://github.com/stacktracejs/stacktrace.js/blob/master/stacktrace.js#L104 stacktrace-gps takes ajax as an option, https://github.com/stacktracejs/stacktrace-gps/blob/master/README.md

From the stacktrace-gps documentation,

ajax: Function (String URL => Promise(responseText)) - Function to be used for making X-Domain requests

This is an optional fetch function that can be configured to send credentials or other headers when making requests for the js and map files.

When an error is processed fetch calls for js files are made for all the urls listed in the error stack, at the end of the file there should be a link to a map file, this is also fetched. I did not notice refetching of these files again by the browser if the same error occurs.

) => {
if (error && error.stack) {
try {
const stackFrames = await StackTrace.fromError(error, {
Copy link
Contributor

@williazz williazz Jun 26, 2024

Choose a reason for hiding this comment

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

Nit: does this need its own try catch logic to capture validation errors?

From documentation: https://github.com/stacktracejs/stacktrace.js?tab=readme-ov-file#get-stack-trace-from-an-error

var error = new Error('BOOM!');

StackTrace.fromError(error).then(callback).catch(errback);
//===> Promise(Array[StackFrame], Error)
``

Copy link
Author

Choose a reason for hiding this comment

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

I added this in the case that if an error does occur from the stacktrace lib that it gets recorded.

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggestion: if there are validation errors from stacktrace-js, then the JSErrors look like they will be dropped. We may want to record the original error instead as a backup.

Copy link
Author

@andy-root andy-root Jul 1, 2024

Choose a reason for hiding this comment

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

This try catch is here as a precaution is case StackTrace.fromError() fails, we would want to know so the catch error is added to the incoming error's stack . Whether StackTrace.fromError() fails or not, incoming errors are never dropped, the try catch is only attempts at replacing the incoming error's stack value, it does not return anything. We would not want to report the catch error as a normal error because it would create an infinite loop, the try catch helps avoid an error reporting infinite loop.

* required then provide a configured fetch function that returns the response as
* a string promise.
*/
sourceMapsEnabled?: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

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

Issue: to clarify, does this PR actually do two things?

  1. Enhance stack traces of all JSErrors by taking on dependency stacktrace-js
  2. Optionally enable source maps if sourceMapsEnabled and sourceMapsFetchFunction are supplied.

Question: How are the source maps retrieved if sourceMapsFetchFunction is undefined? If supplying sourceMapsFetchFunction is required to enable this feature, then sourceMapsEnabled may be redundant?

Copy link
Author

Choose a reason for hiding this comment

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

Setting sourceMapsEnabled to true is all that is needed unless additional headers or credentials are needed when requesting the js and map file. sourceMapsFetchFunction is optional, users would only provide a fetch method if they need to provide additional headers or credential when the stacktrace lib requests the js and map files.

Copy link
Contributor

Choose a reason for hiding this comment

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

Nit / backlog item (blocking): Before merging, we need to make it clear that this is a client side implementation maps: that is, update configuration names, pr title / description, and update documentation outlining the performance tradeoffs.

RUM service team also needs to make sure the names of your new feature do not clash with any server-side implementation in the future.

Copy link
Contributor

Choose a reason for hiding this comment

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

No action needed, we can clean it up

@@ -110,6 +111,9 @@ export const getSession: jest.MockedFunction<GetSession> = jest.fn(() => ({
eventCount: 0
}));

export const sourceMapsFetchFunction: jest.MockedFunction<SourceMapsFetchFunction> =
Copy link
Contributor

Choose a reason for hiding this comment

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

Backlog item: this needs an integration test to validate that source maps are being applied correctly

Copy link
Author

Choose a reason for hiding this comment

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

Yes, I stoped development and shared the PR so that RUM could decide if this client solution is something they want to move forward with vs. a service side solution or maybe this is a temporary solution? If we want to move forward I will work on the integration tests.

@williazz
Copy link
Contributor

Thanks so much for the awesome PR! I left a few high level questions and can definitely help close the gaps.

@williazz
Copy link
Contributor

Of the three fatures:

  1. Enhance all JSErrors with stacktrace-js - this seems very close to mergable. We need better documentation on the expected diff and value add. Also we need to validate things such as package size increase; and how large JSErrors will become with the additional info since there are hard backend limits.
  2. Apply local source maps - I don't understand if this is supported out of the box. If it is, then I have the same feedback as (1).
  3. Fetch remote source maps - This needs more design based on the above comments, and probably should be a separate PR

@andy-root
Copy link
Author

andy-root commented Jun 28, 2024

Comments on the Three features

For questions about local vs remote source maps, do you mean linked vs. inline? Minified js files will usually have source maps either linked at the end of the js file or the source map content will be included at the end of the js file. You either have 2 files, file.js and file.js.map or 1 larger js file that contains both the source and maps. In either case the change in size of the error stack will be the same.

FYI, I have our production apps using a similar design for applying sourcemaps with RUM and I increased our stacktraces from the default 1000 to 2000 through the RUM config.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants