Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
professorice committed Jan 17, 2025
2 parents a1e327b + 2ef1486 commit 8b0ee06
Show file tree
Hide file tree
Showing 20 changed files with 2,526 additions and 32 deletions.
3 changes: 2 additions & 1 deletion packages/sdk/browser/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"rootDir": ".",
"outDir": "dist",
"skipLibCheck": true,
"sourceMap": false,
"sourceMap": true,
"inlineSources": true,
"strict": true,
"stripInternal": true,
"target": "ES2017",
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/browser/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default defineConfig({
minify: true,
format: ['esm', 'cjs'],
splitting: false,
sourcemap: false,
sourcemap: true,
clean: true,
noExternal: ['@launchdarkly/js-sdk-common', '@launchdarkly/js-client-sdk-common'],
dts: true,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { LDClientLogging } from '../src/api';
import { LDClientTracking } from '../src/api/client/LDClientTracking';
import BrowserTelemetryImpl from '../src/BrowserTelemetryImpl';
import { ParsedOptions } from '../src/options';
Expand All @@ -22,6 +23,7 @@ const defaultOptions: ParsedOptions = {
},
evaluations: true,
flagChange: true,
filters: [],
},
stack: {
source: {
Expand Down Expand Up @@ -208,3 +210,315 @@ it('unregisters collectors on close', () => {

expect(mockCollector.unregister).toHaveBeenCalled();
});

it('logs event dropped message when maxPendingEvents is reached', () => {
const mockLogger = {
warn: jest.fn(),
};
const telemetry = new BrowserTelemetryImpl({
...defaultOptions,
maxPendingEvents: 2,
logger: mockLogger,
});
telemetry.captureError(new Error('Test error'));
expect(mockLogger.warn).not.toHaveBeenCalled();
telemetry.captureError(new Error('Test error 2'));
expect(mockLogger.warn).not.toHaveBeenCalled();

telemetry.captureError(new Error('Test error 3'));
expect(mockLogger.warn).toHaveBeenCalledWith(
'LaunchDarkly - Browser Telemetry: Maximum pending events reached. Old events will be dropped until the SDK' +
' client is registered.',
);

telemetry.captureError(new Error('Test error 4'));
expect(mockLogger.warn).toHaveBeenCalledTimes(1);
});

it('filters breadcrumbs using provided filters', () => {
const options: ParsedOptions = {
...defaultOptions,
breadcrumbs: {
...defaultOptions.breadcrumbs,
click: false,
evaluations: false,
flagChange: false,
http: { instrumentFetch: false, instrumentXhr: false },
keyboardInput: false,
filters: [
// Filter to remove breadcrumbs with id:2
(breadcrumb) => {
if (breadcrumb.type === 'custom' && breadcrumb.data?.id === 2) {
return undefined;
}
return breadcrumb;
},
// Filter to transform breadcrumbs with id:3
(breadcrumb) => {
if (breadcrumb.type === 'custom' && breadcrumb.data?.id === 3) {
return {
...breadcrumb,
data: { id: 'filtered-3' },
};
}
return breadcrumb;
},
],
},
};
const telemetry = new BrowserTelemetryImpl(options);

telemetry.addBreadcrumb({
type: 'custom',
data: { id: 1 },
timestamp: Date.now(),
class: 'custom',
level: 'info',
});

telemetry.addBreadcrumb({
type: 'custom',
data: { id: 2 },
timestamp: Date.now(),
class: 'custom',
level: 'info',
});

telemetry.addBreadcrumb({
type: 'custom',
data: { id: 3 },
timestamp: Date.now(),
class: 'custom',
level: 'info',
});

const error = new Error('Test error');
telemetry.captureError(error);
telemetry.register(mockClient);

expect(mockClient.track).toHaveBeenCalledWith(
'$ld:telemetry:error',
expect.objectContaining({
breadcrumbs: expect.arrayContaining([
expect.objectContaining({ data: { id: 1 } }),
expect.objectContaining({ data: { id: 'filtered-3' } }),
]),
}),
);

// Verify breadcrumb with id:2 was filtered out
expect(mockClient.track).toHaveBeenCalledWith(
'$ld:telemetry:error',
expect.objectContaining({
breadcrumbs: expect.not.arrayContaining([expect.objectContaining({ data: { id: 2 } })]),
}),
);
});

it('omits breadcrumb when a filter throws an exception', () => {
const breadSpy = jest.fn((breadcrumb) => breadcrumb);
const options: ParsedOptions = {
...defaultOptions,
breadcrumbs: {
...defaultOptions.breadcrumbs,
filters: [
() => {
throw new Error('Filter error');
},
// This filter should never run
breadSpy,
],
},
};
const telemetry = new BrowserTelemetryImpl(options);

telemetry.addBreadcrumb({
type: 'custom',
data: { id: 1 },
timestamp: Date.now(),
class: 'custom',
level: 'info',
});

const error = new Error('Test error');
telemetry.captureError(error);
telemetry.register(mockClient);

expect(mockClient.track).toHaveBeenCalledWith(
'$ld:telemetry:error',
expect.objectContaining({
breadcrumbs: [],
}),
);

expect(breadSpy).not.toHaveBeenCalled();
});

it('omits breadcrumbs when a filter is not a function', () => {
const options: ParsedOptions = {
...defaultOptions,
breadcrumbs: {
...defaultOptions.breadcrumbs,
// @ts-ignore
filters: ['potato'],
},
};
const telemetry = new BrowserTelemetryImpl(options);

telemetry.addBreadcrumb({
type: 'custom',
data: { id: 1 },
timestamp: Date.now(),
class: 'custom',
level: 'info',
});

const error = new Error('Test error');
telemetry.captureError(error);
telemetry.register(mockClient);

expect(mockClient.track).toHaveBeenCalledWith(
'$ld:telemetry:error',
expect.objectContaining({
breadcrumbs: [],
}),
);
});

it('warns when a breadcrumb filter is not a function', () => {
const mockLogger = {
warn: jest.fn(),
};
const options: ParsedOptions = {
...defaultOptions,
// @ts-ignore
breadcrumbs: { ...defaultOptions.breadcrumbs, filters: ['potato'] },
logger: mockLogger,
};

const telemetry = new BrowserTelemetryImpl(options);
telemetry.addBreadcrumb({
type: 'custom',
data: { id: 1 },
timestamp: Date.now(),
class: 'custom',
level: 'info',
});

expect(mockLogger.warn).toHaveBeenCalledWith(
'LaunchDarkly - Browser Telemetry: Error applying breadcrumb filters: TypeError: filter is not a function',
);
});

it('warns when a breadcrumb filter throws an exception', () => {
const mockLogger = {
warn: jest.fn(),
};
const options: ParsedOptions = {
...defaultOptions,
breadcrumbs: {
...defaultOptions.breadcrumbs,
filters: [
() => {
throw new Error('Filter error');
},
],
},
logger: mockLogger,
};

const telemetry = new BrowserTelemetryImpl(options);
telemetry.addBreadcrumb({
type: 'custom',
data: { id: 1 },
timestamp: Date.now(),
class: 'custom',
level: 'info',
});

expect(mockLogger.warn).toHaveBeenCalledWith(
'LaunchDarkly - Browser Telemetry: Error applying breadcrumb filters: Error: Filter error',
);
});

it('only logs breadcrumb filter error once', () => {
const mockLogger = {
warn: jest.fn(),
};
const options: ParsedOptions = {
...defaultOptions,
breadcrumbs: {
...defaultOptions.breadcrumbs,
filters: [
() => {
throw new Error('Filter error');
},
],
},
logger: mockLogger,
};

const telemetry = new BrowserTelemetryImpl(options);

// Add multiple breadcrumbs that will trigger the filter error
telemetry.addBreadcrumb({
type: 'custom',
data: { id: 1 },
timestamp: Date.now(),
class: 'custom',
level: 'info',
});

telemetry.addBreadcrumb({
type: 'custom',
data: { id: 2 },
timestamp: Date.now(),
class: 'custom',
level: 'info',
});

// Verify warning was only logged once
expect(mockLogger.warn).toHaveBeenCalledTimes(1);
expect(mockLogger.warn).toHaveBeenCalledWith(
'LaunchDarkly - Browser Telemetry: Error applying breadcrumb filters: Error: Filter error',
);
});

it('uses the client logger when no logger is provided', () => {
const options: ParsedOptions = {
...defaultOptions,
breadcrumbs: {
...defaultOptions.breadcrumbs,
filters: [
() => {
throw new Error('Filter error');
},
],
},
};

const telemetry = new BrowserTelemetryImpl(options);

const mockClientWithLogging: jest.Mocked<LDClientLogging & LDClientTracking> = {
logger: {
warn: jest.fn(),
},
track: jest.fn(),
};

telemetry.register(mockClientWithLogging);

// Add multiple breadcrumbs that will trigger the filter error
telemetry.addBreadcrumb({
type: 'custom',
data: { id: 1 },
timestamp: Date.now(),
class: 'custom',
level: 'info',
});

expect(mockClientWithLogging.logger.warn).toHaveBeenCalledTimes(1);
expect(mockClientWithLogging.logger.warn).toHaveBeenCalledWith(
'LaunchDarkly - Browser Telemetry: Error applying breadcrumb filters: Error: Filter error',
);
});
Loading

0 comments on commit 8b0ee06

Please sign in to comment.