Skip to content

Commit

Permalink
feat: Add before and after context lines. (#511)
Browse files Browse the repository at this point in the history
  • Loading branch information
kinyoklion authored Jul 15, 2024
1 parent c5522af commit 41b03e3
Show file tree
Hide file tree
Showing 7 changed files with 295 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ it('can set each option', () => {
instrumentXhr: true,
},
},
stack: {
source: {
beforeLines: 3,
afterLines: 3,
maxLineLength: 280,
},
},
collectors: [new ErrorCollector(), new ErrorCollector()],
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { processUrlToFileName, TrimOptions, trimSourceLine } from '../../src/stack/StackParser';
import {
getLines,
getSrcLines,
processUrlToFileName,
TrimOptions,
trimSourceLine,
} from '../../src/stack/StackParser';

it.each([
['http://www.launchdarkly.com', 'http://www.launchdarkly.com/', '(index)'],
Expand All @@ -21,3 +27,95 @@ it.each([
expect(trimSourceLine(options, source, column)).toEqual(expected);
},
);

describe('given source lines', () => {
const lines = ['1234567890', 'ABCDEFGHIJ', '0987654321', 'abcdefghij'];

it('can get a range which would underflow the lines', () => {
expect(getLines(-1, 2, lines, (input) => input)).toStrictEqual(['1234567890', 'ABCDEFGHIJ']);
});

it('can get a range which would overflow the lines', () => {
expect(getLines(2, 4, lines, (input) => input)).toStrictEqual(['0987654321', 'abcdefghij']);
});

it('can get a range which is satisfied by the lines', () => {
expect(getLines(0, 4, lines, (input) => input)).toStrictEqual([
'1234567890',
'ABCDEFGHIJ',
'0987654321',
'abcdefghij',
]);
});
});

describe('given an input stack frame', () => {
const inputFrame = {
context: ['1234567890', 'ABCDEFGHIJ', 'the src line', '0987654321', 'abcdefghij'],
column: 0,
};

it('can produce a full stack source in the output frame', () => {
expect(
getSrcLines(inputFrame, {
source: {
beforeLines: 2,
afterLines: 2,
maxLineLength: 280,
},
}),
).toMatchObject({
srcBefore: ['1234567890', 'ABCDEFGHIJ'],
srcLine: 'the src line',
srcAfter: ['0987654321', 'abcdefghij'],
});
});

it('can trim all the lines', () => {
expect(
getSrcLines(inputFrame, {
source: {
beforeLines: 2,
afterLines: 2,
maxLineLength: 1,
},
}),
).toMatchObject({
srcBefore: ['1', 'A'],
srcLine: 't',
srcAfter: ['0', 'a'],
});
});

it('can handle fewer input lines than the expected context', () => {
expect(
getSrcLines(inputFrame, {
source: {
beforeLines: 3,
afterLines: 3,
maxLineLength: 280,
},
}),
).toMatchObject({
srcBefore: ['1234567890', 'ABCDEFGHIJ'],
srcLine: 'the src line',
srcAfter: ['0987654321', 'abcdefghij'],
});
});

it('can handle more input lines than the expected context', () => {
expect(
getSrcLines(inputFrame, {
source: {
beforeLines: 1,
afterLines: 1,
maxLineLength: 280,
},
}),
).toMatchObject({
srcBefore: ['ABCDEFGHIJ'],
srcLine: 'the src line',
srcAfter: ['0987654321'],
});
});
});
19 changes: 16 additions & 3 deletions packages/telemetry/browser-telemetry/src/BrowserTelemetryImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
LDEvaluationDetail,
LDInspection,
} from 'launchdarkly-js-client-sdk';
import TraceKit from 'tracekit';

import { Breadcrumb, FeatureManagementBreadcrumb } from './api/Breadcrumb';
import { BrowserTelemetry } from './api/BrowserTelemetry';
Expand All @@ -16,7 +17,7 @@ import FetchCollector from './collectors/http/fetch';
import XhrCollector from './collectors/http/xhr';
import defaultUrlFilter from './filters/defaultUrlFilter';
import makeInspectors from './inspectors';
import { ParsedOptions } from './options';
import { ParsedOptions, ParsedStackOptions } from './options';
import randomUuidV4 from './randomUuidV4';
import parse from './stack/StackParser';

Expand All @@ -37,6 +38,16 @@ function safeValue(u: unknown): string | boolean | number | undefined {
}
}

function configureTraceKit(options: ParsedStackOptions) {
// Include before + after + source line.
// TraceKit only takes a total context size, so we have to over capture and then reduce the lines.
// So, for instance if before is 3 and after is 4 we need to capture 4 and 4 and then drop a line
// from the before context.
// The typing for this is a bool, but it accepts a number.
const beforeAfterMax = Math.max(options.source.afterLines, options.source.beforeLines);
(TraceKit as any).linesOfContext = beforeAfterMax * 2 + 1;
}

export default class BrowserTelemetryImpl implements BrowserTelemetry {
private maxPendingEvents: number;
private maxBreadcrumbs: number;
Expand All @@ -50,7 +61,9 @@ export default class BrowserTelemetryImpl implements BrowserTelemetry {
private collectors: Collector[] = [];
private sessionId: string = randomUuidV4();

constructor(options: ParsedOptions) {
constructor(private options: ParsedOptions) {
configureTraceKit(options.stack);

// Error collector is always required.
this.collectors.push(new ErrorCollector());
this.collectors.push(...options.collectors);
Expand Down Expand Up @@ -117,7 +130,7 @@ export default class BrowserTelemetryImpl implements BrowserTelemetry {
const data: ErrorData = {
type: exception.name || exception.constructor.name || 'generic',
message: exception.message,
stack: parse(exception),
stack: parse(exception, this.options.stack),
breadcrumbs: [...this.breadcrumbs],
sessionId: this.sessionId,
};
Expand Down
33 changes: 33 additions & 0 deletions packages/telemetry/browser-telemetry/src/api/Options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,34 @@ export interface HttpBreadCrumbOptions {
customUrlFilter?: UrlFilter;
}

export interface StackOptions {
/**
* Configuration that controls how source is captured.
*/
source?: {
/**
* The number of lines captured before the originating line.
*
* Defaults to 3.
*/
beforeLines?: number;
/**
* The number of lines captured after the originating line.
*
* Defaults to 3.
*/
afterLines?: number;

/**
* The maximum length of source line to include. Lines longer than this will be
* trimmed.
*
* Defaults to 280.
*/
maxLineLength?: number;
};
}

/**
* Options for configuring browser telemetry.
*/
Expand Down Expand Up @@ -103,4 +131,9 @@ export interface Options {
* Additional, or custom, collectors.
*/
collectors?: Collector[];

/**
* Configuration that controls the capture of the stack trace.
*/
stack?: StackOptions;
}
60 changes: 53 additions & 7 deletions packages/telemetry/browser-telemetry/src/options.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Collector } from './api/Collector';
import { HttpBreadCrumbOptions, Options, UrlFilter } from './api/Options';
import { HttpBreadCrumbOptions, Options, StackOptions, UrlFilter } from './api/Options';

export function defaultOptions(): ParsedOptions {
return {
Expand All @@ -13,6 +13,13 @@ export function defaultOptions(): ParsedOptions {
instrumentXhr: true,
},
},
stack: {
source: {
beforeLines: 3,
afterLines: 3,
maxLineLength: 280,
},
},
maxPendingEvents: 100,
collectors: [],
};
Expand All @@ -26,10 +33,10 @@ function itemOrDefault<T>(item: T | undefined, defaultValue: T): T {
}

function parseHttp(
option: HttpBreadCrumbOptions | false | undefined,
options: HttpBreadCrumbOptions | false | undefined,
defaults: ParsedHttpOptions,
): ParsedHttpOptions {
if (option === false) {
if (options === false) {
return {
instrumentFetch: false,
instrumentXhr: false,
Expand All @@ -38,19 +45,32 @@ function parseHttp(

// Make sure that the custom filter is at least a function.
const customUrlFilter =
option?.customUrlFilter && typeof option?.customUrlFilter === 'function'
? option.customUrlFilter
options?.customUrlFilter && typeof options?.customUrlFilter === 'function'
? options.customUrlFilter
: undefined;

// TODO: Logging for incorrect types.

return {
instrumentFetch: itemOrDefault(option?.instrumentFetch, defaults.instrumentFetch),
instrumentXhr: itemOrDefault(option?.instrumentFetch, defaults.instrumentXhr),
instrumentFetch: itemOrDefault(options?.instrumentFetch, defaults.instrumentFetch),
instrumentXhr: itemOrDefault(options?.instrumentFetch, defaults.instrumentXhr),
customUrlFilter,
};
}

function parseStack(
options: StackOptions | undefined,
defaults: ParsedStackOptions,
): ParsedStackOptions {
return {
source: {
beforeLines: itemOrDefault(options?.source?.beforeLines, defaults.source.beforeLines),
afterLines: itemOrDefault(options?.source?.afterLines, defaults.source.afterLines),
maxLineLength: itemOrDefault(options?.source?.maxLineLength, defaults.source.maxLineLength),
},
};
}

export default function parse(options: Options): ParsedOptions {
const defaults = defaultOptions();
return {
Expand All @@ -67,6 +87,7 @@ export default function parse(options: Options): ParsedOptions {
click: itemOrDefault(options.breadcrumbs?.click, defaults.breadcrumbs.click),
http: parseHttp(options.breadcrumbs?.http, defaults.breadcrumbs.http),
},
stack: parseStack(options.stack, defaults.stack),
maxPendingEvents: itemOrDefault(options.maxPendingEvents, defaults.maxPendingEvents),
collectors: [...itemOrDefault(options.collectors, defaults.collectors)],
};
Expand All @@ -89,6 +110,26 @@ export interface ParsedHttpOptions {
customUrlFilter?: UrlFilter;
}

export interface ParsedStackOptions {
source: {
/**
* The number of lines captured before the originating line.
*/
beforeLines: number;

/**
* The number of lines captured after the originating line.
*/
afterLines: number;

/**
* The maximum length of source line to include. Lines longer than this will be
* trimmed.
*/
maxLineLength: number;
};
}

export interface ParsedOptions {
/**
* The maximum number of pending events. Events may be captured before the LaunchDarkly
Expand Down Expand Up @@ -126,6 +167,11 @@ export interface ParsedOptions {
http: ParsedHttpOptions;
};

/**
* Settings which affect call stack capture.
*/
stack: ParsedStackOptions;

/**
* Additional, or custom, collectors.
*/
Expand Down
14 changes: 14 additions & 0 deletions packages/telemetry/browser-telemetry/src/stack/StackFrame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,24 @@ export default interface StackFrame {
*/
col?: number;

/**
* A number of source code lines before the line the frame originates from.
*
* The number of lines is configurable.
*/
srcBefore?: string[];

/**
* The line of source code the frame originates from.
*
* This line may be partial if the line is too large.
*/
srcLine?: string;

/**
* A number of source code lines after the line the frame originates from.
*
* The number of lines is configurable.
*/
srcAfter?: string[];
}
Loading

0 comments on commit 41b03e3

Please sign in to comment.