Skip to content

Commit b2605be

Browse files
authored
feat(utils): Single implementation to fetch debug ids (#14199)
In the process of fixing getsentry/sentry-electron#1011 I was looking for a way to get a `Map<Filename, DebugId>`. I found that there was already some code duplication for parsing and caching of debug ids from the polyfilled globals. This PR moves the common code to `@sentry/utils` which will be useful for fixing the above.
1 parent 3750914 commit b2605be

File tree

5 files changed

+92
-162
lines changed

5 files changed

+92
-162
lines changed

packages/browser/src/profiling/utils.ts

+3-64
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,11 @@
11
/* eslint-disable max-lines */
22

33
import { DEFAULT_ENVIRONMENT, getClient, spanToJSON } from '@sentry/core';
4-
import type {
5-
DebugImage,
6-
Envelope,
7-
Event,
8-
EventEnvelope,
9-
Profile,
10-
Span,
11-
StackFrame,
12-
StackParser,
13-
ThreadCpuProfile,
14-
} from '@sentry/types';
4+
import type { DebugImage, Envelope, Event, EventEnvelope, Profile, Span, ThreadCpuProfile } from '@sentry/types';
155
import {
16-
GLOBAL_OBJ,
176
browserPerformanceTimeOrigin,
187
forEachEnvelopeItem,
8+
getDebugImagesForResources,
199
logger,
2010
timestampInSeconds,
2111
uuid4,
@@ -352,17 +342,10 @@ export function findProfiledTransactionsFromEnvelope(envelope: Envelope): Event[
352342
return events;
353343
}
354344

355-
const debugIdStackParserCache = new WeakMap<StackParser, Map<string, StackFrame[]>>();
356345
/**
357346
* Applies debug meta data to an event from a list of paths to resources (sourcemaps)
358347
*/
359348
export function applyDebugMetadata(resource_paths: ReadonlyArray<string>): DebugImage[] {
360-
const debugIdMap = GLOBAL_OBJ._sentryDebugIds;
361-
362-
if (!debugIdMap) {
363-
return [];
364-
}
365-
366349
const client = getClient();
367350
const options = client && client.getOptions();
368351
const stackParser = options && options.stackParser;
@@ -371,51 +354,7 @@ export function applyDebugMetadata(resource_paths: ReadonlyArray<string>): Debug
371354
return [];
372355
}
373356

374-
let debugIdStackFramesCache: Map<string, StackFrame[]>;
375-
const cachedDebugIdStackFrameCache = debugIdStackParserCache.get(stackParser);
376-
if (cachedDebugIdStackFrameCache) {
377-
debugIdStackFramesCache = cachedDebugIdStackFrameCache;
378-
} else {
379-
debugIdStackFramesCache = new Map<string, StackFrame[]>();
380-
debugIdStackParserCache.set(stackParser, debugIdStackFramesCache);
381-
}
382-
383-
// Build a map of filename -> debug_id
384-
const filenameDebugIdMap = Object.keys(debugIdMap).reduce<Record<string, string>>((acc, debugIdStackTrace) => {
385-
let parsedStack: StackFrame[];
386-
387-
const cachedParsedStack = debugIdStackFramesCache.get(debugIdStackTrace);
388-
if (cachedParsedStack) {
389-
parsedStack = cachedParsedStack;
390-
} else {
391-
parsedStack = stackParser(debugIdStackTrace);
392-
debugIdStackFramesCache.set(debugIdStackTrace, parsedStack);
393-
}
394-
395-
for (let i = parsedStack.length - 1; i >= 0; i--) {
396-
const stackFrame = parsedStack[i];
397-
const file = stackFrame && stackFrame.filename;
398-
399-
if (stackFrame && file) {
400-
acc[file] = debugIdMap[debugIdStackTrace] as string;
401-
break;
402-
}
403-
}
404-
return acc;
405-
}, {});
406-
407-
const images: DebugImage[] = [];
408-
for (const path of resource_paths) {
409-
if (path && filenameDebugIdMap[path]) {
410-
images.push({
411-
type: 'sourcemap',
412-
code_file: path,
413-
debug_id: filenameDebugIdMap[path] as string,
414-
});
415-
}
416-
}
417-
418-
return images;
357+
return getDebugImagesForResources(stackParser, resource_paths);
419358
}
420359

421360
/**

packages/core/src/utils/prepareEvent.ts

+9-42
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,16 @@ import type {
66
EventHint,
77
Scope as ScopeInterface,
88
ScopeContext,
9-
StackFrame,
109
StackParser,
1110
} from '@sentry/types';
12-
import { GLOBAL_OBJ, addExceptionMechanism, dateTimestampInSeconds, normalize, truncate, uuid4 } from '@sentry/utils';
11+
import {
12+
addExceptionMechanism,
13+
dateTimestampInSeconds,
14+
getFilenameToDebugIdMap,
15+
normalize,
16+
truncate,
17+
uuid4,
18+
} from '@sentry/utils';
1319

1420
import { DEFAULT_ENVIRONMENT } from '../constants';
1521
import { getGlobalScope } from '../currentScopes';
@@ -161,51 +167,12 @@ function applyClientOptions(event: Event, options: ClientOptions): void {
161167
}
162168
}
163169

164-
const debugIdStackParserCache = new WeakMap<StackParser, Map<string, StackFrame[]>>();
165-
166170
/**
167171
* Puts debug IDs into the stack frames of an error event.
168172
*/
169173
export function applyDebugIds(event: Event, stackParser: StackParser): void {
170-
const debugIdMap = GLOBAL_OBJ._sentryDebugIds;
171-
172-
if (!debugIdMap) {
173-
return;
174-
}
175-
176-
let debugIdStackFramesCache: Map<string, StackFrame[]>;
177-
const cachedDebugIdStackFrameCache = debugIdStackParserCache.get(stackParser);
178-
if (cachedDebugIdStackFrameCache) {
179-
debugIdStackFramesCache = cachedDebugIdStackFrameCache;
180-
} else {
181-
debugIdStackFramesCache = new Map<string, StackFrame[]>();
182-
debugIdStackParserCache.set(stackParser, debugIdStackFramesCache);
183-
}
184-
185174
// Build a map of filename -> debug_id
186-
const filenameDebugIdMap = Object.entries(debugIdMap).reduce<Record<string, string>>(
187-
(acc, [debugIdStackTrace, debugIdValue]) => {
188-
let parsedStack: StackFrame[];
189-
const cachedParsedStack = debugIdStackFramesCache.get(debugIdStackTrace);
190-
if (cachedParsedStack) {
191-
parsedStack = cachedParsedStack;
192-
} else {
193-
parsedStack = stackParser(debugIdStackTrace);
194-
debugIdStackFramesCache.set(debugIdStackTrace, parsedStack);
195-
}
196-
197-
for (let i = parsedStack.length - 1; i >= 0; i--) {
198-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
199-
const stackFrame = parsedStack[i]!;
200-
if (stackFrame.filename) {
201-
acc[stackFrame.filename] = debugIdValue;
202-
break;
203-
}
204-
}
205-
return acc;
206-
},
207-
{},
208-
);
175+
const filenameDebugIdMap = getFilenameToDebugIdMap(stackParser);
209176

210177
try {
211178
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion

packages/profiling-node/src/utils.ts

+9-56
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,16 @@ import type {
1414
ProfileChunkEnvelope,
1515
ProfileChunkItem,
1616
SdkInfo,
17-
StackFrame,
18-
StackParser,
1917
ThreadCpuProfile,
2018
} from '@sentry/types';
21-
import { GLOBAL_OBJ, createEnvelope, dsnToString, forEachEnvelopeItem, logger, uuid4 } from '@sentry/utils';
19+
import {
20+
createEnvelope,
21+
dsnToString,
22+
forEachEnvelopeItem,
23+
getDebugImagesForResources,
24+
logger,
25+
uuid4,
26+
} from '@sentry/utils';
2227

2328
import { env, versions } from 'process';
2429
import { isMainThread, threadId } from 'worker_threads';
@@ -415,69 +420,17 @@ export function makeProfileChunkEnvelope(
415420
]);
416421
}
417422

418-
const debugIdStackParserCache = new WeakMap<StackParser, Map<string, StackFrame[]>>();
419-
420423
/**
421424
* Cross reference profile collected resources with debug_ids and return a list of debug images.
422425
* @param {string[]} resource_paths
423426
* @returns {DebugImage[]}
424427
*/
425428
export function applyDebugMetadata(client: Client, resource_paths: ReadonlyArray<string>): DebugImage[] {
426-
const debugIdMap = GLOBAL_OBJ._sentryDebugIds;
427-
if (!debugIdMap) {
428-
return [];
429-
}
430-
431429
const options = client.getOptions();
432430

433431
if (!options || !options.stackParser) {
434432
return [];
435433
}
436434

437-
let debugIdStackFramesCache: Map<string, StackFrame[]>;
438-
const cachedDebugIdStackFrameCache = debugIdStackParserCache.get(options.stackParser);
439-
if (cachedDebugIdStackFrameCache) {
440-
debugIdStackFramesCache = cachedDebugIdStackFrameCache;
441-
} else {
442-
debugIdStackFramesCache = new Map<string, StackFrame[]>();
443-
debugIdStackParserCache.set(options.stackParser, debugIdStackFramesCache);
444-
}
445-
446-
// Build a map of filename -> debug_id.
447-
const filenameDebugIdMap = Object.keys(debugIdMap).reduce<Record<string, string>>((acc, debugIdStackTrace) => {
448-
let parsedStack: StackFrame[];
449-
450-
const cachedParsedStack = debugIdStackFramesCache.get(debugIdStackTrace);
451-
if (cachedParsedStack) {
452-
parsedStack = cachedParsedStack;
453-
} else {
454-
parsedStack = options.stackParser(debugIdStackTrace);
455-
debugIdStackFramesCache.set(debugIdStackTrace, parsedStack);
456-
}
457-
458-
for (let i = parsedStack.length - 1; i >= 0; i--) {
459-
const stackFrame = parsedStack[i];
460-
const file = stackFrame && stackFrame.filename;
461-
462-
if (stackFrame && file) {
463-
acc[file] = debugIdMap[debugIdStackTrace] as string;
464-
break;
465-
}
466-
}
467-
return acc;
468-
}, {});
469-
470-
const images: DebugImage[] = [];
471-
472-
for (const resource of resource_paths) {
473-
if (resource && filenameDebugIdMap[resource]) {
474-
images.push({
475-
type: 'sourcemap',
476-
code_file: resource,
477-
debug_id: filenameDebugIdMap[resource] as string,
478-
});
479-
}
480-
}
481-
482-
return images;
435+
return getDebugImagesForResources(options.stackParser, resource_paths);
483436
}

packages/utils/src/debug-ids.ts

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import type { DebugImage, StackFrame, StackParser } from '@sentry/types';
2+
import { GLOBAL_OBJ } from './worldwide';
3+
4+
const debugIdStackParserCache = new WeakMap<StackParser, Map<string, StackFrame[]>>();
5+
6+
/**
7+
* Returns a map of filenames to debug identifiers.
8+
*/
9+
export function getFilenameToDebugIdMap(stackParser: StackParser): Record<string, string> {
10+
const debugIdMap = GLOBAL_OBJ._sentryDebugIds;
11+
if (!debugIdMap) {
12+
return {};
13+
}
14+
15+
let debugIdStackFramesCache: Map<string, StackFrame[]>;
16+
const cachedDebugIdStackFrameCache = debugIdStackParserCache.get(stackParser);
17+
if (cachedDebugIdStackFrameCache) {
18+
debugIdStackFramesCache = cachedDebugIdStackFrameCache;
19+
} else {
20+
debugIdStackFramesCache = new Map<string, StackFrame[]>();
21+
debugIdStackParserCache.set(stackParser, debugIdStackFramesCache);
22+
}
23+
24+
// Build a map of filename -> debug_id.
25+
return Object.keys(debugIdMap).reduce<Record<string, string>>((acc, debugIdStackTrace) => {
26+
let parsedStack: StackFrame[];
27+
28+
const cachedParsedStack = debugIdStackFramesCache.get(debugIdStackTrace);
29+
if (cachedParsedStack) {
30+
parsedStack = cachedParsedStack;
31+
} else {
32+
parsedStack = stackParser(debugIdStackTrace);
33+
debugIdStackFramesCache.set(debugIdStackTrace, parsedStack);
34+
}
35+
36+
for (let i = parsedStack.length - 1; i >= 0; i--) {
37+
const stackFrame = parsedStack[i];
38+
const file = stackFrame && stackFrame.filename;
39+
40+
if (stackFrame && file) {
41+
acc[file] = debugIdMap[debugIdStackTrace] as string;
42+
break;
43+
}
44+
}
45+
return acc;
46+
}, {});
47+
}
48+
49+
/**
50+
* Returns a list of debug images for the given resources.
51+
*/
52+
export function getDebugImagesForResources(
53+
stackParser: StackParser,
54+
resource_paths: ReadonlyArray<string>,
55+
): DebugImage[] {
56+
const filenameDebugIdMap = getFilenameToDebugIdMap(stackParser);
57+
58+
const images: DebugImage[] = [];
59+
for (const path of resource_paths) {
60+
if (path && filenameDebugIdMap[path]) {
61+
images.push({
62+
type: 'sourcemap',
63+
code_file: path,
64+
debug_id: filenameDebugIdMap[path] as string,
65+
});
66+
}
67+
}
68+
69+
return images;
70+
}

packages/utils/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,4 @@ export * from './buildPolyfills';
4040
export * from './propagationContext';
4141
export * from './vercelWaitUntil';
4242
export * from './version';
43+
export * from './debug-ids';

0 commit comments

Comments
 (0)