diff --git a/packages/javascript/.changesets/fix-matching-on-backtrace-paths-containing-spaces.md b/packages/javascript/.changesets/fix-matching-on-backtrace-paths-containing-spaces.md new file mode 100644 index 00000000..4de2d2d3 --- /dev/null +++ b/packages/javascript/.changesets/fix-matching-on-backtrace-paths-containing-spaces.md @@ -0,0 +1,8 @@ +--- +bump: patch +type: fix +--- + +Fix matching on backtrace paths containing spaces. + +When using `matchBacktracePaths`, when a backtrace line path contains a space, it will now match correctly against the whole path. \ No newline at end of file diff --git a/packages/javascript/src/__tests__/span.test.ts b/packages/javascript/src/__tests__/span.test.ts index 6ce60a45..7adc6ab9 100644 --- a/packages/javascript/src/__tests__/span.test.ts +++ b/packages/javascript/src/__tests__/span.test.ts @@ -89,6 +89,35 @@ describe("Span", () => { }) }) + it("cleans Chrome-style backtraces with spaces in the path", () => { + const error = new Error("test error") + error.stack = [ + "Error: test error", + " at Foo (http://localhost:8080/assets with space/app/bundle.js:13:10)", + " at Bar (http://localhost:8080/assets with space/app/bundle.js:17:10)", + " at track (http://thirdparty.app/script.js:1:530)", + " at http://localhost:8080/assets with space/app/bundle.js:21:10" + ].join("\n") + + span.setError(error) + span.cleanBacktracePath( + [new RegExp("/assets with space/(app/.*)$")].map(toBacktraceMatcher) + ) + + const backtrace = span.serialize().error.backtrace + expect(backtrace).toEqual([ + "Error: test error", + " at Foo (app/bundle.js:13:10)", + " at Bar (app/bundle.js:17:10)", + " at track (http://thirdparty.app/script.js:1:530)", + " at app/bundle.js:21:10" + ]) + + expect(span.serialize().environment).toMatchObject({ + backtrace_paths_matched: "3" + }) + }) + it("cleans Safari/FF-style backtraces", () => { const error = new Error("test error") error.stack = [ @@ -116,6 +145,33 @@ describe("Span", () => { }) }) + it("cleans Safari/FF-style backtraces with spaces in the path", () => { + const error = new Error("test error") + error.stack = [ + "Foo@http://localhost:8080/assets with space/app/bundle.js:13:10", + "Bar@http://localhost:8080/assets with space/app/bundle.js:17:10", + "track@http://thirdparty.app/script.js:1:530", + "@http://localhost:8080/assets with space/app/bundle.js:21:10" + ].join("\n") + + span.setError(error) + span.cleanBacktracePath( + [new RegExp("/assets with space/(app/.*)$")].map(toBacktraceMatcher) + ) + + const backtrace = span.serialize().error.backtrace + expect(backtrace).toEqual([ + "Foo@app/bundle.js:13:10", + "Bar@app/bundle.js:17:10", + "track@http://thirdparty.app/script.js:1:530", + "@app/bundle.js:21:10" + ]) + + expect(span.serialize().environment).toMatchObject({ + backtrace_paths_matched: "3" + }) + }) + it("concatenates all match groups", () => { const error = new Error("test error") error.stack = [ diff --git a/packages/javascript/src/span.ts b/packages/javascript/src/span.ts index a006dfb3..ddd4cd0f 100644 --- a/packages/javascript/src/span.ts +++ b/packages/javascript/src/span.ts @@ -186,15 +186,22 @@ function extractPath(backtraceLine: string): string | undefined { // A Chrome backtrace line always contains `at` near the beginning, // preceded by space characters and followed by one space. const IS_CHROME = /^\s*at\s/ + // In a Chrome backtrace line, the path (if it is available) - // is located, usually within parentheses, after the "@" towards the - // end of the line, along with the line number and column number, - // separated by colons. We check for those to reject clear non-paths. - const CHROME_PATH = /at(?:\s.*)?\s\(?(.*):\d*:\d*\)?$/i + // is located after the "at" towards the end of the line, along with + // the line number and column number, separated by colons. + // When the function name is available, the path is enclosed in + // parentheses. When there is no function name, the path comes + // immediately after the "at". + // We check for the line and column numbers to reject clear non-paths. + const CHROME_PATH_WITH_FUNCTION_NAME = /^\s*at(?:\s[^\(]+)?\s\((.*):\d+:\d+\)$/ + const CHROME_PATH_WITHOUT_FUNCTION_NAME = /^\s*at\s(.*):\d+:\d+$/ if (backtraceLine.match(IS_CHROME)) { - const match = backtraceLine.match(CHROME_PATH) - return match ? match[1] : undefined + return ( + backtraceLine.match(CHROME_PATH_WITH_FUNCTION_NAME)?.[1] ?? + backtraceLine.match(CHROME_PATH_WITHOUT_FUNCTION_NAME)?.[1] + ) } // A Safari or Firefox backtrace line always contains `@` after the first @@ -205,10 +212,9 @@ function extractPath(backtraceLine: string): string | undefined { // is located after the "@" at the towards end of the line, followed by // the line number and column number, separated by colons. We check for // those to reject clear non-paths. - const SAFARI_FF_PATH = /@\s?(.*):\d*:\d*$/i + const SAFARI_FF_PATH = /^.*@\s?(.*):\d+:\d+$/ if (backtraceLine.match(IS_SAFARI_FF)) { - const match = backtraceLine.match(SAFARI_FF_PATH) - return match ? match[1] : undefined + return backtraceLine.match(SAFARI_FF_PATH)?.[1] } }