From 6716031dc8c6136026887f3670c89a5903c9149a Mon Sep 17 00:00:00 2001 From: Igor Sazanovets Date: Mon, 22 Jul 2024 16:49:48 +0300 Subject: [PATCH 1/2] feat: add max breadcrumbs limitation --- .../browser/src/lib/consts/max-breadcrumbs.ts | 1 + .../models/browser-sentry-client-options.ts | 1 + .../browser-micro-sentry-client.spec.ts | 75 ++++++++++++++++++- .../services/browser-micro-sentry-client.ts | 23 +++++- 4 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 libs/browser/src/lib/consts/max-breadcrumbs.ts diff --git a/libs/browser/src/lib/consts/max-breadcrumbs.ts b/libs/browser/src/lib/consts/max-breadcrumbs.ts new file mode 100644 index 0000000..a57c4e1 --- /dev/null +++ b/libs/browser/src/lib/consts/max-breadcrumbs.ts @@ -0,0 +1 @@ +export const MAX_BREADCRUMBS = 100; diff --git a/libs/browser/src/lib/models/browser-sentry-client-options.ts b/libs/browser/src/lib/models/browser-sentry-client-options.ts index 6f3b839..44457a0 100644 --- a/libs/browser/src/lib/models/browser-sentry-client-options.ts +++ b/libs/browser/src/lib/models/browser-sentry-client-options.ts @@ -12,4 +12,5 @@ export interface BrowserSentryClientOptions extends SentryClientOptions { ignoreErrors?: Array; blacklistUrls?: Array; release?: string; + maxBreadcrumbs?: number; } diff --git a/libs/browser/src/lib/services/browser-micro-sentry-client.spec.ts b/libs/browser/src/lib/services/browser-micro-sentry-client.spec.ts index 0c2b840..cbcdf6d 100644 --- a/libs/browser/src/lib/services/browser-micro-sentry-client.spec.ts +++ b/libs/browser/src/lib/services/browser-micro-sentry-client.spec.ts @@ -1,13 +1,16 @@ import { BrowserMicroSentryClient } from './browser-micro-sentry-client'; import { Severity } from '@micro-sentry/core'; +import { MAX_BREADCRUMBS } from '../consts/max-breadcrumbs'; describe('BrowserMicroSentryClient', () => { let client: BrowserMicroSentryClient; + const maxBreadcrumbs = 10; beforeAll(() => { client = new BrowserMicroSentryClient({ dsn: 'http://secret@exampl.dsn/2', release: '1.0.0', + maxBreadcrumbs, }); }); @@ -205,6 +208,76 @@ describe('BrowserMicroSentryClient', () => { }); }); + it('should limit breadcrumbs amount - with custom limit', () => { + for (let i = 0; i < maxBreadcrumbs + 2; i++) { + client.addBreadcrumb({ + event_id: `id${i}`, + type: 'console', + level: Severity.critical, + }); + } + + expect(client.state.breadcrumbs?.length).toBe(maxBreadcrumbs); + }); + + it('should limit breadcrumbs amount - with default limit', () => { + const anotherClient = new BrowserMicroSentryClient({}); + + for (let i = 0; i < MAX_BREADCRUMBS + 2; i++) { + anotherClient.addBreadcrumb({ + event_id: `id${i}`, + type: 'console', + level: Severity.critical, + }); + } + + expect(anotherClient.state.breadcrumbs?.length).toBe(MAX_BREADCRUMBS); + }); + + it('should save only last breadcrumbs', () => { + for (let i = 0; i < maxBreadcrumbs + 2; i++) { + client.addBreadcrumb({ + event_id: `id${i}`, + type: 'console', + level: Severity.critical, + }); + } + + expect( + (client.state.breadcrumbs ?? []) + .map((breadcrumb) => breadcrumb.event_id) + .join(',') + ).toEqual('id2,id3,id4,id5,id6,id7,id8,id9,id10,id11'); + }); + + it('should not add breadcrumbs at all if maxBreadcrumbs is set to 0', () => { + const anotherClient = new BrowserMicroSentryClient({ maxBreadcrumbs: 0 }); + + anotherClient.addBreadcrumb({ + event_id: 'id', + type: 'console', + level: Severity.critical, + }); + + expect(anotherClient.state.breadcrumbs?.length).toBe(0); + }); + + it('should ignore maxBreadcrumbs option if maxBreadcrumbs is a negative number', () => { + const anotherClient = new BrowserMicroSentryClient({ + maxBreadcrumbs: -100, + }); + + for (let i = 0; i < MAX_BREADCRUMBS + 2; i++) { + anotherClient.addBreadcrumb({ + event_id: `id${i}`, + type: 'console', + level: Severity.critical, + }); + } + + expect(anotherClient.state.breadcrumbs?.length).toBe(MAX_BREADCRUMBS); + }); + it('should skip breadcrumbs if beforeBreadcrumb returns null', () => { client = new BrowserMicroSentryClient({ dsn: 'http://secret@exampl.dsn/2', @@ -237,7 +310,7 @@ describe('BrowserMicroSentryClient', () => { ]); }); - afterAll(() => { + afterEach(() => { client.clearState(); }); }); diff --git a/libs/browser/src/lib/services/browser-micro-sentry-client.ts b/libs/browser/src/lib/services/browser-micro-sentry-client.ts index f4c265f..4781609 100644 --- a/libs/browser/src/lib/services/browser-micro-sentry-client.ts +++ b/libs/browser/src/lib/services/browser-micro-sentry-client.ts @@ -12,12 +12,14 @@ import { State } from '../models/state'; import { MicroSentryPlugin } from '../models/plugin'; import { BrowserSentryClientOptions } from '../models/browser-sentry-client-options'; import { isMatchingPattern } from '../utils/is-matching-pattern'; +import { MAX_BREADCRUMBS } from '../consts/max-breadcrumbs'; function getWindow(): Window { return window; } export class BrowserMicroSentryClient extends MicroSentryClient { + private readonly breadcrumbsKeyName = 'breadcrumbs'; private destroyed = false; private readonly plugins: MicroSentryPlugin[]; private readonly beforeSend: NonNullable< @@ -33,6 +35,7 @@ export class BrowserMicroSentryClient extends MicroSentryClient { BrowserSentryClientOptions['ignoreErrors'] >; private readonly release?: string; + private readonly maxBreadcrumbs: number; constructor( private options: BrowserSentryClientOptions, @@ -47,7 +50,8 @@ export class BrowserMicroSentryClient extends MicroSentryClient { blacklistUrls = [], ignoreErrors = [], release = undefined, - } = this.options || {}; + maxBreadcrumbs = MAX_BREADCRUMBS, + } = this.options || {} || []; this.plugins = plugins.map((Plugin) => new Plugin(this)); this.beforeSend = beforeSend; @@ -55,6 +59,8 @@ export class BrowserMicroSentryClient extends MicroSentryClient { this.blacklistUrls = blacklistUrls; this.ignoreErrors = ignoreErrors; this.release = release; + this.maxBreadcrumbs = + maxBreadcrumbs >= 0 ? maxBreadcrumbs : MAX_BREADCRUMBS; } protected _state: State = {}; @@ -126,13 +132,22 @@ export class BrowserMicroSentryClient extends MicroSentryClient { } this.extendState({ - breadcrumbs: [ + [this.breadcrumbsKeyName]: [ { timestamp: Date.now() / 1_000, ...result, }, ], }); + + const breadcrumbs = this.getKeyState(this.breadcrumbsKeyName); + + if (!!breadcrumbs && (breadcrumbs.length ?? 0) > this.maxBreadcrumbs) { + this.setKeyState( + this.breadcrumbsKeyName, + this.maxBreadcrumbs > 0 ? breadcrumbs.slice(-this.maxBreadcrumbs) : [] + ); + } } setBreadcrumbs(breadcrumbs: Breadcrumb[] | undefined) { @@ -307,4 +322,8 @@ export class BrowserMicroSentryClient extends MicroSentryClient { private setKeyState(key: T, value: State[T]) { this._state[key] = value; } + + private getKeyState(key: T): State[T] { + return this._state[key]; + } } From 6de10d2b33551ab9b637f34dd784f70963026995 Mon Sep 17 00:00:00 2001 From: Igor Sazanovets Date: Tue, 23 Jul 2024 14:31:41 +0300 Subject: [PATCH 2/2] feat: add breadcrumbs limitation to setBreadcrumbs method --- .../browser-micro-sentry-client.spec.ts | 103 +++++++++++------- .../services/browser-micro-sentry-client.ts | 22 ++-- 2 files changed, 78 insertions(+), 47 deletions(-) diff --git a/libs/browser/src/lib/services/browser-micro-sentry-client.spec.ts b/libs/browser/src/lib/services/browser-micro-sentry-client.spec.ts index cbcdf6d..a686081 100644 --- a/libs/browser/src/lib/services/browser-micro-sentry-client.spec.ts +++ b/libs/browser/src/lib/services/browser-micro-sentry-client.spec.ts @@ -5,6 +5,12 @@ import { MAX_BREADCRUMBS } from '../consts/max-breadcrumbs'; describe('BrowserMicroSentryClient', () => { let client: BrowserMicroSentryClient; const maxBreadcrumbs = 10; + const getBreadcrumbs = (amount: number) => + [...Array(amount).keys()].map((index) => ({ + event_id: `id${index}`, + type: 'console', + level: Severity.critical, + })); beforeAll(() => { client = new BrowserMicroSentryClient({ @@ -208,40 +214,41 @@ describe('BrowserMicroSentryClient', () => { }); }); - it('should limit breadcrumbs amount - with custom limit', () => { - for (let i = 0; i < maxBreadcrumbs + 2; i++) { - client.addBreadcrumb({ - event_id: `id${i}`, - type: 'console', - level: Severity.critical, - }); - } + it('should limit breadcrumbs amount - with custom limit; incremental addition', () => { + getBreadcrumbs(maxBreadcrumbs + 2).forEach((bc) => + client.addBreadcrumb(bc) + ); + + expect(client.state.breadcrumbs?.length).toBe(maxBreadcrumbs); + }); + + it('should limit breadcrumbs amount - with custom limit; all at once', () => { + client.setBreadcrumbs(getBreadcrumbs(maxBreadcrumbs + 2)); expect(client.state.breadcrumbs?.length).toBe(maxBreadcrumbs); }); - it('should limit breadcrumbs amount - with default limit', () => { + it('should limit breadcrumbs amount - with default limit; incremental addition', () => { const anotherClient = new BrowserMicroSentryClient({}); - for (let i = 0; i < MAX_BREADCRUMBS + 2; i++) { - anotherClient.addBreadcrumb({ - event_id: `id${i}`, - type: 'console', - level: Severity.critical, - }); - } + getBreadcrumbs(MAX_BREADCRUMBS + 2).forEach((bc) => + anotherClient.addBreadcrumb(bc) + ); expect(anotherClient.state.breadcrumbs?.length).toBe(MAX_BREADCRUMBS); }); - it('should save only last breadcrumbs', () => { - for (let i = 0; i < maxBreadcrumbs + 2; i++) { - client.addBreadcrumb({ - event_id: `id${i}`, - type: 'console', - level: Severity.critical, - }); - } + it('should limit breadcrumbs amount - with default limit; all at once', () => { + const anotherClient = new BrowserMicroSentryClient({}); + anotherClient.setBreadcrumbs(getBreadcrumbs(MAX_BREADCRUMBS + 2)); + + expect(anotherClient.state.breadcrumbs?.length).toBe(MAX_BREADCRUMBS); + }); + + it('should save only last breadcrumbs; incremental addition', () => { + getBreadcrumbs(maxBreadcrumbs + 2).forEach((bc) => + client.addBreadcrumb(bc) + ); expect( (client.state.breadcrumbs ?? []) @@ -250,30 +257,50 @@ describe('BrowserMicroSentryClient', () => { ).toEqual('id2,id3,id4,id5,id6,id7,id8,id9,id10,id11'); }); - it('should not add breadcrumbs at all if maxBreadcrumbs is set to 0', () => { + it('should save only last breadcrumbs; all at once', () => { + client.setBreadcrumbs(getBreadcrumbs(maxBreadcrumbs + 2)); + + expect( + (client.state.breadcrumbs ?? []) + .map((breadcrumb) => breadcrumb.event_id) + .join(',') + ).toEqual('id2,id3,id4,id5,id6,id7,id8,id9,id10,id11'); + }); + + it('should not add breadcrumbs at all if maxBreadcrumbs is set to 0; incremental addition', () => { const anotherClient = new BrowserMicroSentryClient({ maxBreadcrumbs: 0 }); - anotherClient.addBreadcrumb({ - event_id: 'id', - type: 'console', - level: Severity.critical, - }); + getBreadcrumbs(1).forEach((bc) => anotherClient.addBreadcrumb(bc)); + + expect(anotherClient.state.breadcrumbs?.length).toBe(0); + }); + + it('should not add breadcrumbs at all if maxBreadcrumbs is set to 0; all at once', () => { + const anotherClient = new BrowserMicroSentryClient({ maxBreadcrumbs: 0 }); + + anotherClient.setBreadcrumbs(getBreadcrumbs(1)); expect(anotherClient.state.breadcrumbs?.length).toBe(0); }); - it('should ignore maxBreadcrumbs option if maxBreadcrumbs is a negative number', () => { + it('should ignore maxBreadcrumbs option if maxBreadcrumbs is a negative number; incremental addition', () => { const anotherClient = new BrowserMicroSentryClient({ maxBreadcrumbs: -100, }); - for (let i = 0; i < MAX_BREADCRUMBS + 2; i++) { - anotherClient.addBreadcrumb({ - event_id: `id${i}`, - type: 'console', - level: Severity.critical, - }); - } + getBreadcrumbs(MAX_BREADCRUMBS + 2).forEach((bc) => + anotherClient.addBreadcrumb(bc) + ); + + expect(anotherClient.state.breadcrumbs?.length).toBe(MAX_BREADCRUMBS); + }); + + it('should ignore maxBreadcrumbs option if maxBreadcrumbs is a negative number; all at once', () => { + const anotherClient = new BrowserMicroSentryClient({ + maxBreadcrumbs: -100, + }); + + anotherClient.setBreadcrumbs(getBreadcrumbs(MAX_BREADCRUMBS + 2)); expect(anotherClient.state.breadcrumbs?.length).toBe(MAX_BREADCRUMBS); }); diff --git a/libs/browser/src/lib/services/browser-micro-sentry-client.ts b/libs/browser/src/lib/services/browser-micro-sentry-client.ts index 4781609..77db041 100644 --- a/libs/browser/src/lib/services/browser-micro-sentry-client.ts +++ b/libs/browser/src/lib/services/browser-micro-sentry-client.ts @@ -140,18 +140,12 @@ export class BrowserMicroSentryClient extends MicroSentryClient { ], }); - const breadcrumbs = this.getKeyState(this.breadcrumbsKeyName); - - if (!!breadcrumbs && (breadcrumbs.length ?? 0) > this.maxBreadcrumbs) { - this.setKeyState( - this.breadcrumbsKeyName, - this.maxBreadcrumbs > 0 ? breadcrumbs.slice(-this.maxBreadcrumbs) : [] - ); - } + this.trimBreadcrumbs(); } setBreadcrumbs(breadcrumbs: Breadcrumb[] | undefined) { - this.setKeyState('breadcrumbs', breadcrumbs); + this.setKeyState(this.breadcrumbsKeyName, breadcrumbs); + this.trimBreadcrumbs(); } captureMessage(message: string, level?: Severity) { @@ -326,4 +320,14 @@ export class BrowserMicroSentryClient extends MicroSentryClient { private getKeyState(key: T): State[T] { return this._state[key]; } + + private trimBreadcrumbs(): void { + const breadcrumbs = this.getKeyState(this.breadcrumbsKeyName); + if (breadcrumbs && (breadcrumbs.length ?? 0) > this.maxBreadcrumbs) { + this.setKeyState( + this.breadcrumbsKeyName, + this.maxBreadcrumbs > 0 ? breadcrumbs.slice(-this.maxBreadcrumbs) : [] + ); + } + } }