From 51783cbb716b2c9c4a81055b8f9a573080b73104 Mon Sep 17 00:00:00 2001 From: Noemi Lapresta Date: Tue, 30 Sep 2025 10:46:59 +0200 Subject: [PATCH] Match on the entire backtrace path line On the `SAFARI_FF_PATH` regular expressions, match the whole line from beginning to end, repeating the matching at the beginning of the line that is done by the `IS_SAFARI_FF` regular expression. Split the `CHROME_PATH` regular expression into two variants of it, with and without a function name. When the function name is present, the path appears in parenthesis, making it easier to delimit it. Attempt to match the version where the path is in parenthesis first, then the version where the path stands alone. On both variants, like with `SAFARI_FF_PATH`, match the whole line from beginning to end. This fixes a bug where the regular expressions would only match the last space-separated segment of a path containing spaces. --- ...ng-on-backtrace-paths-containing-spaces.md | 8 +++ .../javascript/src/__tests__/span.test.ts | 56 +++++++++++++++++++ packages/javascript/src/span.ts | 24 +++++--- 3 files changed, 79 insertions(+), 9 deletions(-) create mode 100644 packages/javascript/.changesets/fix-matching-on-backtrace-paths-containing-spaces.md 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] } }