Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ref: Expose configurable stack parser #4902

Merged
merged 23 commits into from
Apr 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
97e6ff3
[v7] feat: Remove references to @sentry/apm (#4845)
AbhiPrasad Apr 7, 2022
57d37c2
[v7] feat(core): Delete API class (#4848)
AbhiPrasad Apr 7, 2022
424df27
[v7] feat: Delete deprecated `startSpan` and `child` methods (#4849)
AbhiPrasad Apr 7, 2022
5a7f145
feat(core): Remove whitelistUrls/blacklistUrls (#4850)
AbhiPrasad Apr 7, 2022
f855260
feat(gatsby): Remove Sentry from window (#4857)
AbhiPrasad Apr 7, 2022
f1e9da2
feat(hub): Remove getActiveDomain (#4858)
AbhiPrasad Apr 7, 2022
55b0570
feat(types): Remove deprecated user dsn field (#4864)
AbhiPrasad Apr 7, 2022
2fc13c6
feat(hub): Remove setTransaction scope method (#4865)
AbhiPrasad Apr 7, 2022
20c7773
[v7] feat: Drop support for Node 6 (#4851)
AbhiPrasad Apr 7, 2022
244eb0e
[v7] feat(tracing): Rename registerRequestInstrumentation -> instrume…
AbhiPrasad Apr 7, 2022
285ca26
fix(test): Increase MongoMemoryServer creation timeout (#4881)
Lms24 Apr 7, 2022
fb1b74f
[v7] feat(node): Remove deprecated `frameContextLines` (#4884)
timfish Apr 7, 2022
81b10fb
[v7] feat(browser): Remove top level eventbuilder exports (#4887)
timfish Apr 7, 2022
a0792da
Merge branch '7.x' of https://github.com/getsentry/sentry-javascript …
timfish Apr 8, 2022
79084f2
Mostly working
timfish Apr 8, 2022
52047b6
Lint
timfish Apr 8, 2022
14a10dc
make generic across all javascript
timfish Apr 8, 2022
dc765c7
Merge remote-tracking branch 'upstream/7.x' into v7/configurable-stac…
timfish Apr 8, 2022
0988600
Fix build
timfish Apr 8, 2022
c6e8a60
export nodeStackParser
timfish Apr 8, 2022
03b5d5c
Fix test
timfish Apr 8, 2022
7943990
Merge 'upstream/7.x' into v7/configurable-stack-parser
timfish Apr 12, 2022
623fcfd
Fix tests from bad merge
timfish Apr 12, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 9 additions & 3 deletions packages/browser/src/client.ts
@@ -1,6 +1,6 @@
import { BaseClient, getEnvelopeEndpointWithUrlEncodedAuth, initAPIDetails, Scope, SDK_VERSION } from '@sentry/core';
import { Event, EventHint, Options, Severity, Transport, TransportOptions } from '@sentry/types';
import { getGlobalObject, logger, supportsFetch } from '@sentry/utils';
import { getGlobalObject, logger, stackParserFromOptions, supportsFetch } from '@sentry/utils';

import { eventFromException, eventFromMessage } from './eventbuilder';
import { IS_DEBUG_BUILD } from './flags';
Expand Down Expand Up @@ -83,14 +83,20 @@ export class BrowserClient extends BaseClient<BrowserOptions> {
* @inheritDoc
*/
public eventFromException(exception: unknown, hint?: EventHint): PromiseLike<Event> {
return eventFromException(exception, hint, this._options.attachStacktrace);
return eventFromException(stackParserFromOptions(this._options), exception, hint, this._options.attachStacktrace);
}

/**
* @inheritDoc
*/
public eventFromMessage(message: string, level: Severity = Severity.Info, hint?: EventHint): PromiseLike<Event> {
return eventFromMessage(message, level, hint, this._options.attachStacktrace);
return eventFromMessage(
stackParserFromOptions(this._options),
message,
level,
hint,
this._options.attachStacktrace,
);
}

/**
Expand Down
65 changes: 30 additions & 35 deletions packages/browser/src/eventbuilder.ts
@@ -1,8 +1,7 @@
import { Event, EventHint, Exception, Severity, StackFrame } from '@sentry/types';
import { Event, EventHint, Exception, Severity, StackFrame, StackParser } from '@sentry/types';
import {
addExceptionMechanism,
addExceptionTypeValue,
createStackParser,
extractExceptionKeysForMessage,
isDOMError,
isDOMException,
Expand All @@ -14,22 +13,12 @@ import {
resolvedSyncPromise,
} from '@sentry/utils';

import {
chromeStackParser,
geckoStackParser,
opera10StackParser,
opera11StackParser,
winjsStackParser,
} from './stack-parsers';

/**
* This function creates an exception from an TraceKitStackTrace
* @param stacktrace TraceKitStackTrace that will be converted to an exception
* @hidden
*/
export function exceptionFromError(ex: Error): Exception {
export function exceptionFromError(stackParser: StackParser, ex: Error): Exception {
// Get the frames first since Opera can lose the stack if we touch anything else first
const frames = parseStackFrames(ex);
const frames = parseStackFrames(stackParser, ex);

const exception: Exception = {
type: ex && ex.name,
Expand All @@ -51,6 +40,7 @@ export function exceptionFromError(ex: Error): Exception {
* @hidden
*/
export function eventFromPlainObject(
stackParser: StackParser,
exception: Record<string, unknown>,
syntheticException?: Error,
isUnhandledRejection?: boolean,
Expand All @@ -72,7 +62,7 @@ export function eventFromPlainObject(
};

if (syntheticException) {
const frames = parseStackFrames(syntheticException);
const frames = parseStackFrames(stackParser, syntheticException);
if (frames.length) {
// event.exception.values[0] has been set above
(event.exception as { values: Exception[] }).values[0].stacktrace = { frames };
Expand All @@ -85,16 +75,19 @@ export function eventFromPlainObject(
/**
* @hidden
*/
export function eventFromError(ex: Error): Event {
export function eventFromError(stackParser: StackParser, ex: Error): Event {
return {
exception: {
values: [exceptionFromError(ex)],
values: [exceptionFromError(stackParser, ex)],
},
};
}

/** Parses stack frames from an error */
export function parseStackFrames(ex: Error & { framesToPop?: number; stacktrace?: string }): StackFrame[] {
export function parseStackFrames(
stackParser: StackParser,
ex: Error & { framesToPop?: number; stacktrace?: string },
): StackFrame[] {
// Access and store the stacktrace property before doing ANYTHING
// else to it because Opera is not very good at providing it
// reliably in other circumstances.
Expand All @@ -103,13 +96,7 @@ export function parseStackFrames(ex: Error & { framesToPop?: number; stacktrace?
const popSize = getPopSize(ex);

try {
return createStackParser(
opera10StackParser,
opera11StackParser,
chromeStackParser,
winjsStackParser,
geckoStackParser,
)(stacktrace, popSize);
return stackParser(stacktrace, popSize);
} catch (e) {
// no-empty
}
Expand Down Expand Up @@ -155,12 +142,13 @@ function extractMessage(ex: Error & { message: { error?: Error } }): string {
* @hidden
*/
export function eventFromException(
stackParser: StackParser,
exception: unknown,
hint?: EventHint,
attachStacktrace?: boolean,
): PromiseLike<Event> {
const syntheticException = (hint && hint.syntheticException) || undefined;
const event = eventFromUnknownInput(exception, syntheticException, attachStacktrace);
const event = eventFromUnknownInput(stackParser, exception, syntheticException, attachStacktrace);
addExceptionMechanism(event); // defaults to { type: 'generic', handled: true }
event.level = Severity.Error;
if (hint && hint.event_id) {
Expand All @@ -174,13 +162,14 @@ export function eventFromException(
* @hidden
*/
export function eventFromMessage(
stackParser: StackParser,
message: string,
level: Severity = Severity.Info,
hint?: EventHint,
attachStacktrace?: boolean,
): PromiseLike<Event> {
const syntheticException = (hint && hint.syntheticException) || undefined;
const event = eventFromString(message, syntheticException, attachStacktrace);
const event = eventFromString(stackParser, message, syntheticException, attachStacktrace);
event.level = level;
if (hint && hint.event_id) {
event.event_id = hint.event_id;
Expand All @@ -192,6 +181,7 @@ export function eventFromMessage(
* @hidden
*/
export function eventFromUnknownInput(
stackParser: StackParser,
exception: unknown,
syntheticException?: Error,
attachStacktrace?: boolean,
Expand All @@ -202,7 +192,7 @@ export function eventFromUnknownInput(
if (isErrorEvent(exception as ErrorEvent) && (exception as ErrorEvent).error) {
// If it is an ErrorEvent with `error` property, extract it to get actual Error
const errorEvent = exception as ErrorEvent;
return eventFromError(errorEvent.error as Error);
return eventFromError(stackParser, errorEvent.error as Error);
}

// If it is a `DOMError` (which is a legacy API, but still supported in some browsers) then we just extract the name
Expand All @@ -216,11 +206,11 @@ export function eventFromUnknownInput(
const domException = exception as DOMException;

if ('stack' in (exception as Error)) {
event = eventFromError(exception as Error);
event = eventFromError(stackParser, exception as Error);
} else {
const name = domException.name || (isDOMError(domException) ? 'DOMError' : 'DOMException');
const message = domException.message ? `${name}: ${domException.message}` : name;
event = eventFromString(message, syntheticException, attachStacktrace);
event = eventFromString(stackParser, message, syntheticException, attachStacktrace);
addExceptionTypeValue(event, message);
}
if ('code' in domException) {
Expand All @@ -231,14 +221,14 @@ export function eventFromUnknownInput(
}
if (isError(exception)) {
// we have a real Error object, do nothing
return eventFromError(exception);
return eventFromError(stackParser, exception);
}
if (isPlainObject(exception) || isEvent(exception)) {
// If it's a plain object or an instance of `Event` (the built-in JS kind, not this SDK's `Event` type), serialize
// it manually. This will allow us to group events based on top-level keys which is much better than creating a new
// group on any key/value change.
const objectException = exception as Record<string, unknown>;
event = eventFromPlainObject(objectException, syntheticException, isUnhandledRejection);
event = eventFromPlainObject(stackParser, objectException, syntheticException, isUnhandledRejection);
addExceptionMechanism(event, {
synthetic: true,
});
Expand All @@ -254,7 +244,7 @@ export function eventFromUnknownInput(
// - a plain Object
//
// So bail out and capture it as a simple message:
event = eventFromString(exception as string, syntheticException, attachStacktrace);
event = eventFromString(stackParser, exception as string, syntheticException, attachStacktrace);
addExceptionTypeValue(event, `${exception}`, undefined);
addExceptionMechanism(event, {
synthetic: true,
Expand All @@ -266,13 +256,18 @@ export function eventFromUnknownInput(
/**
* @hidden
*/
export function eventFromString(input: string, syntheticException?: Error, attachStacktrace?: boolean): Event {
export function eventFromString(
stackParser: StackParser,
input: string,
syntheticException?: Error,
attachStacktrace?: boolean,
): Event {
const event: Event = {
message: input,
};

if (attachStacktrace && syntheticException) {
const frames = parseStackFrames(syntheticException);
const frames = parseStackFrames(stackParser, syntheticException);
if (frames.length) {
event.exception = {
values: [{ value: input, stacktrace: { frames } }],
Expand Down
9 changes: 9 additions & 0 deletions packages/browser/src/exports.ts
Expand Up @@ -42,6 +42,15 @@ export {
} from '@sentry/core';

export { BrowserClient, BrowserOptions } from './client';

export {
defaultStackParsers,
chromeStackParser,
geckoStackParser,
opera10StackParser,
opera11StackParser,
winjsStackParser,
} from './stack-parsers';
export { injectReportDialog, ReportDialogOptions } from './helpers';
export { defaultIntegrations, forceLoad, init, lastEventId, onLoad, showReportDialog, flush, close, wrap } from './sdk';
export { SDK_NAME } from './version';
22 changes: 13 additions & 9 deletions packages/browser/src/integrations/globalhandlers.ts
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { getCurrentHub } from '@sentry/core';
import { Event, EventHint, Hub, Integration, Primitive, Severity } from '@sentry/types';
import { Event, EventHint, Hub, Integration, Primitive, Severity, StackParser } from '@sentry/types';
import {
addExceptionMechanism,
addInstrumentationHandler,
Expand All @@ -9,8 +9,10 @@ import {
isPrimitive,
isString,
logger,
stackParserFromOptions,
} from '@sentry/utils';

import { BrowserClient } from '../client';
import { eventFromUnknownInput } from '../eventbuilder';
import { IS_DEBUG_BUILD } from '../flags';
import { shouldIgnoreOnError } from '../helpers';
Expand Down Expand Up @@ -79,7 +81,7 @@ function _installGlobalOnErrorHandler(): void {
'error',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(data: { msg: any; url: any; line: any; column: any; error: any }) => {
const [hub, attachStacktrace] = getHubAndAttachStacktrace();
const [hub, stackParser, attachStacktrace] = getHubAndOptions();
if (!hub.getIntegration(GlobalHandlers)) {
return;
}
Expand All @@ -92,7 +94,7 @@ function _installGlobalOnErrorHandler(): void {
error === undefined && isString(msg)
? _eventFromIncompleteOnError(msg, url, line, column)
: _enhanceEventWithInitialFrame(
eventFromUnknownInput(error || msg, undefined, attachStacktrace, false),
eventFromUnknownInput(stackParser, error || msg, undefined, attachStacktrace, false),
url,
line,
column,
Expand All @@ -111,7 +113,7 @@ function _installGlobalOnUnhandledRejectionHandler(): void {
'unhandledrejection',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(e: any) => {
const [hub, attachStacktrace] = getHubAndAttachStacktrace();
const [hub, stackParser, attachStacktrace] = getHubAndOptions();
if (!hub.getIntegration(GlobalHandlers)) {
return;
}
Expand Down Expand Up @@ -142,7 +144,7 @@ function _installGlobalOnUnhandledRejectionHandler(): void {

const event = isPrimitive(error)
? _eventFromRejectionWithPrimitive(error)
: eventFromUnknownInput(error, undefined, attachStacktrace, true);
: eventFromUnknownInput(stackParser, error, undefined, attachStacktrace, true);

event.level = Severity.Error;

Expand Down Expand Up @@ -250,9 +252,11 @@ function addMechanismAndCapture(hub: Hub, error: EventHint['originalException'],
});
}

function getHubAndAttachStacktrace(): [Hub, boolean | undefined] {
function getHubAndOptions(): [Hub, StackParser, boolean | undefined] {
const hub = getCurrentHub();
const client = hub.getClient();
const attachStacktrace = client && client.getOptions().attachStacktrace;
return [hub, attachStacktrace];
const client = hub.getClient<BrowserClient>();
const options = client?.getOptions();
const parser = stackParserFromOptions(options);
const attachStacktrace = options?.attachStacktrace;
return [hub, parser, attachStacktrace];
}
32 changes: 24 additions & 8 deletions packages/browser/src/integrations/linkederrors.ts
@@ -1,7 +1,8 @@
import { addGlobalEventProcessor, getCurrentHub } from '@sentry/core';
import { Event, EventHint, Exception, ExtendedError, Integration } from '@sentry/types';
import { isInstanceOf } from '@sentry/utils';
import { Event, EventHint, Exception, ExtendedError, Integration, StackParser } from '@sentry/types';
import { isInstanceOf, stackParserFromOptions } from '@sentry/utils';

import { BrowserClient } from '../client';
import { exceptionFromError } from '../eventbuilder';

const DEFAULT_KEY = 'cause';
Expand Down Expand Up @@ -46,32 +47,47 @@ export class LinkedErrors implements Integration {
* @inheritDoc
*/
public setupOnce(): void {
const options = getCurrentHub().getClient<BrowserClient>()?.getOptions();
const parser = stackParserFromOptions(options);

addGlobalEventProcessor((event: Event, hint?: EventHint) => {
const self = getCurrentHub().getIntegration(LinkedErrors);
return self ? _handler(self._key, self._limit, event, hint) : event;
return self ? _handler(parser, self._key, self._limit, event, hint) : event;
});
}
}

/**
* @inheritDoc
*/
export function _handler(key: string, limit: number, event: Event, hint?: EventHint): Event | null {
export function _handler(
parser: StackParser,
key: string,
limit: number,
event: Event,
hint?: EventHint,
): Event | null {
if (!event.exception || !event.exception.values || !hint || !isInstanceOf(hint.originalException, Error)) {
return event;
}
const linkedErrors = _walkErrorTree(limit, hint.originalException as ExtendedError, key);
const linkedErrors = _walkErrorTree(parser, limit, hint.originalException as ExtendedError, key);
event.exception.values = [...linkedErrors, ...event.exception.values];
return event;
}

/**
* JSDOC
*/
export function _walkErrorTree(limit: number, error: ExtendedError, key: string, stack: Exception[] = []): Exception[] {
export function _walkErrorTree(
parser: StackParser,
limit: number,
error: ExtendedError,
key: string,
stack: Exception[] = [],
): Exception[] {
if (!isInstanceOf(error[key], Error) || stack.length + 1 >= limit) {
return stack;
}
const exception = exceptionFromError(error[key]);
return _walkErrorTree(limit, error[key], key, [exception, ...stack]);
const exception = exceptionFromError(parser, error[key]);
return _walkErrorTree(parser, limit, error[key], key, [exception, ...stack]);
}
4 changes: 4 additions & 0 deletions packages/browser/src/sdk.ts
Expand Up @@ -6,6 +6,7 @@ import { BrowserClient, BrowserOptions } from './client';
import { IS_DEBUG_BUILD } from './flags';
import { ReportDialogOptions, wrap as internalWrap } from './helpers';
import { Breadcrumbs, Dedupe, GlobalHandlers, LinkedErrors, TryCatch, UserAgent } from './integrations';
import { defaultStackParsers } from './stack-parsers';

export const defaultIntegrations = [
new CoreIntegrations.InboundFilters(),
Expand Down Expand Up @@ -92,6 +93,9 @@ export function init(options: BrowserOptions = {}): void {
if (options.sendClientReports === undefined) {
options.sendClientReports = true;
}
if (options.stackParser === undefined) {
options.stackParser = defaultStackParsers;
}

initAndBind(BrowserClient, options);

Expand Down