diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index cea9a4554ccb..96e2383c6ec0 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -44,9 +44,8 @@ export { export { NodeClient } from './client'; export { makeNodeTransport } from './transports'; -export { defaultIntegrations, init, lastEventId, flush, close, getSentryRelease } from './sdk'; +export { defaultIntegrations, init, defaultStackParser, lastEventId, flush, close, getSentryRelease } from './sdk'; export { deepReadDirSync } from './utils'; -export { defaultStackParser } from './stack-parser'; import { Integrations as CoreIntegrations } from '@sentry/core'; import { getMainCarrier } from '@sentry/hub'; diff --git a/packages/node/src/module.ts b/packages/node/src/module.ts new file mode 100644 index 000000000000..15682d16d121 --- /dev/null +++ b/packages/node/src/module.ts @@ -0,0 +1,36 @@ +import { basename, dirname } from '@sentry/utils'; + +/** Gets the module from a filename */ +export function getModule(filename: string | undefined): string | undefined { + if (!filename) { + return; + } + + // We could use optional chaining here but webpack does like that mixed with require + const base = `${ + (require && require.main && require.main.filename && dirname(require.main.filename)) || global.process.cwd() + }/`; + + // It's specifically a module + const file = basename(filename, '.js'); + + const path = dirname(filename); + let n = path.lastIndexOf('/node_modules/'); + if (n > -1) { + // /node_modules/ is 14 chars + return `${path.substr(n + 14).replace(/\//g, '.')}:${file}`; + } + // Let's see if it's a part of the main module + // To be a part of main module, it has to share the same base + n = `${path}/`.lastIndexOf(base, 0); + + if (n === 0) { + let moduleName = path.substr(base.length).replace(/\//g, '.'); + if (moduleName) { + moduleName += ':'; + } + moduleName += file; + return moduleName; + } + return file; +} diff --git a/packages/node/src/sdk.ts b/packages/node/src/sdk.ts index 34ce79ef480b..e83320103cb6 100644 --- a/packages/node/src/sdk.ts +++ b/packages/node/src/sdk.ts @@ -1,13 +1,14 @@ import { getCurrentHub, getIntegrationsToSetup, initAndBind, Integrations as CoreIntegrations } from '@sentry/core'; import { getMainCarrier, setHubOnCarrier } from '@sentry/hub'; -import { SessionStatus } from '@sentry/types'; -import { getGlobalObject, logger, stackParserFromStackParserOptions } from '@sentry/utils'; +import { SessionStatus, StackParser } from '@sentry/types'; +import { createStackParser, getGlobalObject, logger, stackParserFromStackParserOptions } from '@sentry/utils'; import * as domain from 'domain'; import { NodeClient } from './client'; import { IS_DEBUG_BUILD } from './flags'; import { Console, ContextLines, Http, LinkedErrors, OnUncaughtException, OnUnhandledRejection } from './integrations'; -import { defaultStackParser } from './stack-parser'; +import { getModule } from './module'; +import { nodeStackLineParser } from './stack-parser'; import { makeNodeTransport } from './transports'; import { NodeClientOptions, NodeOptions } from './types'; @@ -232,6 +233,9 @@ export function getSentryRelease(fallback?: string): string | undefined { ); } +/** Node.js stack parser */ +export const defaultStackParser: StackParser = createStackParser(nodeStackLineParser(getModule)); + /** * Enable automatic Session Tracking for the node process. */ diff --git a/packages/node/src/stack-parser.ts b/packages/node/src/stack-parser.ts index ea7cb1468ca1..7fd8a5ee3abd 100644 --- a/packages/node/src/stack-parser.ts +++ b/packages/node/src/stack-parser.ts @@ -1,119 +1,89 @@ import { StackLineParser, StackLineParserFn } from '@sentry/types'; -import { basename, createStackParser, dirname } from '@sentry/utils'; - -/** Gets the module */ -function getModule(filename: string | undefined): string | undefined { - if (!filename) { - return; - } - - // We could use optional chaining here but webpack does like that mixed with require - const base = `${ - (require && require.main && require.main.filename && dirname(require.main.filename)) || global.process.cwd() - }/`; - - // It's specifically a module - const file = basename(filename, '.js'); - - const path = dirname(filename); - let n = path.lastIndexOf('/node_modules/'); - if (n > -1) { - // /node_modules/ is 14 chars - return `${path.substr(n + 14).replace(/\//g, '.')}:${file}`; - } - // Let's see if it's a part of the main module - // To be a part of main module, it has to share the same base - n = `${path}/`.lastIndexOf(base, 0); - - if (n === 0) { - let moduleName = path.substr(base.length).replace(/\//g, '.'); - if (moduleName) { - moduleName += ':'; - } - moduleName += file; - return moduleName; - } - return file; -} const FILENAME_MATCH = /^\s*[-]{4,}$/; const FULL_MATCH = /at (?:async )?(?:(.+?)\s+\()?(?:(.+?):(\d+)(?::(\d+))?|([^)]+))\)?/; +type GetModuleFn = (filename: string | undefined) => string | undefined; + // eslint-disable-next-line complexity -const node: StackLineParserFn = (line: string) => { - if (line.match(FILENAME_MATCH)) { - return { - filename: line, - }; - } +function node(getModule?: GetModuleFn): StackLineParserFn { + // eslint-disable-next-line complexity + return (line: string) => { + if (line.match(FILENAME_MATCH)) { + return { + filename: line, + }; + } - const lineMatch = line.match(FULL_MATCH); - if (!lineMatch) { - return undefined; - } + const lineMatch = line.match(FULL_MATCH); + if (!lineMatch) { + return undefined; + } - let object: string | undefined; - let method: string | undefined; - let functionName: string | undefined; - let typeName: string | undefined; - let methodName: string | undefined; + let object: string | undefined; + let method: string | undefined; + let functionName: string | undefined; + let typeName: string | undefined; + let methodName: string | undefined; - if (lineMatch[1]) { - functionName = lineMatch[1]; + if (lineMatch[1]) { + functionName = lineMatch[1]; - let methodStart = functionName.lastIndexOf('.'); - if (functionName[methodStart - 1] === '.') { - // eslint-disable-next-line no-plusplus - methodStart--; - } + let methodStart = functionName.lastIndexOf('.'); + if (functionName[methodStart - 1] === '.') { + // eslint-disable-next-line no-plusplus + methodStart--; + } - if (methodStart > 0) { - object = functionName.substr(0, methodStart); - method = functionName.substr(methodStart + 1); - const objectEnd = object.indexOf('.Module'); - if (objectEnd > 0) { - functionName = functionName.substr(objectEnd + 1); - object = object.substr(0, objectEnd); + if (methodStart > 0) { + object = functionName.substr(0, methodStart); + method = functionName.substr(methodStart + 1); + const objectEnd = object.indexOf('.Module'); + if (objectEnd > 0) { + functionName = functionName.substr(objectEnd + 1); + object = object.substr(0, objectEnd); + } } + typeName = undefined; } - typeName = undefined; - } - if (method) { - typeName = object; - methodName = method; - } + if (method) { + typeName = object; + methodName = method; + } - if (method === '') { - methodName = undefined; - functionName = undefined; - } + if (method === '') { + methodName = undefined; + functionName = undefined; + } - if (functionName === undefined) { - methodName = methodName || ''; - functionName = typeName ? `${typeName}.${methodName}` : methodName; - } + if (functionName === undefined) { + methodName = methodName || ''; + functionName = typeName ? `${typeName}.${methodName}` : methodName; + } - const filename = lineMatch[2]?.startsWith('file://') ? lineMatch[2].substr(7) : lineMatch[2]; - const isNative = lineMatch[5] === 'native'; - const isInternal = - isNative || (filename && !filename.startsWith('/') && !filename.startsWith('.') && filename.indexOf(':\\') !== 1); + const filename = lineMatch[2]?.startsWith('file://') ? lineMatch[2].substr(7) : lineMatch[2]; + const isNative = lineMatch[5] === 'native'; + const isInternal = + isNative || (filename && !filename.startsWith('/') && !filename.startsWith('.') && filename.indexOf(':\\') !== 1); - // in_app is all that's not an internal Node function or a module within node_modules - // note that isNative appears to return true even for node core libraries - // see https://github.com/getsentry/raven-node/issues/176 - const in_app = !isInternal && filename !== undefined && !filename.includes('node_modules/'); + // in_app is all that's not an internal Node function or a module within node_modules + // note that isNative appears to return true even for node core libraries + // see https://github.com/getsentry/raven-node/issues/176 + const in_app = !isInternal && filename !== undefined && !filename.includes('node_modules/'); - return { - filename, - module: getModule(filename), - function: functionName, - lineno: parseInt(lineMatch[3], 10) || undefined, - colno: parseInt(lineMatch[4], 10) || undefined, - in_app, + return { + filename, + module: getModule?.(filename), + function: functionName, + lineno: parseInt(lineMatch[3], 10) || undefined, + colno: parseInt(lineMatch[4], 10) || undefined, + in_app, + }; }; -}; - -export const nodeStackLineParser: StackLineParser = [90, node]; +} -export const defaultStackParser = createStackParser(nodeStackLineParser); +/** Node.js stack line parser */ +export function nodeStackLineParser(getModule?: GetModuleFn): StackLineParser { + return [90, node(getModule)]; +} diff --git a/packages/node/test/context-lines.test.ts b/packages/node/test/context-lines.test.ts index 446429fdff33..2b0fef8206b8 100644 --- a/packages/node/test/context-lines.test.ts +++ b/packages/node/test/context-lines.test.ts @@ -3,7 +3,7 @@ import * as fs from 'fs'; import { parseStackFrames } from '../src/eventbuilder'; import { ContextLines, resetFileContentCache } from '../src/integrations/contextlines'; -import { defaultStackParser } from '../src/stack-parser'; +import { defaultStackParser } from '../src/sdk'; import { getError } from './helper/error'; describe('ContextLines', () => { diff --git a/packages/node/test/index.test.ts b/packages/node/test/index.test.ts index b12bd23fdd17..7b127637778c 100644 --- a/packages/node/test/index.test.ts +++ b/packages/node/test/index.test.ts @@ -16,7 +16,7 @@ import { Scope, } from '../src'; import { ContextLines, LinkedErrors } from '../src/integrations'; -import { defaultStackParser } from '../src/stack-parser'; +import { defaultStackParser } from '../src/sdk'; import { getDefaultNodeClientOptions } from './helper/node-client-options'; jest.mock('@sentry/core', () => { diff --git a/packages/node/test/integrations/linkederrors.test.ts b/packages/node/test/integrations/linkederrors.test.ts index c2bba0bd97d2..96510369f4ee 100644 --- a/packages/node/test/integrations/linkederrors.test.ts +++ b/packages/node/test/integrations/linkederrors.test.ts @@ -2,7 +2,7 @@ import { ExtendedError } from '@sentry/types'; import { Event, NodeClient } from '../../src'; import { LinkedErrors } from '../../src/integrations/linkederrors'; -import { defaultStackParser as stackParser } from '../../src/stack-parser'; +import { defaultStackParser as stackParser } from '../../src/sdk'; import { getDefaultNodeClientOptions } from '../helper/node-client-options'; let linkedErrors: any; diff --git a/packages/node/test/stacktrace.test.ts b/packages/node/test/stacktrace.test.ts index 227f87991c8a..e84c436364ed 100644 --- a/packages/node/test/stacktrace.test.ts +++ b/packages/node/test/stacktrace.test.ts @@ -11,7 +11,7 @@ */ import { parseStackFrames } from '../src/eventbuilder'; -import { defaultStackParser as stackParser } from '../src/stack-parser'; +import { defaultStackParser as stackParser } from '../src/sdk'; function testBasic() { return new Error('something went wrong');