-
Notifications
You must be signed in to change notification settings - Fork 66
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
base: main
Are you sure you want to change the base?
Conversation
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. |
@@ -674,6 +719,69 @@ describe('JsErrorPlugin tests', () => { | |||
plugin.disable(); | |||
|
|||
// Assert | |||
await waitForTick(); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
src/plugins/utils/js-error-utils.ts
Outdated
ajax: fetchFunction | ||
} as StackTrace.StackTraceOptions); | ||
error.stack = stackFrames.join(' \n '); | ||
console.log('appendErrorSourceMaps done'); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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;
}
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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, { |
There was a problem hiding this comment.
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)
``
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
- Enhance stack traces of all JSErrors by taking on dependency
stacktrace-js
- Optionally enable source maps if
sourceMapsEnabled
andsourceMapsFetchFunction
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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> = |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
Thanks so much for the awesome PR! I left a few high level questions and can definitely help close the gaps. |
Of the three fatures:
|
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. |
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.