Skip to content
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
URL structure (#1297)
- chore(user actions): Rename `userActionEventType` to `userActionTrigger` for improved clarity (#1298)

- chore(`@grafana/faro web-sdk`): Move the stackFrames from the errors instrumentation package to the utils package (#1316)
- improvement (`@grafana/faro-web-sdk`): Allow users to configure the stackTraceParser in the BrowserConfig (#1316)
- chore(`@grafana/faro-web-sdk`): remove the setStackTraceParser function (#1316)

## 1.18.2

- fix(user actions): don't attach user action context to http request when in halt mode (#1249)
Expand Down
1 change: 0 additions & 1 deletion docs/sources/developer/architecture/components/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ like `web-sdk` or by the user.

Methods:

- `changeStacktraceParser` - replaces the existing stacktrace parser with a new one
- `getStacktraceParser` - returns the current stacktrace parser
- `pushError` - sends an exception

Expand Down
19 changes: 3 additions & 16 deletions packages/core/src/api/exceptions/initialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@ import type { ApiMessageBusMessages } from '../types';
import { shouldIgnoreEvent } from '../utils';

import { defaultExceptionType } from './const';
import type { ErrorWithIndexProperties, ExceptionEvent, ExceptionsAPI, StacktraceParser } from './types';

let stacktraceParser: StacktraceParser | undefined;
import type { ErrorWithIndexProperties, ExceptionEvent, ExceptionsAPI } from './types';

export function initializeExceptionsAPI({
internalLogger,
Expand All @@ -49,15 +47,7 @@ export function initializeExceptionsAPI({

let lastPayload: Pick<ExceptionEvent, 'type' | 'value' | 'stacktrace' | 'context'> | null = null;

stacktraceParser = config.parseStacktrace ?? stacktraceParser;

const changeStacktraceParser: ExceptionsAPI['changeStacktraceParser'] = (newStacktraceParser) => {
internalLogger.debug('Changing stacktrace parser');

stacktraceParser = newStacktraceParser ?? stacktraceParser;
};

const getStacktraceParser: ExceptionsAPI['getStacktraceParser'] = () => stacktraceParser;
const getStacktraceParser: ExceptionsAPI['getStacktraceParser'] = () => config.parseStacktrace;

const { ignoreErrors = [], preserveOriginalError } = config;

Expand Down Expand Up @@ -91,7 +81,7 @@ export function initializeExceptionsAPI({
},
type: TransportItemType.EXCEPTION,
};

const stacktraceParser = getStacktraceParser();
stackFrames = stackFrames ?? (error.stack ? stacktraceParser?.(error).frames : undefined);

if (stackFrames?.length) {
Expand Down Expand Up @@ -128,10 +118,7 @@ export function initializeExceptionsAPI({
}
};

changeStacktraceParser(config.parseStacktrace);

return {
changeStacktraceParser,
getStacktraceParser,
pushError,
};
Expand Down
1 change: 0 additions & 1 deletion packages/core/src/api/exceptions/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ export type ErrorWithIndexProperties = Error & {
};

export interface ExceptionsAPI {
changeStacktraceParser: (stacktraceParser: StacktraceParser) => void;
getStacktraceParser: () => StacktraceParser | undefined;
pushError: (value: ErrorWithIndexProperties, options?: PushErrorOptions) => void;
}
1 change: 0 additions & 1 deletion packages/core/src/api/initialize.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ describe('initialize', () => {
expect(api).toHaveProperty('pushMeasurement');
expect(api).toHaveProperty('pushTraces');
expect(api).toHaveProperty('pushEvent');
expect(api).toHaveProperty('changeStacktraceParser');
expect(api).toHaveProperty('getOTEL');
expect(api).toHaveProperty('getPage');
expect(api).toHaveProperty('getSession');
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export interface Config<P = APIEvent> {
metas: MetaItem[];

/**
* Custom function used to parse stack traces
* The stacktrace parser to use for parsing stack traces.
*/
parseStacktrace: StacktraceParser;

Expand Down
7 changes: 5 additions & 2 deletions packages/web-sdk/src/config/makeCoreConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ import {
import type { Config, Instrumentation, MetaItem, MetaSession, Transport } from '@grafana/faro-core';

import { defaultEventDomain } from '../consts';
import { parseStacktrace } from '../instrumentations';
import { defaultSessionTrackingConfig } from '../instrumentations/session';
import { userActionDataAttribute } from '../instrumentations/userActions/const';
import { browserMeta } from '../metas';
import { k6Meta } from '../metas/k6';
import { createPageMeta } from '../metas/page';
import { FetchTransport } from '../transports';
import { parseStacktrace } from '../utils';

import { getWebInstrumentations } from './getWebInstrumentations';
import type { BrowserConfig } from './types';
Expand Down Expand Up @@ -65,6 +65,9 @@ export function makeCoreConfig(browserConfig: BrowserConfig): Config {
...restProperties
}: BrowserConfig = browserConfig;

// use the default stacktrace parser (parseStacktrace) if no custom stacktrace parser is provided
const stackTraceParser = browserConfig.parseStacktrace ?? parseStacktrace;

return {
...restProperties,

Expand All @@ -79,7 +82,7 @@ export function makeCoreConfig(browserConfig: BrowserConfig): Config {
isolate,
logArgsSerializer,
metas,
parseStacktrace,
parseStacktrace: stackTraceParser,
paused,
preventGlobalExposure,
transports,
Expand Down
2 changes: 1 addition & 1 deletion packages/web-sdk/src/config/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Config, LogLevel } from '@grafana/faro-core';

export interface BrowserConfig extends Partial<Omit<Config, 'app' | 'parseStacktrace'>>, Pick<Config, 'app'> {
export interface BrowserConfig extends Partial<Omit<Config, 'app'>>, Pick<Config, 'app'> {
url?: string;
apiKey?: string;
}
Expand Down
6 changes: 2 additions & 4 deletions packages/web-sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@ export { defaultEventDomain } from './consts';

export { initializeFaro } from './initialize';

export { getDataFromSafariExtensions, getStackFramesFromError, parseStacktrace, newStackTraceParser, buildStackFrame } from './utils';

export {
buildStackFrame,
ConsoleInstrumentation,
ErrorsInstrumentation,
getDataFromSafariExtensions,
getStackFramesFromError,
parseStacktrace,
ViewInstrumentation,
WebVitalsInstrumentation,
SessionInstrumentation,
Expand Down
24 changes: 16 additions & 8 deletions packages/web-sdk/src/instrumentations/errors/getErrorDetails.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { isDomError, isDomException, isError, isErrorEvent, isEvent, isObject, isString } from '@grafana/faro-core';
import type { ExceptionStackFrame, LogArgsSerializer } from '@grafana/faro-core';
import type { ExceptionStackFrame, LogArgsSerializer, StacktraceParser } from '@grafana/faro-core';

import { buildStackFrame, newStackTraceParser } from '../../utils/stackFrames';

import { domErrorType, domExceptionType, objectEventValue, unknownSymbolString } from './const';
import { getValueAndTypeFromMessage } from './getValueAndTypeFromMessage';
import { buildStackFrame, getStackFramesFromError } from './stackFrames';
import type { ErrorEvent } from './types';

export function getErrorDetails(evt: ErrorEvent): [string | undefined, string | undefined, ExceptionStackFrame[]] {
export function getErrorDetails(
evt: ErrorEvent,
stacktraceParser: StacktraceParser = newStackTraceParser()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use parseStacktrace from the config? under the hood it's the same parsing logic.
We can update parseStacktrace to take the optional options object.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option could be to receive the parser from api.getStacktraceParser
Looks like we currently have three functions doing teh same thing.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in actual usage the parser is received here by api.getStacktraceParser

): [string | undefined, string | undefined, ExceptionStackFrame[]] {
let value: string | undefined;
let type: string | undefined;
let stackFrames: ExceptionStackFrame[] = [];
Expand All @@ -16,15 +20,16 @@ export function getErrorDetails(evt: ErrorEvent): [string | undefined, string |
if (isErrorEvent(evt) && evt.error) {
value = evt.error.message;
type = evt.error.name;
stackFrames = getStackFramesFromError(evt.error);
const { frames } = stacktraceParser(evt.error);
stackFrames = frames;
} else if ((isDomErrorRes = isDomError(evt)) || isDomException(evt)) {
const { name, message } = evt;

type = name ?? (isDomErrorRes ? domErrorType : domExceptionType);
value = message ? `${type}: ${message}` : type;
} else if (isError(evt)) {
value = evt.message;
stackFrames = getStackFramesFromError(evt);
const { frames } = stacktraceParser(evt);
stackFrames = frames;
} else if (isObject(evt) || (isEventRes = isEvent(evt))) {
type = isEventRes ? evt.constructor.name : undefined;
value = `${objectEventValue} ${Object.keys(evt)}`;
Expand All @@ -39,7 +44,10 @@ export interface ErrorDetails {
stackFrames?: ExceptionStackFrame[];
}

export function getDetailsFromErrorArgs(args: [any?, ...any[]]): ErrorDetails {
export function getDetailsFromErrorArgs(
args: [any?, ...any[]],
stacktraceParser: StacktraceParser = newStackTraceParser()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, why not use parseStacktrace?

): ErrorDetails {
const [evt, source, lineno, colno, error] = args;

let value: string | undefined;
Expand All @@ -49,7 +57,7 @@ export function getDetailsFromErrorArgs(args: [any?, ...any[]]): ErrorDetails {
const initialStackFrame = buildStackFrame(source, unknownSymbolString, lineno, colno);

if (error || !eventIsString) {
[value, type, stackFrames] = getErrorDetails((error ?? evt) as Error | Event);
[value, type, stackFrames] = getErrorDetails((error ?? evt) as Error | Event, stacktraceParser);

if (stackFrames.length === 0) {
stackFrames = [initialStackFrame];
Expand Down
2 changes: 0 additions & 2 deletions packages/web-sdk/src/instrumentations/errors/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
export { ErrorsInstrumentation } from './instrumentation';

export { buildStackFrame, getDataFromSafariExtensions, getStackFramesFromError, parseStacktrace } from './stackFrames';

export type { ErrorEvent, ExtendedPromiseRejectionEvent } from './types';
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export function registerOnerror(api: API): void {

window.onerror = (...args) => {
try {
const { value, type, stackFrames } = getDetailsFromErrorArgs(args);
const { value, type, stackFrames } = getDetailsFromErrorArgs(args, api.getStacktraceParser());
const originalError = args[4];

if (value) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export function registerOnunhandledrejection(api: API): void {
value = `${primitiveUnhandledValue} ${String(error)}`;
type = primitiveUnhandledType;
} else {
[value, type, stackFrames] = getErrorDetails(error);
[value, type, stackFrames] = getErrorDetails(error, api.getStacktraceParser());
}

if (value) {
Expand Down

This file was deleted.

9 changes: 2 additions & 7 deletions packages/web-sdk/src/instrumentations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,8 @@ export { SessionInstrumentation } from './session';
export { ConsoleInstrumentation } from './console';
export type { ConsoleInstrumentationOptions } from './console';

export {
buildStackFrame,
ErrorsInstrumentation,
getDataFromSafariExtensions,
getStackFramesFromError,
parseStacktrace,
} from './errors';
export { ErrorsInstrumentation } from './errors';

export type { ErrorEvent, ExtendedPromiseRejectionEvent } from './errors';

export { ViewInstrumentation } from './view';
Expand Down
8 changes: 8 additions & 0 deletions packages/web-sdk/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,11 @@ export {
export { throttle } from './throttle';

export { getIgnoreUrls, getUrlFromResource } from './url';

export {
buildStackFrame,
getDataFromSafariExtensions,
getStackFramesFromError,
parseStacktrace,
newStackTraceParser,
} from './stackFrames';
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ describe('getStackFramesFromError', () => {
buildStackFrame('http://path/to/file.js', 'bar', 108, 168),
]);
});

it('should correctly skip lines if configured to', () => {
const result = getStackFramesFromError(CapturedExceptions.OPERA_25, { maximumLineLength: 36 });
expect(result).toEqual([buildStackFrame('http://path/to/file.js', undefined, 47, 22)]);
});
});

/* Taken from: https://github.com/stacktracejs/error-stack-parser/blob/master/spec/fixtures/captured-errors.js */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ import {
webkitLineRegex,
} from './const';
import { getDataFromSafariExtensions } from './getDataFromSafariExtensions';
import type { StackframeParserOptions } from './types';

export function getStackFramesFromError(error: ExtendedError): ExceptionStackFrame[] {
export function getStackFramesFromError(
error: ExtendedError,
options: StackframeParserOptions = {}
): ExceptionStackFrame[] {
let lines: string[] = [];

if (error.stacktrace) {
Expand All @@ -33,6 +37,13 @@ export function getStackFramesFromError(error: ExtendedError): ExceptionStackFra
let lineno: string | undefined;
let colno: string | undefined;

// skip attempting to parse stack frames over the limit, if it is set and above 0
if (options.maximumLineLength !== undefined && options.maximumLineLength > 0) {
if (line.length > options.maximumLineLength) {
return acc;
}
}

if ((parts = webkitLineRegex.exec(line))) {
func = parts[1];
filename = parts[2];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ export { getDataFromSafariExtensions } from './getDataFromSafariExtensions';

export { getStackFramesFromError } from './getStackFramesFromError';

export { parseStacktrace } from './parseStacktrace';
export { parseStacktrace, newStackTraceParser } from './parseStacktrace';
18 changes: 18 additions & 0 deletions packages/web-sdk/src/utils/stackFrames/parseStacktrace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { ExtendedError, Stacktrace, StacktraceParser } from '@grafana/faro-core';

import { getStackFramesFromError } from './getStackFramesFromError';
import type { StackframeParserOptions } from './types';

export function parseStacktrace(error: ExtendedError): Stacktrace {
return {
frames: getStackFramesFromError(error),
};
}

export function newStackTraceParser(options?: StackframeParserOptions): StacktraceParser {
return (error: ExtendedError) => {
return {
frames: getStackFramesFromError(error, options),
};
};
}
3 changes: 3 additions & 0 deletions packages/web-sdk/src/utils/stackFrames/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface StackframeParserOptions {
maximumLineLength?: number;
}
Loading