Skip to content

Commit d47db98

Browse files
shumoneAbinet18
andauthored
Update regex filter + unit tests (#852)
Co-authored-by: Abinet Debele <[email protected]>
1 parent c87fee8 commit d47db98

File tree

4 files changed

+244
-1
lines changed

4 files changed

+244
-1
lines changed

packages/web/src/SplunkErrorInstrumentation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ function stringifyValue(value: unknown) {
4040
function parseErrorStack(stack: string): string {
4141
//get list of files in stack , find corresponding sourcemap id and add it to the source map id object
4242
const sourceMapIds = {};
43-
const urlPattern = /(https?:\/\/[^\s]+\/[^\s:]+|\/[^\s:]+)/g;
43+
const urlPattern = /([\w]+:\/\/[^\s/]+\/[^\s?:#]+)/g;
4444
const urls = stack.match(urlPattern);
4545
if (urls) {
4646
urls.forEach(url => {

packages/web/test/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,4 @@ import './SplunkSpanAttributesProcessor.test';
3131
import './SplunkOtelWeb.test';
3232
import './synthetics.test';
3333
import './socketio.test';
34+
import './stacktrace.test';

packages/web/test/stacktrace.test.ts

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
/*
2+
Copyright 2024 Splunk Inc.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import * as assert from 'assert';
18+
import { beforeEach } from 'mocha';
19+
import { generateFilePaths, generateRandomStackTrace } from './utils';
20+
21+
const chromeStackTraceEval = `Error: Something went wrong
22+
at eval (eval at <anonymous> (http://example.com/scripts/main.js:10:20), <anonymous>:1:1)
23+
at Object.functionName (http://example.com/scripts/utils.js:15:25)
24+
at http://example.com/scripts/app.js:20:30
25+
at new ConstructorName (http://example.com/scripts/controller.js:25:35)
26+
at http://example.com/scripts/main.js:30:40`;
27+
const chromeStackTraceEvalExpected = [
28+
'http://example.com/scripts/main.js',
29+
'http://example.com/scripts/utils.js',
30+
'http://example.com/scripts/app.js',
31+
'http://example.com/scripts/controller.js',
32+
];
33+
34+
const chromeStackTraceAnonymous = `TypeError: undefined is not a function
35+
at http://example.com/js/anonymous.js:10:5
36+
at <anonymous>:15:10
37+
at Object.functionName (http://example.com/js/utils.js:20:15)
38+
at new ConstructorName (http://example.com/js/app.js:25:20)
39+
at <anonymous>:30:25`;
40+
const chromeStackTraceAnonymousExpected = [
41+
'http://example.com/js/anonymous.js',
42+
'http://example.com/js/utils.js',
43+
'http://example.com/js/app.js',
44+
];
45+
46+
const geckoStackTraceEval = `Error: Something went wrong
47+
@http://example.com/scripts/main.js:10:20
48+
@eval (eval at <anonymous>:1:1)
49+
functionName@http://example.com/scripts/utils.js:15:25
50+
@http://example.com/scripts/app.js:20:30
51+
ConstructorName@http://example.com/scripts/controller.js:25:35
52+
@http://example.com/scripts/main.js:30:40`;
53+
const geckoStackTraceEvalExpected = [
54+
'http://example.com/scripts/main.js',
55+
'http://example.com/scripts/utils.js',
56+
'http://example.com/scripts/app.js',
57+
'http://example.com/scripts/controller.js',
58+
];
59+
60+
const geckoStackTraceAnonymous = `TypeError: undefined is not a function
61+
@http://example.com/js/anonymous.js:10:5
62+
@<anonymous>:15:10
63+
functionName@http://example.com/js/utils.js:20:15
64+
ConstructorName@http://example.com/js/app.js:25:20
65+
@<anonymous>:30:25`;
66+
const geckoStackTraceAnonymousExpected = [
67+
'http://example.com/js/anonymous.js',
68+
'http://example.com/js/utils.js',
69+
'http://example.com/js/app.js'
70+
];
71+
72+
// Test 1: simple test w/ dupes
73+
const stack1 = `Error
74+
at http://localhost:8080/js/script1.js:10:15
75+
at http://localhost:8080/js/script2.js:20:25
76+
at http://localhost:8080/js/script1.js:30:35`;
77+
const expected1 = ['http://localhost:8080/js/script1.js', 'http://localhost:8080/js/script2.js'];
78+
79+
// Test 2: http and https
80+
const stack2 = `Error
81+
at https://example.com/js/app.js:50:10
82+
at http://localhost/js/util.js:100:50`;
83+
const expected2 = ['https://example.com/js/app.js', 'http://localhost/js/util.js'];
84+
85+
// Test 3: No full path URLs
86+
const stack3 = `Error
87+
at someFunction (file.js:10:15)
88+
at anotherFunction (file.js:20:25)`;
89+
const expected3 = [];
90+
91+
// Test 4: Only one URL, with port
92+
const stack4 = `Error
93+
at http://localhost:3000/js/main.js:10:15`;
94+
const expected4 = ['http://localhost:3000/js/main.js'];
95+
96+
// Test 5: Duplicate URLs
97+
const stack5 = `Error
98+
at http://localhost:3000/js/main.js:10:15
99+
at http://localhost:3000/js/main.js:20:25
100+
at http://localhost:3000/js/utils.js:30:35`;
101+
const expected5 = ['http://localhost:3000/js/main.js', 'http://localhost:3000/js/utils.js'];
102+
103+
// Test 6: Urls with query strings and fragments
104+
const stack6 = `Error
105+
at http://example.com:8080/path/js/main.js?name=testname:10:15
106+
at http://example.com:8080/path/js/main2.js#fragmentHere:20:15
107+
at http://example.com:8080/path/js/main3.js?name=testname#fragmentHere:30:15`;
108+
const expected6 = ['http://example.com:8080/path/js/main.js', 'http://example.com:8080/path/js/main2.js', 'http://example.com:8080/path/js/main3.js'];
109+
110+
// Test 7: Urls with different protocols and blobs
111+
const stack7 = `Error
112+
at file://testing.com:8000/js/testFile.js:1:2
113+
at blob:https://example.com:1000/src/hello.js:2:3`;
114+
const expected7 = ['file://testing.com:8000/js/testFile.js', 'https://example.com:1000/src/hello.js'];
115+
116+
const regexFilter = /([\w]+:\/\/[^\s/]+\/[^\s?:#]+)/g;
117+
describe('regexFilter', () => {
118+
let urls = new Set();
119+
let match;
120+
121+
beforeEach(() => {
122+
urls = new Set();
123+
match = null;
124+
});
125+
it('should test chrome eval stack traces', () => {
126+
while ((match = regexFilter.exec(chromeStackTraceEval)) !== null) {
127+
urls.add(match[0]);
128+
}
129+
const urlArr = [...urls];
130+
assert.deepEqual(urlArr, chromeStackTraceEvalExpected);
131+
});
132+
133+
it ('should test chrome anonymous stack traces', () => {
134+
while ((match = regexFilter.exec(chromeStackTraceAnonymous)) !== null) {
135+
urls.add(match[0]);
136+
}
137+
const urlArr = [...urls];
138+
assert.deepEqual(urlArr, chromeStackTraceAnonymousExpected);
139+
});
140+
141+
it ('should test gecko eval stack traces', () => {
142+
while ((match = regexFilter.exec(geckoStackTraceEval)) !== null) {
143+
urls.add(match[0]);
144+
}
145+
const urlArr = [...urls];
146+
assert.deepEqual(urlArr, geckoStackTraceEvalExpected);
147+
});
148+
149+
it ('should test gecko anonymous stack traces', () => {
150+
while ((match = regexFilter.exec(geckoStackTraceAnonymous)) !== null) {
151+
urls.add(match[0]);
152+
}
153+
const urlArr = [...urls];
154+
assert.deepEqual(urlArr, geckoStackTraceAnonymousExpected);
155+
});
156+
157+
it ('should test simple stack trace with dupes', () => {
158+
while ((match = regexFilter.exec(stack1)) !== null) {
159+
urls.add(match[0]);
160+
}
161+
const urlArr = [...urls];
162+
assert.deepEqual(urlArr, expected1);
163+
});
164+
165+
it ('should test http vs https stack traces', () => {
166+
while ((match = regexFilter.exec(stack2)) !== null) {
167+
urls.add(match[0]);
168+
}
169+
const urlArr = [...urls];
170+
assert.deepEqual(urlArr, expected2);
171+
});
172+
173+
it ('should test no full url path stack traces', () => {
174+
while ((match = regexFilter.exec(stack3)) !== null) {
175+
urls.add(match[0]);
176+
}
177+
const urlArr = [...urls];
178+
assert.deepEqual(urlArr, expected3);
179+
});
180+
181+
it ('should test url ports in stack traces', () => {
182+
while ((match = regexFilter.exec(stack4)) !== null) {
183+
urls.add(match[0]);
184+
}
185+
const urlArr = [...urls];
186+
assert.deepEqual(urlArr, expected4);
187+
});
188+
189+
it ('should test duplicate urls in stack traces', () => {
190+
while ((match = regexFilter.exec(stack5)) !== null) {
191+
urls.add(match[0]);
192+
}
193+
const urlArr = [...urls];
194+
assert.deepEqual(urlArr, expected5);
195+
});
196+
197+
it ('should test query strings/fragments in stack traces', () => {
198+
while ((match = regexFilter.exec(stack6)) !== null) {
199+
urls.add(match[0]);
200+
}
201+
const urlArr = [...urls];
202+
assert.deepEqual(urlArr, expected6);
203+
});
204+
205+
it ('should test blobs and diff protocols in stack traces', () => {
206+
while ((match = regexFilter.exec(stack7)) !== null) {
207+
urls.add(match[0]);
208+
}
209+
const urlArr = [...urls];
210+
assert.deepEqual(urlArr, expected7);
211+
});
212+
213+
it ('should test long stack traces', () => {
214+
const randomPaths = generateFilePaths(20, 20);
215+
const randomStack = generateRandomStackTrace(randomPaths, 10000);
216+
217+
while ((match = regexFilter.exec(randomStack)) !== null) {
218+
urls.add(match[0]);
219+
}
220+
const urlArr = [...urls];
221+
assert.deepEqual(urlArr.sort(), randomPaths.sort());
222+
});
223+
});

packages/web/test/utils.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,22 @@ export function initWithSyncPipeline(additionalOptions = {}): {
9999
export function deinit(force?: boolean): void {
100100
SplunkRum.deinit(force);
101101
}
102+
103+
export function generateFilePaths(domainCount: number, pathCount: number): string[] {
104+
const paths: string[] = [];
105+
for (let i = 0; i < domainCount; i++) {
106+
const domain = `http://domain${i}.com`;
107+
for (let j = 0; j < pathCount; j++) {
108+
paths.push(`${domain}/path${j}.js`);
109+
}
110+
}
111+
return paths;
112+
}
113+
114+
export function generateRandomStackTrace(paths: string[], stackCount: number): string {
115+
let stack = 'Error\n';
116+
for (let i = 0; i < stackCount; i++) {
117+
stack += `at ${paths[Math.floor(Math.random() * paths.length)]}:${Math.floor(Math.random() * 1000)}:${Math.floor(Math.random() * 1000)}\n`;
118+
}
119+
return stack;
120+
}

0 commit comments

Comments
 (0)