Skip to content
Open
Show file tree
Hide file tree
Changes from 11 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 @@ -6,6 +6,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 stack frame tracer
in the ErrorsInstrumentation and the stackTraceParser in the BrowserConfig (#1316)

## 1.18.2

- fix(user actions): don't attach user action context to http request when in halt mode (#1249)
Expand Down
2 changes: 1 addition & 1 deletion packages/web-sdk/src/config/getWebInstrumentations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import type { GetWebInstrumentationsOptions } from './types';
export function getWebInstrumentations(options: GetWebInstrumentationsOptions = {}): Instrumentation[] {
const instrumentations: Instrumentation[] = [
new UserActionInstrumentation(),
new ErrorsInstrumentation(),
new ErrorsInstrumentation(options.errorInstrumentationOptions),
new WebVitalsInstrumentation(),
new SessionInstrumentation(),
new ViewInstrumentation(),
Expand Down
6 changes: 4 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,8 @@ export function makeCoreConfig(browserConfig: BrowserConfig): Config {
...restProperties
}: BrowserConfig = browserConfig;

const stackTraceParser = browserConfig.parseStacktrace ?? parseStacktrace;

return {
...restProperties,

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

export interface BrowserConfig extends Partial<Omit<Config, 'app' | 'parseStacktrace'>>, Pick<Config, 'app'> {
import type { ErrorInstrumentationOptions } from '../instrumentations/errors/types';

export interface BrowserConfig extends Partial<Omit<Config, 'app'>>, Pick<Config, 'app'> {
url?: string;
apiKey?: string;
}
Expand All @@ -9,4 +11,5 @@ export interface GetWebInstrumentationsOptions {
captureConsole?: boolean;
captureConsoleDisabledLevels?: LogLevel[];
enablePerformanceInstrumentation?: boolean;
errorInstrumentationOptions?: ErrorInstrumentationOptions;
}
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, buildStackFrame } from './utils';

export {
buildStackFrame,
ConsoleInstrumentation,
ErrorsInstrumentation,
getDataFromSafariExtensions,
getStackFramesFromError,
parseStacktrace,
ViewInstrumentation,
WebVitalsInstrumentation,
SessionInstrumentation,
Expand Down
14 changes: 9 additions & 5 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 { buildStackFrame, getStackFramesFromError } from '../../utils/stackFrames';

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

export function getErrorDetails(evt: ErrorEvent): [string | undefined, string | undefined, ExceptionStackFrame[]] {
export function getErrorDetails(
evt: ErrorEvent,
options: ErrorInstrumentationOptions = {}
): [string | undefined, string | undefined, ExceptionStackFrame[]] {
let value: string | undefined;
let type: string | undefined;
let stackFrames: ExceptionStackFrame[] = [];
Expand All @@ -16,15 +20,15 @@ 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);
stackFrames = getStackFramesFromError(evt.error, options.stackframeParserOptions);
} 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);
stackFrames = getStackFramesFromError(evt, options.stackframeParserOptions);
} else if (isObject(evt) || (isEventRes = isEvent(evt))) {
type = isEventRes ? evt.constructor.name : undefined;
value = `${objectEventValue} ${Object.keys(evt)}`;
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 @@ -2,16 +2,21 @@ import { BaseInstrumentation, VERSION } from '@grafana/faro-core';

import { registerOnerror } from './registerOnerror';
import { registerOnunhandledrejection } from './registerOnunhandledrejection';
import type { ErrorInstrumentationOptions } from './types';

export class ErrorsInstrumentation extends BaseInstrumentation {
readonly name = '@grafana/faro-web-sdk:instrumentation-errors';
readonly version = VERSION;

constructor(private readonly options: ErrorInstrumentationOptions = {}) {
super();
}

initialize(): void {
this.logDebug('Initializing');

registerOnerror(this.api);

registerOnunhandledrejection(this.api);
registerOnunhandledrejection(this.api, this.options);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import type { API, ExceptionStackFrame } from '@grafana/faro-core';

import { primitiveUnhandledType, primitiveUnhandledValue } from './const';
import { getErrorDetails } from './getErrorDetails';
import type { ExtendedPromiseRejectionEvent } from './types';
import type { ErrorInstrumentationOptions, ExtendedPromiseRejectionEvent } from './types';

export function registerOnunhandledrejection(api: API): void {
export function registerOnunhandledrejection(api: API, options: ErrorInstrumentationOptions = {}): void {
window.addEventListener('unhandledrejection', (evt: ExtendedPromiseRejectionEvent) => {
let error = evt;

Expand All @@ -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, options);
}

if (value) {
Expand Down

This file was deleted.

6 changes: 6 additions & 0 deletions packages/web-sdk/src/instrumentations/errors/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { StackframeParserOptions } from '../../utils/stackFrames/types';

export interface ExtendedPromiseRejectionEvent extends PromiseRejectionEvent {
detail?: {
reason: PromiseRejectionEvent['reason'];
Expand All @@ -7,3 +9,7 @@ export interface ExtendedPromiseRejectionEvent extends PromiseRejectionEvent {
export type ErrorEvent = (Error | Event) & {
error?: Error;
};

export type ErrorInstrumentationOptions = {
stackframeParserOptions?: StackframeParserOptions;
};
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