Skip to content

Commit 31be2be

Browse files
timfishAbhiPrasad
authored andcommitted
ref(node): Allow node stack parser to work in browser context (#5135)
1 parent 8fcf3ff commit 31be2be

File tree

8 files changed

+116
-107
lines changed

8 files changed

+116
-107
lines changed

packages/node/src/index.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,8 @@ export {
4444

4545
export { NodeClient } from './client';
4646
export { makeNodeTransport } from './transports';
47-
export { defaultIntegrations, init, lastEventId, flush, close, getSentryRelease } from './sdk';
47+
export { defaultIntegrations, init, defaultStackParser, lastEventId, flush, close, getSentryRelease } from './sdk';
4848
export { deepReadDirSync } from './utils';
49-
export { defaultStackParser } from './stack-parser';
5049

5150
import { Integrations as CoreIntegrations } from '@sentry/core';
5251
import { getMainCarrier } from '@sentry/hub';

packages/node/src/module.ts

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { basename, dirname } from '@sentry/utils';
2+
3+
/** Gets the module from a filename */
4+
export function getModule(filename: string | undefined): string | undefined {
5+
if (!filename) {
6+
return;
7+
}
8+
9+
// We could use optional chaining here but webpack does like that mixed with require
10+
const base = `${
11+
(require && require.main && require.main.filename && dirname(require.main.filename)) || global.process.cwd()
12+
}/`;
13+
14+
// It's specifically a module
15+
const file = basename(filename, '.js');
16+
17+
const path = dirname(filename);
18+
let n = path.lastIndexOf('/node_modules/');
19+
if (n > -1) {
20+
// /node_modules/ is 14 chars
21+
return `${path.substr(n + 14).replace(/\//g, '.')}:${file}`;
22+
}
23+
// Let's see if it's a part of the main module
24+
// To be a part of main module, it has to share the same base
25+
n = `${path}/`.lastIndexOf(base, 0);
26+
27+
if (n === 0) {
28+
let moduleName = path.substr(base.length).replace(/\//g, '.');
29+
if (moduleName) {
30+
moduleName += ':';
31+
}
32+
moduleName += file;
33+
return moduleName;
34+
}
35+
return file;
36+
}

packages/node/src/sdk.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { getCurrentHub, getIntegrationsToSetup, initAndBind, Integrations as CoreIntegrations } from '@sentry/core';
22
import { getMainCarrier, setHubOnCarrier } from '@sentry/hub';
3-
import { SessionStatus } from '@sentry/types';
4-
import { getGlobalObject, logger, stackParserFromStackParserOptions } from '@sentry/utils';
3+
import { SessionStatus, StackParser } from '@sentry/types';
4+
import { createStackParser, getGlobalObject, logger, stackParserFromStackParserOptions } from '@sentry/utils';
55
import * as domain from 'domain';
66

77
import { NodeClient } from './client';
88
import { IS_DEBUG_BUILD } from './flags';
99
import { Console, ContextLines, Http, LinkedErrors, OnUncaughtException, OnUnhandledRejection } from './integrations';
10-
import { defaultStackParser } from './stack-parser';
10+
import { getModule } from './module';
11+
import { nodeStackLineParser } from './stack-parser';
1112
import { makeNodeTransport } from './transports';
1213
import { NodeClientOptions, NodeOptions } from './types';
1314

@@ -232,6 +233,9 @@ export function getSentryRelease(fallback?: string): string | undefined {
232233
);
233234
}
234235

236+
/** Node.js stack parser */
237+
export const defaultStackParser: StackParser = createStackParser(nodeStackLineParser(getModule));
238+
235239
/**
236240
* Enable automatic Session Tracking for the node process.
237241
*/

packages/node/src/stack-parser.ts

+68-98
Original file line numberDiff line numberDiff line change
@@ -1,119 +1,89 @@
11
import { StackLineParser, StackLineParserFn } from '@sentry/types';
2-
import { basename, createStackParser, dirname } from '@sentry/utils';
3-
4-
/** Gets the module */
5-
function getModule(filename: string | undefined): string | undefined {
6-
if (!filename) {
7-
return;
8-
}
9-
10-
// We could use optional chaining here but webpack does like that mixed with require
11-
const base = `${
12-
(require && require.main && require.main.filename && dirname(require.main.filename)) || global.process.cwd()
13-
}/`;
14-
15-
// It's specifically a module
16-
const file = basename(filename, '.js');
17-
18-
const path = dirname(filename);
19-
let n = path.lastIndexOf('/node_modules/');
20-
if (n > -1) {
21-
// /node_modules/ is 14 chars
22-
return `${path.substr(n + 14).replace(/\//g, '.')}:${file}`;
23-
}
24-
// Let's see if it's a part of the main module
25-
// To be a part of main module, it has to share the same base
26-
n = `${path}/`.lastIndexOf(base, 0);
27-
28-
if (n === 0) {
29-
let moduleName = path.substr(base.length).replace(/\//g, '.');
30-
if (moduleName) {
31-
moduleName += ':';
32-
}
33-
moduleName += file;
34-
return moduleName;
35-
}
36-
return file;
37-
}
382

393
const FILENAME_MATCH = /^\s*[-]{4,}$/;
404
const FULL_MATCH = /at (?:async )?(?:(.+?)\s+\()?(?:(.+?):(\d+)(?::(\d+))?|([^)]+))\)?/;
415

6+
type GetModuleFn = (filename: string | undefined) => string | undefined;
7+
428
// eslint-disable-next-line complexity
43-
const node: StackLineParserFn = (line: string) => {
44-
if (line.match(FILENAME_MATCH)) {
45-
return {
46-
filename: line,
47-
};
48-
}
9+
function node(getModule?: GetModuleFn): StackLineParserFn {
10+
// eslint-disable-next-line complexity
11+
return (line: string) => {
12+
if (line.match(FILENAME_MATCH)) {
13+
return {
14+
filename: line,
15+
};
16+
}
4917

50-
const lineMatch = line.match(FULL_MATCH);
51-
if (!lineMatch) {
52-
return undefined;
53-
}
18+
const lineMatch = line.match(FULL_MATCH);
19+
if (!lineMatch) {
20+
return undefined;
21+
}
5422

55-
let object: string | undefined;
56-
let method: string | undefined;
57-
let functionName: string | undefined;
58-
let typeName: string | undefined;
59-
let methodName: string | undefined;
23+
let object: string | undefined;
24+
let method: string | undefined;
25+
let functionName: string | undefined;
26+
let typeName: string | undefined;
27+
let methodName: string | undefined;
6028

61-
if (lineMatch[1]) {
62-
functionName = lineMatch[1];
29+
if (lineMatch[1]) {
30+
functionName = lineMatch[1];
6331

64-
let methodStart = functionName.lastIndexOf('.');
65-
if (functionName[methodStart - 1] === '.') {
66-
// eslint-disable-next-line no-plusplus
67-
methodStart--;
68-
}
32+
let methodStart = functionName.lastIndexOf('.');
33+
if (functionName[methodStart - 1] === '.') {
34+
// eslint-disable-next-line no-plusplus
35+
methodStart--;
36+
}
6937

70-
if (methodStart > 0) {
71-
object = functionName.substr(0, methodStart);
72-
method = functionName.substr(methodStart + 1);
73-
const objectEnd = object.indexOf('.Module');
74-
if (objectEnd > 0) {
75-
functionName = functionName.substr(objectEnd + 1);
76-
object = object.substr(0, objectEnd);
38+
if (methodStart > 0) {
39+
object = functionName.substr(0, methodStart);
40+
method = functionName.substr(methodStart + 1);
41+
const objectEnd = object.indexOf('.Module');
42+
if (objectEnd > 0) {
43+
functionName = functionName.substr(objectEnd + 1);
44+
object = object.substr(0, objectEnd);
45+
}
7746
}
47+
typeName = undefined;
7848
}
79-
typeName = undefined;
80-
}
8149

82-
if (method) {
83-
typeName = object;
84-
methodName = method;
85-
}
50+
if (method) {
51+
typeName = object;
52+
methodName = method;
53+
}
8654

87-
if (method === '<anonymous>') {
88-
methodName = undefined;
89-
functionName = undefined;
90-
}
55+
if (method === '<anonymous>') {
56+
methodName = undefined;
57+
functionName = undefined;
58+
}
9159

92-
if (functionName === undefined) {
93-
methodName = methodName || '<anonymous>';
94-
functionName = typeName ? `${typeName}.${methodName}` : methodName;
95-
}
60+
if (functionName === undefined) {
61+
methodName = methodName || '<anonymous>';
62+
functionName = typeName ? `${typeName}.${methodName}` : methodName;
63+
}
9664

97-
const filename = lineMatch[2]?.startsWith('file://') ? lineMatch[2].substr(7) : lineMatch[2];
98-
const isNative = lineMatch[5] === 'native';
99-
const isInternal =
100-
isNative || (filename && !filename.startsWith('/') && !filename.startsWith('.') && filename.indexOf(':\\') !== 1);
65+
const filename = lineMatch[2]?.startsWith('file://') ? lineMatch[2].substr(7) : lineMatch[2];
66+
const isNative = lineMatch[5] === 'native';
67+
const isInternal =
68+
isNative || (filename && !filename.startsWith('/') && !filename.startsWith('.') && filename.indexOf(':\\') !== 1);
10169

102-
// in_app is all that's not an internal Node function or a module within node_modules
103-
// note that isNative appears to return true even for node core libraries
104-
// see https://github.com/getsentry/raven-node/issues/176
105-
const in_app = !isInternal && filename !== undefined && !filename.includes('node_modules/');
70+
// in_app is all that's not an internal Node function or a module within node_modules
71+
// note that isNative appears to return true even for node core libraries
72+
// see https://github.com/getsentry/raven-node/issues/176
73+
const in_app = !isInternal && filename !== undefined && !filename.includes('node_modules/');
10674

107-
return {
108-
filename,
109-
module: getModule(filename),
110-
function: functionName,
111-
lineno: parseInt(lineMatch[3], 10) || undefined,
112-
colno: parseInt(lineMatch[4], 10) || undefined,
113-
in_app,
75+
return {
76+
filename,
77+
module: getModule?.(filename),
78+
function: functionName,
79+
lineno: parseInt(lineMatch[3], 10) || undefined,
80+
colno: parseInt(lineMatch[4], 10) || undefined,
81+
in_app,
82+
};
11483
};
115-
};
116-
117-
export const nodeStackLineParser: StackLineParser = [90, node];
84+
}
11885

119-
export const defaultStackParser = createStackParser(nodeStackLineParser);
86+
/** Node.js stack line parser */
87+
export function nodeStackLineParser(getModule?: GetModuleFn): StackLineParser {
88+
return [90, node(getModule)];
89+
}

packages/node/test/context-lines.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as fs from 'fs';
33

44
import { parseStackFrames } from '../src/eventbuilder';
55
import { ContextLines, resetFileContentCache } from '../src/integrations/contextlines';
6-
import { defaultStackParser } from '../src/stack-parser';
6+
import { defaultStackParser } from '../src/sdk';
77
import { getError } from './helper/error';
88

99
describe('ContextLines', () => {

packages/node/test/index.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
Scope,
1717
} from '../src';
1818
import { ContextLines, LinkedErrors } from '../src/integrations';
19-
import { defaultStackParser } from '../src/stack-parser';
19+
import { defaultStackParser } from '../src/sdk';
2020
import { getDefaultNodeClientOptions } from './helper/node-client-options';
2121

2222
jest.mock('@sentry/core', () => {

packages/node/test/integrations/linkederrors.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { ExtendedError } from '@sentry/types';
22

33
import { Event, NodeClient } from '../../src';
44
import { LinkedErrors } from '../../src/integrations/linkederrors';
5-
import { defaultStackParser as stackParser } from '../../src/stack-parser';
5+
import { defaultStackParser as stackParser } from '../../src/sdk';
66
import { getDefaultNodeClientOptions } from '../helper/node-client-options';
77

88
let linkedErrors: any;

packages/node/test/stacktrace.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
*/
1212

1313
import { parseStackFrames } from '../src/eventbuilder';
14-
import { defaultStackParser as stackParser } from '../src/stack-parser';
14+
import { defaultStackParser as stackParser } from '../src/sdk';
1515

1616
function testBasic() {
1717
return new Error('something went wrong');

0 commit comments

Comments
 (0)