From e9e7c65656bc870255363f2740b07f10ba14da47 Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Tue, 1 Aug 2023 14:50:52 +0200 Subject: [PATCH 1/4] Breadcrumbs support --- .../browser/tests/client/clientTests.spec.ts | 3 + .../node/tests/client/clientTests.spec.ts | 3 + .../common/nodeOptionReaderTests.spec.ts | 2 +- packages/sdk-core/src/BacktraceCoreClient.ts | 17 +++ packages/sdk-core/src/index.ts | 1 + .../configuration/BacktraceConfiguration.ts | 29 ++++ .../breadcrumbs/BacktraceBreadcrumbs.ts | 27 ++++ .../modules/breadcrumbs/BreadcrumbManager.ts | 118 ++++++++++++++++ .../modules/breadcrumbs/BreadcrumbSetup.ts | 7 + .../events/BreadcrurmbsEventSubscriber.ts | 14 ++ .../events/ConsoleEventSubscriber.ts | 40 ++++++ .../sdk-core/src/modules/breadcrumbs/index.ts | 7 + .../modules/breadcrumbs/model/Breadcrumb.ts | 10 ++ .../breadcrumbs/model/BreadcrumbLogLevel.ts | 9 ++ .../breadcrumbs/model/BreadcrumbType.ts | 11 ++ .../breadcrumbs/storage/BreadcrumbStorage.ts | 25 ++++ .../storage/InMemoryBreadcrumbsStorage.ts | 58 ++++++++ .../breadcrumbsCreationTests.spec.ts | 78 ++++++++++ .../breadcrumbsFilteringOptionsTests.spec.ts | 133 ++++++++++++++++++ .../sdk-core/tests/client/clientTests.spec.ts | 21 ++- 20 files changed, 607 insertions(+), 6 deletions(-) create mode 100644 packages/sdk-core/src/modules/breadcrumbs/BacktraceBreadcrumbs.ts create mode 100644 packages/sdk-core/src/modules/breadcrumbs/BreadcrumbManager.ts create mode 100644 packages/sdk-core/src/modules/breadcrumbs/BreadcrumbSetup.ts create mode 100644 packages/sdk-core/src/modules/breadcrumbs/events/BreadcrurmbsEventSubscriber.ts create mode 100644 packages/sdk-core/src/modules/breadcrumbs/events/ConsoleEventSubscriber.ts create mode 100644 packages/sdk-core/src/modules/breadcrumbs/index.ts create mode 100644 packages/sdk-core/src/modules/breadcrumbs/model/Breadcrumb.ts create mode 100644 packages/sdk-core/src/modules/breadcrumbs/model/BreadcrumbLogLevel.ts create mode 100644 packages/sdk-core/src/modules/breadcrumbs/model/BreadcrumbType.ts create mode 100644 packages/sdk-core/src/modules/breadcrumbs/storage/BreadcrumbStorage.ts create mode 100644 packages/sdk-core/src/modules/breadcrumbs/storage/InMemoryBreadcrumbsStorage.ts create mode 100644 packages/sdk-core/tests/breadcrumbs/breadcrumbsCreationTests.spec.ts create mode 100644 packages/sdk-core/tests/breadcrumbs/breadcrumbsFilteringOptionsTests.spec.ts diff --git a/packages/browser/tests/client/clientTests.spec.ts b/packages/browser/tests/client/clientTests.spec.ts index 0606afe6..d01519cb 100644 --- a/packages/browser/tests/client/clientTests.spec.ts +++ b/packages/browser/tests/client/clientTests.spec.ts @@ -14,6 +14,9 @@ describe('Client tests', () => { metrics: { enable: false, }, + breadcrumbs: { + enable: false, + }, }; let client: BacktraceClient; diff --git a/packages/node/tests/client/clientTests.spec.ts b/packages/node/tests/client/clientTests.spec.ts index 36318377..0be09b4f 100644 --- a/packages/node/tests/client/clientTests.spec.ts +++ b/packages/node/tests/client/clientTests.spec.ts @@ -13,6 +13,9 @@ describe('Client tests', () => { metrics: { enable: false, }, + breadcrumbs: { + enable: false, + }, }; let client: BacktraceClient; it('Should create a client', () => { diff --git a/packages/node/tests/common/nodeOptionReaderTests.spec.ts b/packages/node/tests/common/nodeOptionReaderTests.spec.ts index dc6017a0..5d22779d 100644 --- a/packages/node/tests/common/nodeOptionReaderTests.spec.ts +++ b/packages/node/tests/common/nodeOptionReaderTests.spec.ts @@ -21,7 +21,7 @@ describe('Node options reader', () => { }); it('should read undefined if the option is not available', () => { - const value = NodeOptionReader.read('', ['']); + const value = NodeOptionReader.read('', [''], ''); expect(value).toBeUndefined(); }); diff --git a/packages/sdk-core/src/BacktraceCoreClient.ts b/packages/sdk-core/src/BacktraceCoreClient.ts index d6ec9cca..f537a00f 100644 --- a/packages/sdk-core/src/BacktraceCoreClient.ts +++ b/packages/sdk-core/src/BacktraceCoreClient.ts @@ -14,6 +14,8 @@ import { BacktraceRequestHandler } from './model/http/BacktraceRequestHandler'; import { BacktraceReport } from './model/report/BacktraceReport'; import { AttributeManager } from './modules/attribute/AttributeManager'; import { ClientAttributeProvider } from './modules/attribute/ClientAttributeProvider'; +import { BacktraceBreadcrumbs, BreadcrumbSetup } from './modules/breadcrumbs'; +import { BreadcrumbManager } from './modules/breadcrumbs/BreadcrumbManager'; import { V8StackTraceConverter } from './modules/converter/V8StackTraceConverter'; import { BacktraceDataBuilder } from './modules/data/BacktraceDataBuilder'; import { BacktraceMetrics } from './modules/metrics/BacktraceMetrics'; @@ -59,11 +61,16 @@ export abstract class BacktraceCoreClient { return this._metrics; } + public get breadcrumbs(): BacktraceBreadcrumbs | undefined { + return this.breadcrumbManager; + } + /** * Client cached attachments */ public readonly attachments: BacktraceAttachment[]; + protected readonly breadcrumbManager?: BreadcrumbManager; private readonly _dataBuilder: BacktraceDataBuilder; private readonly _reportSubmission: BacktraceReportSubmission; private readonly _rateLimitWatcher: RateLimitWatcher; @@ -78,6 +85,7 @@ export abstract class BacktraceCoreClient { stackTraceConverter: BacktraceStackTraceConverter = new V8StackTraceConverter(), private readonly _sessionProvider: BacktraceSessionProvider = new SingleSessionProvider(), debugIdMapProvider?: DebugIdMapProvider, + breadcrumbsSetup?: BreadcrumbSetup, ) { this._dataBuilder = new BacktraceDataBuilder( this._sdkOptions, @@ -102,6 +110,13 @@ export abstract class BacktraceCoreClient { this._metrics = metrics; this._metrics.start(); } + + if (options?.breadcrumbs?.enable !== false) { + this.breadcrumbManager = new BreadcrumbManager(options?.breadcrumbs, breadcrumbsSetup); + this._attributeProvider.addProvider(this.breadcrumbManager); + this.attachments.push(this.breadcrumbManager.breadcrumbStorage); + this.breadcrumbManager.start(); + } } /** @@ -153,6 +168,8 @@ export abstract class BacktraceCoreClient { : new BacktraceReport(data, reportAttributes, [], { skipFrames: this.skipFrameOnMessage(data), }); + + this.breadcrumbManager?.fromReport(report); if (this.options.skipReport && this.options.skipReport(report)) { return; } diff --git a/packages/sdk-core/src/index.ts b/packages/sdk-core/src/index.ts index a6d4b0fe..88409b13 100644 --- a/packages/sdk-core/src/index.ts +++ b/packages/sdk-core/src/index.ts @@ -9,6 +9,7 @@ export * from './model/http'; export * from './model/report/BacktraceErrorType'; export * from './model/report/BacktraceReport'; export * from './modules/attribute/BacktraceAttributeProvider'; +export * from './modules/breadcrumbs'; export * from './modules/converter'; export * from './modules/metrics/BacktraceSessionProvider'; export * from './sourcemaps/index'; diff --git a/packages/sdk-core/src/model/configuration/BacktraceConfiguration.ts b/packages/sdk-core/src/model/configuration/BacktraceConfiguration.ts index 8dd31aca..0994a998 100644 --- a/packages/sdk-core/src/model/configuration/BacktraceConfiguration.ts +++ b/packages/sdk-core/src/model/configuration/BacktraceConfiguration.ts @@ -1,3 +1,4 @@ +import { BreadcrumbLogLevel, BreadcrumbType } from '../../modules/breadcrumbs'; import { BacktraceAttachment } from '../attachment'; import { BacktraceData } from '../data/BacktraceData'; import { BacktraceReport } from '../report/BacktraceReport'; @@ -26,6 +27,29 @@ export interface BacktraceMetricsOptions { size?: number; } +export interface BacktraceBreadcrumbsSettigs { + /** + * Determines if the breadcrumbs support is enabled. By default the value is set to true. + */ + enable?: boolean; + + /** + * Specifies which log level severity to include. By default all logs are included. + */ + logLevel?: BreadcrumbLogLevel; + + /** + * Specifies which breadcrumb type to include. By default all types are included. + */ + eventType?: BreadcrumbType; + + /** + * Specifies maximum number of breadcrumbs stored by the library. By default, only 100 breacrumbs + * wil be stored. + */ + maximumBreadcrumbs?: number; +} + export interface BacktraceConfiguration { /** * The server address (submission URL) is required to submit exceptions from your project to your Backtrace instance. @@ -90,6 +114,11 @@ export interface BacktraceConfiguration { * Metrics such as crash free users and crash free sessions */ metrics?: BacktraceMetricsOptions; + + /** + * Breadcrumbs settings + */ + breadcrumbs?: BacktraceBreadcrumbsSettigs; /** * Offline database settings */ diff --git a/packages/sdk-core/src/modules/breadcrumbs/BacktraceBreadcrumbs.ts b/packages/sdk-core/src/modules/breadcrumbs/BacktraceBreadcrumbs.ts new file mode 100644 index 00000000..40cfaaa8 --- /dev/null +++ b/packages/sdk-core/src/modules/breadcrumbs/BacktraceBreadcrumbs.ts @@ -0,0 +1,27 @@ +import { AttributeType } from '../../model/data/BacktraceData'; +import { BreadcrumbLogLevel } from './model/BreadcrumbLogLevel'; +import { BreadcrumbType } from './model/BreadcrumbType'; + +export interface BacktraceBreadcrumbs { + /** + * Breadcrumbs type + */ + readonly breadcrumbsType: BreadcrumbType; + + /** + * Breadcrumbs Log level + */ + readonly logLevel: BreadcrumbLogLevel; + + /** + * Dispose breadcrumbs integration + */ + dispose(): void; + + verbose(message: string, attributes?: Record): void; + debug(message: string, attributes?: Record): void; + info(message: string, attributes?: Record): void; + warn(message: string, attributes?: Record): void; + error(message: string, attributes?: Record): void; + log(message: string, level: BreadcrumbLogLevel, attributes?: Record): void; +} diff --git a/packages/sdk-core/src/modules/breadcrumbs/BreadcrumbManager.ts b/packages/sdk-core/src/modules/breadcrumbs/BreadcrumbManager.ts new file mode 100644 index 00000000..241ee18d --- /dev/null +++ b/packages/sdk-core/src/modules/breadcrumbs/BreadcrumbManager.ts @@ -0,0 +1,118 @@ +import { + BacktraceBreadcrumbs, + BreadcrumbLogLevel, + BreadcrumbType, + defaultBreadcrumbsLogLevel, + defaultBreadcurmbType, +} from '.'; +import { BacktraceBreadcrumbsSettigs } from '../../model/configuration/BacktraceConfiguration'; +import { AttributeType } from '../../model/data/BacktraceData'; +import { BacktraceReport } from '../../model/report/BacktraceReport'; +import { BreadcrumbSetup } from './BreadcrumbSetup'; +import { BreadcrumbsEventSubscriber } from './events/BreadcrurmbsEventSubscriber'; +import { ConsoleEventSubscriber } from './events/ConsoleEventSubscriber'; +import { BreadcrumbStorage } from './storage/BreadcrumbStorage'; +import { InMemoryBreadcrumbsStorage } from './storage/InMemoryBreadcrumbsStorage'; + +export class BreadcrumbManager implements BacktraceBreadcrumbs { + /** + * Breadcrumbs type + */ + public readonly breadcrumbsType: BreadcrumbType; + + public readonly BREADCRUMB_ATTRIBUTE_NAME = 'breadcrumbs.lastId'; + + /** + * Breadcrumbs Log level + */ + public readonly logLevel: BreadcrumbLogLevel; + + get type(): 'scoped' | 'dynamic' { + return 'dynamic'; + } + + public readonly breadcrumbStorage: BreadcrumbStorage; + + /** + * Determines if the breadcrumb manager is enabled. + */ + private _enabled = true; + private readonly _eventSubscribers: BreadcrumbsEventSubscriber[] = [new ConsoleEventSubscriber()]; + + constructor(configuration?: BacktraceBreadcrumbsSettigs, options?: BreadcrumbSetup) { + this.breadcrumbsType = configuration?.eventType ?? defaultBreadcurmbType; + this.logLevel = configuration?.logLevel ?? defaultBreadcrumbsLogLevel; + this.breadcrumbStorage = options?.storage ?? new InMemoryBreadcrumbsStorage(configuration?.maximumBreadcrumbs); + if (options?.subscribers) { + this._eventSubscribers.push(...options.subscribers); + } + } + + public dispose(): void { + this._enabled = false; + for (const subscriber of this._eventSubscribers) { + subscriber.dispose(); + } + } + + public get(): Record { + return { + [this.BREADCRUMB_ATTRIBUTE_NAME]: this.breadcrumbStorage.lastBreadcrumbId, + }; + } + + public start() { + for (const subscriber of this._eventSubscribers) { + subscriber.start(this); + } + } + + public verbose(message: string, attributes?: Record | undefined): boolean { + return this.log(message, BreadcrumbLogLevel.Verbose, attributes); + } + public debug(message: string, attributes?: Record | undefined): boolean { + return this.log(message, BreadcrumbLogLevel.Debug, attributes); + } + public info(message: string, attributes?: Record | undefined): boolean { + return this.log(message, BreadcrumbLogLevel.Info, attributes); + } + public warn(message: string, attributes?: Record | undefined): boolean { + return this.log(message, BreadcrumbLogLevel.Warning, attributes); + } + public error(message: string, attributes?: Record | undefined): boolean { + return this.log(message, BreadcrumbLogLevel.Error, attributes); + } + public log( + message: string, + level: BreadcrumbLogLevel, + attributes?: Record | undefined, + ): boolean { + return this.addBreadcrumb(message, level, BreadcrumbType.Manual, attributes); + } + + public fromReport(report: BacktraceReport) { + const level = report.data instanceof Error ? BreadcrumbLogLevel.Error : BreadcrumbLogLevel.Warning; + return this.addBreadcrumb(report.message, level, BreadcrumbType.System); + } + + public addBreadcrumb( + message: string, + level: BreadcrumbLogLevel, + type: BreadcrumbType, + attributes?: Record | undefined, + ): boolean { + if (!this._enabled) { + return false; + } + if ((this.logLevel & level) !== level) { + return false; + } + + if ((this.breadcrumbsType & type) !== type) { + return false; + } + + this.breadcrumbStorage.add(message, level, type, attributes); + return true; + } +} diff --git a/packages/sdk-core/src/modules/breadcrumbs/BreadcrumbSetup.ts b/packages/sdk-core/src/modules/breadcrumbs/BreadcrumbSetup.ts new file mode 100644 index 00000000..89663fb8 --- /dev/null +++ b/packages/sdk-core/src/modules/breadcrumbs/BreadcrumbSetup.ts @@ -0,0 +1,7 @@ +import { BreadcrumbsEventSubscriber } from './events/BreadcrurmbsEventSubscriber'; +import { BreadcrumbStorage } from './storage/BreadcrumbStorage'; + +export interface BreadcrumbSetup { + storage?: BreadcrumbStorage; + subscribers?: BreadcrumbsEventSubscriber[]; +} diff --git a/packages/sdk-core/src/modules/breadcrumbs/events/BreadcrurmbsEventSubscriber.ts b/packages/sdk-core/src/modules/breadcrumbs/events/BreadcrurmbsEventSubscriber.ts new file mode 100644 index 00000000..31f4ad0f --- /dev/null +++ b/packages/sdk-core/src/modules/breadcrumbs/events/BreadcrurmbsEventSubscriber.ts @@ -0,0 +1,14 @@ +import { BreadcrumbManager } from '../BreadcrumbManager'; + +export interface BreadcrumbsEventSubscriber { + /** + * Set up breadcrumbs listener + * @param breadcrumbsManager breadcrumbs manager + */ + start(breadcrumbsManager: BreadcrumbManager): void; + + /** + * Dispose all breadcrumbs events + */ + dispose(): void; +} diff --git a/packages/sdk-core/src/modules/breadcrumbs/events/ConsoleEventSubscriber.ts b/packages/sdk-core/src/modules/breadcrumbs/events/ConsoleEventSubscriber.ts new file mode 100644 index 00000000..a720d38d --- /dev/null +++ b/packages/sdk-core/src/modules/breadcrumbs/events/ConsoleEventSubscriber.ts @@ -0,0 +1,40 @@ +import { format } from 'util'; +import { BreadcrumbManager } from '../BreadcrumbManager'; +import { BreadcrumbLogLevel } from '../model/BreadcrumbLogLevel'; +import { BreadcrumbType } from '../model/BreadcrumbType'; +import { BreadcrumbsEventSubscriber } from './BreadcrurmbsEventSubscriber'; + +type ConsoleMethod = (...args: unknown[]) => void; +export class ConsoleEventSubscriber implements BreadcrumbsEventSubscriber { + /** + * All overriden console events + */ + private readonly _events: Record = {}; + + public start(breadcrumbsManager: BreadcrumbManager): void { + this.bindToConsoleMethod('log', BreadcrumbLogLevel.Info, breadcrumbsManager); + this.bindToConsoleMethod('warn', BreadcrumbLogLevel.Warning, breadcrumbsManager); + this.bindToConsoleMethod('error', BreadcrumbLogLevel.Error, breadcrumbsManager); + this.bindToConsoleMethod('debug', BreadcrumbLogLevel.Debug, breadcrumbsManager); + this.bindToConsoleMethod('trace', BreadcrumbLogLevel.Verbose, breadcrumbsManager); + } + + public dispose(): void { + for (const key in this._events) { + const consoleMethod = this._events[key]; + (console[key as keyof Console] as ConsoleMethod) = consoleMethod; + } + } + + private bindToConsoleMethod(name: keyof Console, level: BreadcrumbLogLevel, breadcrumbsManager: BreadcrumbManager) { + const originalMethod = console[name] as ConsoleMethod; + const defaultImplementation = originalMethod.bind(console); + + (console[name] as ConsoleMethod) = (...args: unknown[]) => { + defaultImplementation.apply(console, args); + const message = format(...args); + breadcrumbsManager.addBreadcrumb(message, level, BreadcrumbType.System); + }; + this._events[name] = originalMethod; + } +} diff --git a/packages/sdk-core/src/modules/breadcrumbs/index.ts b/packages/sdk-core/src/modules/breadcrumbs/index.ts new file mode 100644 index 00000000..df9b735d --- /dev/null +++ b/packages/sdk-core/src/modules/breadcrumbs/index.ts @@ -0,0 +1,7 @@ +export * from './BacktraceBreadcrumbs'; +export * from './BreadcrumbManager'; +export * from './BreadcrumbSetup'; +export * from './events/BreadcrurmbsEventSubscriber'; +export * from './model/BreadcrumbLogLevel'; +export * from './model/BreadcrumbType'; +export * from './storage/BreadcrumbStorage'; diff --git a/packages/sdk-core/src/modules/breadcrumbs/model/Breadcrumb.ts b/packages/sdk-core/src/modules/breadcrumbs/model/Breadcrumb.ts new file mode 100644 index 00000000..70af3cd6 --- /dev/null +++ b/packages/sdk-core/src/modules/breadcrumbs/model/Breadcrumb.ts @@ -0,0 +1,10 @@ +import { AttributeType } from '../../../model/data/BacktraceData'; + +export interface Breadcrumb { + id: number; + message: string; + timestamp: number; + level: string; + type: string; + attributes?: Record; +} diff --git a/packages/sdk-core/src/modules/breadcrumbs/model/BreadcrumbLogLevel.ts b/packages/sdk-core/src/modules/breadcrumbs/model/BreadcrumbLogLevel.ts new file mode 100644 index 00000000..e0f3cb59 --- /dev/null +++ b/packages/sdk-core/src/modules/breadcrumbs/model/BreadcrumbLogLevel.ts @@ -0,0 +1,9 @@ +export enum BreadcrumbLogLevel { + Verbose = 1 << 0, + Debug = 1 << 1, + Info = 1 << 2, + Warning = 1 << 3, + Error = 1 << 4, +} + +export const defaultBreadcrumbsLogLevel = (1 << 5) - 1; diff --git a/packages/sdk-core/src/modules/breadcrumbs/model/BreadcrumbType.ts b/packages/sdk-core/src/modules/breadcrumbs/model/BreadcrumbType.ts new file mode 100644 index 00000000..9eed3f78 --- /dev/null +++ b/packages/sdk-core/src/modules/breadcrumbs/model/BreadcrumbType.ts @@ -0,0 +1,11 @@ +export enum BreadcrumbType { + Manual = 1 << 0, + Log = 1 << 1, + Navigation = 1 << 2, + Http = 1 << 3, + System = 1 << 4, + User = 1 << 5, + Configuration = 1 << 6, +} + +export const defaultBreadcurmbType: BreadcrumbType = (1 << 7) - 1; diff --git a/packages/sdk-core/src/modules/breadcrumbs/storage/BreadcrumbStorage.ts b/packages/sdk-core/src/modules/breadcrumbs/storage/BreadcrumbStorage.ts new file mode 100644 index 00000000..974281c1 --- /dev/null +++ b/packages/sdk-core/src/modules/breadcrumbs/storage/BreadcrumbStorage.ts @@ -0,0 +1,25 @@ +import { BacktraceAttachment } from '../../../model/attachment'; +import { AttributeType } from '../../../model/data/BacktraceData'; +import { BreadcrumbLogLevel } from '../model/BreadcrumbLogLevel'; +import { BreadcrumbType } from '../model/BreadcrumbType'; + +export interface BreadcrumbStorage extends BacktraceAttachment { + /** + * Id of the last breadcrumb added to the SDK + */ + get lastBreadcrumbId(): number; + + /** + * Adds breadcrumb to the storage + * @param message message + * @param level log level + * @param type type + * @param attributes attributes + */ + add( + message: string, + level: BreadcrumbLogLevel, + type: BreadcrumbType, + attributes?: Record, + ): number; +} diff --git a/packages/sdk-core/src/modules/breadcrumbs/storage/InMemoryBreadcrumbsStorage.ts b/packages/sdk-core/src/modules/breadcrumbs/storage/InMemoryBreadcrumbsStorage.ts new file mode 100644 index 00000000..b7d56bfd --- /dev/null +++ b/packages/sdk-core/src/modules/breadcrumbs/storage/InMemoryBreadcrumbsStorage.ts @@ -0,0 +1,58 @@ +import { TimeHelper } from '../../../common/TimeHelper'; +import { AttributeType } from '../../../model/data/BacktraceData'; +import { Breadcrumb } from '../model/Breadcrumb'; +import { BreadcrumbLogLevel } from '../model/BreadcrumbLogLevel'; +import { BreadcrumbType } from '../model/BreadcrumbType'; +import { BreadcrumbStorage } from './BreadcrumbStorage'; + +export class InMemoryBreadcrumbsStorage implements BreadcrumbStorage { + public get lastBreadcrumbId(): number { + return this._lastBreadcrumbId; + } + /** + * Breadcrumb name + */ + public readonly name: string = 'bt-breadcrumbs-0'; + + private _lastBreadcrumbId: number = TimeHelper.toTimestampInSec(TimeHelper.now()); + private _breadcrumbs: Breadcrumb[] = []; + + constructor(private readonly _maximumBreadcrumbs: number = 100) {} + + /** + * Returns breadcrumbs in the JSON format + * @returns Breadcrumbs JSON + */ + public get(): string { + return JSON.stringify(this._breadcrumbs); + } + + public add( + message: string, + level: BreadcrumbLogLevel, + type: BreadcrumbType, + attributes?: Record | undefined, + ): number { + this._lastBreadcrumbId++; + const id = this._lastBreadcrumbId; + const breadcrumb: Breadcrumb = { + id, + message, + timestamp: TimeHelper.toTimestampInSec(TimeHelper.now()), + type: BreadcrumbType[type].toLowerCase(), + level: BreadcrumbLogLevel[level].toLowerCase(), + }; + + if (attributes) { + breadcrumb.attributes = attributes; + } + + this._breadcrumbs.push(breadcrumb); + + if (this._maximumBreadcrumbs < this._breadcrumbs.length) { + this._breadcrumbs = this._breadcrumbs.slice(this._breadcrumbs.length - this._maximumBreadcrumbs); + } + + return id; + } +} diff --git a/packages/sdk-core/tests/breadcrumbs/breadcrumbsCreationTests.spec.ts b/packages/sdk-core/tests/breadcrumbs/breadcrumbsCreationTests.spec.ts new file mode 100644 index 00000000..6ef2e86a --- /dev/null +++ b/packages/sdk-core/tests/breadcrumbs/breadcrumbsCreationTests.spec.ts @@ -0,0 +1,78 @@ +import { BreadcrumbLogLevel, BreadcrumbType } from '../../lib/modules/breadcrumbs'; +import { BreadcrumbManager } from '../../lib/modules/breadcrumbs/BreadcrumbManager'; + +describe('Breadcrumbs creation tests', () => { + describe('Last breadcrumb id attribute should be equal to last bredcrumb id in the array', () => { + const breadcrumbManager = new BreadcrumbManager(); + breadcrumbManager.info('test'); + + const attributes = breadcrumbManager.get(); + const [breadcrumb] = JSON.parse(breadcrumbManager.breadcrumbStorage.get() as string); + + expect(breadcrumb.id).toEqual(attributes[breadcrumbManager.BREADCRUMB_ATTRIBUTE_NAME]); + }); + + it('Each breadcrumb should have different id', () => { + const breadcrumbManager = new BreadcrumbManager(); + breadcrumbManager.info('test'); + breadcrumbManager.info('test2'); + + const attachment = JSON.parse(breadcrumbManager.breadcrumbStorage.get() as string); + + expect(attachment[0].id).toBeLessThan(attachment[1].id); + }); + + it('Should update breadcrumb id every time after adding a breadcrumb', () => { + const breadcrumbManager = new BreadcrumbManager(); + + breadcrumbManager.info('test'); + const attributes1 = breadcrumbManager.get(); + breadcrumbManager.info('test2'); + const attributes2 = breadcrumbManager.get(); + + expect(attributes1[breadcrumbManager.BREADCRUMB_ATTRIBUTE_NAME] as number).toBeLessThan( + attributes2[breadcrumbManager.BREADCRUMB_ATTRIBUTE_NAME] as number, + ); + }); + + it('Should set expected breadcrumb message', () => { + const message = 'test'; + const breadcrumbManager = new BreadcrumbManager(); + breadcrumbManager.info(message); + const [breadcrumb] = JSON.parse(breadcrumbManager.breadcrumbStorage.get() as string); + + expect(breadcrumb.message).toEqual(message); + }); + + it('Should set expected breadcrumb level', () => { + const message = 'test'; + const level = BreadcrumbLogLevel.Warning; + const breadcrumbManager = new BreadcrumbManager(); + breadcrumbManager.log(message, level); + const [breadcrumb] = JSON.parse(breadcrumbManager.breadcrumbStorage.get() as string); + + expect(breadcrumb.level).toEqual(BreadcrumbLogLevel[level].toLowerCase()); + }); + + it('Should set expected breadcrumb type', () => { + const message = 'test'; + const level = BreadcrumbLogLevel.Warning; + const type = BreadcrumbType.Configuration; + const breadcrumbManager = new BreadcrumbManager(); + breadcrumbManager.addBreadcrumb(message, level, type); + const [breadcrumb] = JSON.parse(breadcrumbManager.breadcrumbStorage.get() as string); + + expect(breadcrumb.type).toEqual(BreadcrumbType[type].toLowerCase()); + }); + + it('Should include attributes if they are available', () => { + const message = 'test'; + const level = BreadcrumbLogLevel.Warning; + const attributes = { foo: 'bar', baz: 1 }; + const breadcrumbManager = new BreadcrumbManager(); + breadcrumbManager.log(message, level, attributes); + const [breadcrumb] = JSON.parse(breadcrumbManager.breadcrumbStorage.get() as string); + + expect(breadcrumb.attributes).toMatchObject(attributes); + }); +}); diff --git a/packages/sdk-core/tests/breadcrumbs/breadcrumbsFilteringOptionsTests.spec.ts b/packages/sdk-core/tests/breadcrumbs/breadcrumbsFilteringOptionsTests.spec.ts new file mode 100644 index 00000000..8bc8477c --- /dev/null +++ b/packages/sdk-core/tests/breadcrumbs/breadcrumbsFilteringOptionsTests.spec.ts @@ -0,0 +1,133 @@ +import { BreadcrumbLogLevel, BreadcrumbType } from '../../lib/modules/breadcrumbs'; +import { BreadcrumbManager } from '../../lib/modules/breadcrumbs/BreadcrumbManager'; + +describe('Breadcrumbs filtering options tests', () => { + describe('Event type tests', () => { + it('Should filter out breadcrumbs based on the event type', () => { + const message = 'test'; + const breadcrumbManager = new BreadcrumbManager({ + eventType: BreadcrumbType.Configuration, + }); + + const result = breadcrumbManager.addBreadcrumb(message, BreadcrumbLogLevel.Debug, BreadcrumbType.Http); + const breadcrumbs = JSON.parse(breadcrumbManager.breadcrumbStorage.get() as string); + expect(result).toBeFalsy(); + expect(breadcrumbs.length).toEqual(0); + }); + + it('Should allow to add a breadcrumb with allowed event type', () => { + const message = 'test'; + const allowedBreadcrumbType = BreadcrumbType.Configuration; + const breadcrumbManager = new BreadcrumbManager({ + eventType: allowedBreadcrumbType, + }); + + const result = breadcrumbManager.addBreadcrumb(message, BreadcrumbLogLevel.Debug, allowedBreadcrumbType); + const [breadcrumb] = JSON.parse(breadcrumbManager.breadcrumbStorage.get() as string); + expect(result).toBeTruthy(); + expect(breadcrumb.type).toEqual(BreadcrumbType[allowedBreadcrumbType].toLowerCase()); + }); + }); + + describe('Log level tests', () => { + it('Should filter out breadcrumbs based on the log level', () => { + const message = 'test'; + const breadcrumbManager = new BreadcrumbManager({ + logLevel: BreadcrumbLogLevel.Error, + }); + + const result = breadcrumbManager.addBreadcrumb(message, BreadcrumbLogLevel.Debug, BreadcrumbType.Http); + const breadcrumbs = JSON.parse(breadcrumbManager.breadcrumbStorage.get() as string); + expect(result).toBeFalsy(); + expect(breadcrumbs.length).toEqual(0); + }); + + it('Should allow to add a breadcrumb with allowed log level', () => { + const message = 'test'; + const allowedLogLevel = BreadcrumbLogLevel.Debug; + const breadcrumbManager = new BreadcrumbManager({ + logLevel: allowedLogLevel, + }); + + const result = breadcrumbManager.addBreadcrumb(message, allowedLogLevel, BreadcrumbType.Http); + const [breadcrumb] = JSON.parse(breadcrumbManager.breadcrumbStorage.get() as string); + expect(result).toBeTruthy(); + expect(breadcrumb.level).toEqual(BreadcrumbLogLevel[allowedLogLevel].toLowerCase()); + }); + + it('Should filter out warn breadcrumb if allowed log level is error or debug', () => { + const message = 'test'; + const allowedLogLevel = BreadcrumbLogLevel.Debug | BreadcrumbLogLevel.Error; + const breadcrumbManager = new BreadcrumbManager({ + logLevel: allowedLogLevel, + }); + + const result = breadcrumbManager.warn(message); + expect(result).toBeFalsy(); + }); + + it('Should allow to store breadcrumb if user selected multiple log levels', () => { + const message = 'test'; + const allowedLogLevel = BreadcrumbLogLevel.Debug | BreadcrumbLogLevel.Error; + const breadcrumbManager = new BreadcrumbManager({ + logLevel: allowedLogLevel, + }); + + const result = breadcrumbManager.error(message); + expect(result).toBeTruthy(); + }); + }); + + describe('Disabled breadcrumbs integration', () => { + it('Should not accept breadcrumbs after breadcrumbs dispose', () => { + const breadcrumbManager = new BreadcrumbManager(); + breadcrumbManager.dispose(); + + const result = breadcrumbManager.error('test'); + expect(result).toBeFalsy(); + }); + }); + + describe('Breadcrumbs overflow tests', () => { + it('Should always store maximum breadcrumbs', () => { + const maximumBreadcrumbs = 2; + const breadcrumbManager = new BreadcrumbManager({ + maximumBreadcrumbs, + }); + for (let index = 0; index < maximumBreadcrumbs; index++) { + breadcrumbManager.error(index.toString()); + } + + const addResult = breadcrumbManager.addBreadcrumb( + 'after free space', + BreadcrumbLogLevel.Debug, + BreadcrumbType.Configuration, + ); + const breadcrumbs = JSON.parse(breadcrumbManager.breadcrumbStorage.get() as string); + + expect(addResult).toBeTruthy(); + expect(breadcrumbs.length).toEqual(maximumBreadcrumbs); + }); + + it('Should drop the oldest event to free up the space for the new one', () => { + const maximumBreadcrumbs = 2; + const breadcrumbManager = new BreadcrumbManager({ + maximumBreadcrumbs, + }); + const expectedBreadcrumbMessage = 'after free space'; + for (let index = 0; index < maximumBreadcrumbs; index++) { + breadcrumbManager.error(index.toString()); + } + + const addResult = breadcrumbManager.addBreadcrumb( + expectedBreadcrumbMessage, + BreadcrumbLogLevel.Debug, + BreadcrumbType.Configuration, + ); + const breadcrumbs = JSON.parse(breadcrumbManager.breadcrumbStorage.get() as string); + + expect(addResult).toBeTruthy(); + expect(breadcrumbs[breadcrumbs.length - 1].message).toEqual(expectedBreadcrumbMessage); + }); + }); +}); diff --git a/packages/sdk-core/tests/client/clientTests.spec.ts b/packages/sdk-core/tests/client/clientTests.spec.ts index a2921d6d..a4cb0630 100644 --- a/packages/sdk-core/tests/client/clientTests.spec.ts +++ b/packages/sdk-core/tests/client/clientTests.spec.ts @@ -28,8 +28,11 @@ describe('Client tests', () => { }); describe('Attachment tests', () => { + const disabledBreadcrumbsConfiguration = { + breadcrumbs: { enable: false }, + }; it(`Should generate an empty attachment list`, async () => { - const client = BacktraceTestClient.buildFakeClient(); + const client = BacktraceTestClient.buildFakeClient(disabledBreadcrumbsConfiguration); expect(client.attachments).toBeDefined(); expect(client.attachments.length).toEqual(0); @@ -43,7 +46,11 @@ describe('Client tests', () => { }, }; - const client = BacktraceTestClient.buildFakeClient({}, [], [inMemoryAttachment]); + const client = BacktraceTestClient.buildFakeClient( + disabledBreadcrumbsConfiguration, + [], + [inMemoryAttachment], + ); expect(client.attachments).toBeDefined(); expect(client.attachments.length).toEqual(1); @@ -51,7 +58,7 @@ describe('Client tests', () => { }); it(`Should allow to add more attachments`, async () => { - const client = BacktraceTestClient.buildFakeClient(); + const client = BacktraceTestClient.buildFakeClient(disabledBreadcrumbsConfiguration); const inMemoryAttachment = { name: 'client-in-memory-test', get() { @@ -69,7 +76,7 @@ describe('Client tests', () => { it(`Should allow to use string attachment`, async () => { const expectedAttachmentContent = 'test'; const testedAttachment = new BacktraceStringAttachment('client-add-test', expectedAttachmentContent); - const client = BacktraceTestClient.buildFakeClient(); + const client = BacktraceTestClient.buildFakeClient(disabledBreadcrumbsConfiguration); client.attachments.push(testedAttachment); expect(client.attachments).toBeDefined(); @@ -90,7 +97,11 @@ describe('Client tests', () => { return new Uint8Array(0); }, }; - const client = BacktraceTestClient.buildFakeClient({}, [], [clientAttachment]); + const client = BacktraceTestClient.buildFakeClient( + disabledBreadcrumbsConfiguration, + [], + [clientAttachment], + ); await client.send(new Error(''), {}, [reportAttachment]); From a4aa61766f9cd661a4791ffa943f3b46ef9e108a Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Wed, 2 Aug 2023 11:41:36 +0200 Subject: [PATCH 2/4] Code review adjustements --- packages/sdk-core/src/BacktraceCoreClient.ts | 22 +++---- .../configuration/BacktraceConfiguration.ts | 4 +- .../modules/breadcrumbs/BreadcrumbSetup.ts | 7 --- ...dcrumbManager.ts => BreadcrumbsManager.ts} | 20 +++---- .../modules/breadcrumbs/BreadcrumbsSetup.ts | 7 +++ .../events/BreadcrurmbsEventSubscriber.ts | 4 +- .../events/ConsoleEventSubscriber.ts | 10 +++- .../sdk-core/src/modules/breadcrumbs/index.ts | 6 +- ...dcrumbStorage.ts => BreadcrumbsStorage.ts} | 2 +- .../storage/InMemoryBreadcrumbsStorage.ts | 4 +- .../breadcrumbsCreationTests.spec.ts | 58 +++++++++---------- .../breadcrumbsFilteringOptionsTests.spec.ts | 56 +++++++++--------- 12 files changed, 102 insertions(+), 98 deletions(-) delete mode 100644 packages/sdk-core/src/modules/breadcrumbs/BreadcrumbSetup.ts rename packages/sdk-core/src/modules/breadcrumbs/{BreadcrumbManager.ts => BreadcrumbsManager.ts} (82%) create mode 100644 packages/sdk-core/src/modules/breadcrumbs/BreadcrumbsSetup.ts rename packages/sdk-core/src/modules/breadcrumbs/storage/{BreadcrumbStorage.ts => BreadcrumbsStorage.ts} (91%) diff --git a/packages/sdk-core/src/BacktraceCoreClient.ts b/packages/sdk-core/src/BacktraceCoreClient.ts index f537a00f..5cc3623c 100644 --- a/packages/sdk-core/src/BacktraceCoreClient.ts +++ b/packages/sdk-core/src/BacktraceCoreClient.ts @@ -14,8 +14,8 @@ import { BacktraceRequestHandler } from './model/http/BacktraceRequestHandler'; import { BacktraceReport } from './model/report/BacktraceReport'; import { AttributeManager } from './modules/attribute/AttributeManager'; import { ClientAttributeProvider } from './modules/attribute/ClientAttributeProvider'; -import { BacktraceBreadcrumbs, BreadcrumbSetup } from './modules/breadcrumbs'; -import { BreadcrumbManager } from './modules/breadcrumbs/BreadcrumbManager'; +import { BacktraceBreadcrumbs, BreadcrumbsSetup } from './modules/breadcrumbs'; +import { BreadcrumbsManager } from './modules/breadcrumbs/BreadcrumbsManager'; import { V8StackTraceConverter } from './modules/converter/V8StackTraceConverter'; import { BacktraceDataBuilder } from './modules/data/BacktraceDataBuilder'; import { BacktraceMetrics } from './modules/metrics/BacktraceMetrics'; @@ -62,7 +62,7 @@ export abstract class BacktraceCoreClient { } public get breadcrumbs(): BacktraceBreadcrumbs | undefined { - return this.breadcrumbManager; + return this.breadcrumbsManager; } /** @@ -70,7 +70,7 @@ export abstract class BacktraceCoreClient { */ public readonly attachments: BacktraceAttachment[]; - protected readonly breadcrumbManager?: BreadcrumbManager; + protected readonly breadcrumbsManager?: BreadcrumbsManager; private readonly _dataBuilder: BacktraceDataBuilder; private readonly _reportSubmission: BacktraceReportSubmission; private readonly _rateLimitWatcher: RateLimitWatcher; @@ -85,7 +85,7 @@ export abstract class BacktraceCoreClient { stackTraceConverter: BacktraceStackTraceConverter = new V8StackTraceConverter(), private readonly _sessionProvider: BacktraceSessionProvider = new SingleSessionProvider(), debugIdMapProvider?: DebugIdMapProvider, - breadcrumbsSetup?: BreadcrumbSetup, + breadcrumbsSetup?: BreadcrumbsSetup, ) { this._dataBuilder = new BacktraceDataBuilder( this._sdkOptions, @@ -111,11 +111,11 @@ export abstract class BacktraceCoreClient { this._metrics.start(); } - if (options?.breadcrumbs?.enable !== false) { - this.breadcrumbManager = new BreadcrumbManager(options?.breadcrumbs, breadcrumbsSetup); - this._attributeProvider.addProvider(this.breadcrumbManager); - this.attachments.push(this.breadcrumbManager.breadcrumbStorage); - this.breadcrumbManager.start(); + if (options.breadcrumbs?.enable !== false) { + this.breadcrumbsManager = new BreadcrumbsManager(options?.breadcrumbs, breadcrumbsSetup); + this._attributeProvider.addProvider(this.breadcrumbsManager); + this.attachments.push(this.breadcrumbsManager.breadcrumbsStorage); + this.breadcrumbsManager.start(); } } @@ -169,7 +169,7 @@ export abstract class BacktraceCoreClient { skipFrames: this.skipFrameOnMessage(data), }); - this.breadcrumbManager?.fromReport(report); + this.breadcrumbsManager?.logReport(report); if (this.options.skipReport && this.options.skipReport(report)) { return; } diff --git a/packages/sdk-core/src/model/configuration/BacktraceConfiguration.ts b/packages/sdk-core/src/model/configuration/BacktraceConfiguration.ts index 0994a998..5f479b7c 100644 --- a/packages/sdk-core/src/model/configuration/BacktraceConfiguration.ts +++ b/packages/sdk-core/src/model/configuration/BacktraceConfiguration.ts @@ -27,7 +27,7 @@ export interface BacktraceMetricsOptions { size?: number; } -export interface BacktraceBreadcrumbsSettigs { +export interface BacktraceBreadcrumbsSettings { /** * Determines if the breadcrumbs support is enabled. By default the value is set to true. */ @@ -118,7 +118,7 @@ export interface BacktraceConfiguration { /** * Breadcrumbs settings */ - breadcrumbs?: BacktraceBreadcrumbsSettigs; + breadcrumbs?: BacktraceBreadcrumbsSettings; /** * Offline database settings */ diff --git a/packages/sdk-core/src/modules/breadcrumbs/BreadcrumbSetup.ts b/packages/sdk-core/src/modules/breadcrumbs/BreadcrumbSetup.ts deleted file mode 100644 index 89663fb8..00000000 --- a/packages/sdk-core/src/modules/breadcrumbs/BreadcrumbSetup.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { BreadcrumbsEventSubscriber } from './events/BreadcrurmbsEventSubscriber'; -import { BreadcrumbStorage } from './storage/BreadcrumbStorage'; - -export interface BreadcrumbSetup { - storage?: BreadcrumbStorage; - subscribers?: BreadcrumbsEventSubscriber[]; -} diff --git a/packages/sdk-core/src/modules/breadcrumbs/BreadcrumbManager.ts b/packages/sdk-core/src/modules/breadcrumbs/BreadcrumbsManager.ts similarity index 82% rename from packages/sdk-core/src/modules/breadcrumbs/BreadcrumbManager.ts rename to packages/sdk-core/src/modules/breadcrumbs/BreadcrumbsManager.ts index 241ee18d..8aee22a6 100644 --- a/packages/sdk-core/src/modules/breadcrumbs/BreadcrumbManager.ts +++ b/packages/sdk-core/src/modules/breadcrumbs/BreadcrumbsManager.ts @@ -5,16 +5,16 @@ import { defaultBreadcrumbsLogLevel, defaultBreadcurmbType, } from '.'; -import { BacktraceBreadcrumbsSettigs } from '../../model/configuration/BacktraceConfiguration'; +import { BacktraceBreadcrumbsSettings } from '../../model/configuration/BacktraceConfiguration'; import { AttributeType } from '../../model/data/BacktraceData'; import { BacktraceReport } from '../../model/report/BacktraceReport'; -import { BreadcrumbSetup } from './BreadcrumbSetup'; +import { BreadcrumbsSetup } from './BreadcrumbsSetup'; import { BreadcrumbsEventSubscriber } from './events/BreadcrurmbsEventSubscriber'; import { ConsoleEventSubscriber } from './events/ConsoleEventSubscriber'; -import { BreadcrumbStorage } from './storage/BreadcrumbStorage'; +import { BreadcrumbsStorage } from './storage/BreadcrumbsStorage'; import { InMemoryBreadcrumbsStorage } from './storage/InMemoryBreadcrumbsStorage'; -export class BreadcrumbManager implements BacktraceBreadcrumbs { +export class BreadcrumbsManager implements BacktraceBreadcrumbs { /** * Breadcrumbs type */ @@ -31,7 +31,7 @@ export class BreadcrumbManager implements BacktraceBreadcrumbs { return 'dynamic'; } - public readonly breadcrumbStorage: BreadcrumbStorage; + public readonly breadcrumbsStorage: BreadcrumbsStorage; /** * Determines if the breadcrumb manager is enabled. @@ -39,10 +39,10 @@ export class BreadcrumbManager implements BacktraceBreadcrumbs { private _enabled = true; private readonly _eventSubscribers: BreadcrumbsEventSubscriber[] = [new ConsoleEventSubscriber()]; - constructor(configuration?: BacktraceBreadcrumbsSettigs, options?: BreadcrumbSetup) { + constructor(configuration?: BacktraceBreadcrumbsSettings, options?: BreadcrumbsSetup) { this.breadcrumbsType = configuration?.eventType ?? defaultBreadcurmbType; this.logLevel = configuration?.logLevel ?? defaultBreadcrumbsLogLevel; - this.breadcrumbStorage = options?.storage ?? new InMemoryBreadcrumbsStorage(configuration?.maximumBreadcrumbs); + this.breadcrumbsStorage = options?.storage ?? new InMemoryBreadcrumbsStorage(configuration?.maximumBreadcrumbs); if (options?.subscribers) { this._eventSubscribers.push(...options.subscribers); } @@ -57,7 +57,7 @@ export class BreadcrumbManager implements BacktraceBreadcrumbs { public get(): Record { return { - [this.BREADCRUMB_ATTRIBUTE_NAME]: this.breadcrumbStorage.lastBreadcrumbId, + [this.BREADCRUMB_ATTRIBUTE_NAME]: this.breadcrumbsStorage.lastBreadcrumbId, }; } @@ -90,7 +90,7 @@ export class BreadcrumbManager implements BacktraceBreadcrumbs { return this.addBreadcrumb(message, level, BreadcrumbType.Manual, attributes); } - public fromReport(report: BacktraceReport) { + public logReport(report: BacktraceReport) { const level = report.data instanceof Error ? BreadcrumbLogLevel.Error : BreadcrumbLogLevel.Warning; return this.addBreadcrumb(report.message, level, BreadcrumbType.System); } @@ -112,7 +112,7 @@ export class BreadcrumbManager implements BacktraceBreadcrumbs { return false; } - this.breadcrumbStorage.add(message, level, type, attributes); + this.breadcrumbsStorage.add(message, level, type, attributes); return true; } } diff --git a/packages/sdk-core/src/modules/breadcrumbs/BreadcrumbsSetup.ts b/packages/sdk-core/src/modules/breadcrumbs/BreadcrumbsSetup.ts new file mode 100644 index 00000000..a300ebe0 --- /dev/null +++ b/packages/sdk-core/src/modules/breadcrumbs/BreadcrumbsSetup.ts @@ -0,0 +1,7 @@ +import { BreadcrumbsEventSubscriber } from './events/BreadcrurmbsEventSubscriber'; +import { BreadcrumbsStorage } from './storage/BreadcrumbsStorage'; + +export interface BreadcrumbsSetup { + storage?: BreadcrumbsStorage; + subscribers?: BreadcrumbsEventSubscriber[]; +} diff --git a/packages/sdk-core/src/modules/breadcrumbs/events/BreadcrurmbsEventSubscriber.ts b/packages/sdk-core/src/modules/breadcrumbs/events/BreadcrurmbsEventSubscriber.ts index 31f4ad0f..5fdc31b3 100644 --- a/packages/sdk-core/src/modules/breadcrumbs/events/BreadcrurmbsEventSubscriber.ts +++ b/packages/sdk-core/src/modules/breadcrumbs/events/BreadcrurmbsEventSubscriber.ts @@ -1,11 +1,11 @@ -import { BreadcrumbManager } from '../BreadcrumbManager'; +import { BreadcrumbsManager } from '../BreadcrumbsManager'; export interface BreadcrumbsEventSubscriber { /** * Set up breadcrumbs listener * @param breadcrumbsManager breadcrumbs manager */ - start(breadcrumbsManager: BreadcrumbManager): void; + start(breadcrumbsManager: BreadcrumbsManager): void; /** * Dispose all breadcrumbs events diff --git a/packages/sdk-core/src/modules/breadcrumbs/events/ConsoleEventSubscriber.ts b/packages/sdk-core/src/modules/breadcrumbs/events/ConsoleEventSubscriber.ts index a720d38d..f1e2eed4 100644 --- a/packages/sdk-core/src/modules/breadcrumbs/events/ConsoleEventSubscriber.ts +++ b/packages/sdk-core/src/modules/breadcrumbs/events/ConsoleEventSubscriber.ts @@ -1,5 +1,5 @@ import { format } from 'util'; -import { BreadcrumbManager } from '../BreadcrumbManager'; +import { BreadcrumbsManager } from '../BreadcrumbsManager'; import { BreadcrumbLogLevel } from '../model/BreadcrumbLogLevel'; import { BreadcrumbType } from '../model/BreadcrumbType'; import { BreadcrumbsEventSubscriber } from './BreadcrurmbsEventSubscriber'; @@ -11,7 +11,7 @@ export class ConsoleEventSubscriber implements BreadcrumbsEventSubscriber { */ private readonly _events: Record = {}; - public start(breadcrumbsManager: BreadcrumbManager): void { + public start(breadcrumbsManager: BreadcrumbsManager): void { this.bindToConsoleMethod('log', BreadcrumbLogLevel.Info, breadcrumbsManager); this.bindToConsoleMethod('warn', BreadcrumbLogLevel.Warning, breadcrumbsManager); this.bindToConsoleMethod('error', BreadcrumbLogLevel.Error, breadcrumbsManager); @@ -26,7 +26,11 @@ export class ConsoleEventSubscriber implements BreadcrumbsEventSubscriber { } } - private bindToConsoleMethod(name: keyof Console, level: BreadcrumbLogLevel, breadcrumbsManager: BreadcrumbManager) { + private bindToConsoleMethod( + name: keyof Console, + level: BreadcrumbLogLevel, + breadcrumbsManager: BreadcrumbsManager, + ) { const originalMethod = console[name] as ConsoleMethod; const defaultImplementation = originalMethod.bind(console); diff --git a/packages/sdk-core/src/modules/breadcrumbs/index.ts b/packages/sdk-core/src/modules/breadcrumbs/index.ts index df9b735d..376dee6c 100644 --- a/packages/sdk-core/src/modules/breadcrumbs/index.ts +++ b/packages/sdk-core/src/modules/breadcrumbs/index.ts @@ -1,7 +1,7 @@ export * from './BacktraceBreadcrumbs'; -export * from './BreadcrumbManager'; -export * from './BreadcrumbSetup'; +export * from './BreadcrumbsManager'; +export * from './BreadcrumbsSetup'; export * from './events/BreadcrurmbsEventSubscriber'; export * from './model/BreadcrumbLogLevel'; export * from './model/BreadcrumbType'; -export * from './storage/BreadcrumbStorage'; +export * from './storage/BreadcrumbsStorage'; diff --git a/packages/sdk-core/src/modules/breadcrumbs/storage/BreadcrumbStorage.ts b/packages/sdk-core/src/modules/breadcrumbs/storage/BreadcrumbsStorage.ts similarity index 91% rename from packages/sdk-core/src/modules/breadcrumbs/storage/BreadcrumbStorage.ts rename to packages/sdk-core/src/modules/breadcrumbs/storage/BreadcrumbsStorage.ts index 974281c1..da48af8b 100644 --- a/packages/sdk-core/src/modules/breadcrumbs/storage/BreadcrumbStorage.ts +++ b/packages/sdk-core/src/modules/breadcrumbs/storage/BreadcrumbsStorage.ts @@ -3,7 +3,7 @@ import { AttributeType } from '../../../model/data/BacktraceData'; import { BreadcrumbLogLevel } from '../model/BreadcrumbLogLevel'; import { BreadcrumbType } from '../model/BreadcrumbType'; -export interface BreadcrumbStorage extends BacktraceAttachment { +export interface BreadcrumbsStorage extends BacktraceAttachment { /** * Id of the last breadcrumb added to the SDK */ diff --git a/packages/sdk-core/src/modules/breadcrumbs/storage/InMemoryBreadcrumbsStorage.ts b/packages/sdk-core/src/modules/breadcrumbs/storage/InMemoryBreadcrumbsStorage.ts index b7d56bfd..87897ad2 100644 --- a/packages/sdk-core/src/modules/breadcrumbs/storage/InMemoryBreadcrumbsStorage.ts +++ b/packages/sdk-core/src/modules/breadcrumbs/storage/InMemoryBreadcrumbsStorage.ts @@ -3,9 +3,9 @@ import { AttributeType } from '../../../model/data/BacktraceData'; import { Breadcrumb } from '../model/Breadcrumb'; import { BreadcrumbLogLevel } from '../model/BreadcrumbLogLevel'; import { BreadcrumbType } from '../model/BreadcrumbType'; -import { BreadcrumbStorage } from './BreadcrumbStorage'; +import { BreadcrumbsStorage } from './BreadcrumbsStorage'; -export class InMemoryBreadcrumbsStorage implements BreadcrumbStorage { +export class InMemoryBreadcrumbsStorage implements BreadcrumbsStorage { public get lastBreadcrumbId(): number { return this._lastBreadcrumbId; } diff --git a/packages/sdk-core/tests/breadcrumbs/breadcrumbsCreationTests.spec.ts b/packages/sdk-core/tests/breadcrumbs/breadcrumbsCreationTests.spec.ts index 6ef2e86a..d64694b9 100644 --- a/packages/sdk-core/tests/breadcrumbs/breadcrumbsCreationTests.spec.ts +++ b/packages/sdk-core/tests/breadcrumbs/breadcrumbsCreationTests.spec.ts @@ -1,45 +1,45 @@ import { BreadcrumbLogLevel, BreadcrumbType } from '../../lib/modules/breadcrumbs'; -import { BreadcrumbManager } from '../../lib/modules/breadcrumbs/BreadcrumbManager'; +import { BreadcrumbsManager } from '../../lib/modules/breadcrumbs/BreadcrumbsManager'; describe('Breadcrumbs creation tests', () => { describe('Last breadcrumb id attribute should be equal to last bredcrumb id in the array', () => { - const breadcrumbManager = new BreadcrumbManager(); - breadcrumbManager.info('test'); + const breadcrumbsManager = new BreadcrumbsManager(); + breadcrumbsManager.info('test'); - const attributes = breadcrumbManager.get(); - const [breadcrumb] = JSON.parse(breadcrumbManager.breadcrumbStorage.get() as string); + const attributes = breadcrumbsManager.get(); + const [breadcrumb] = JSON.parse(breadcrumbsManager.breadcrumbsStorage.get() as string); - expect(breadcrumb.id).toEqual(attributes[breadcrumbManager.BREADCRUMB_ATTRIBUTE_NAME]); + expect(breadcrumb.id).toEqual(attributes[breadcrumbsManager.BREADCRUMB_ATTRIBUTE_NAME]); }); it('Each breadcrumb should have different id', () => { - const breadcrumbManager = new BreadcrumbManager(); - breadcrumbManager.info('test'); - breadcrumbManager.info('test2'); + const breadcrumbsManager = new BreadcrumbsManager(); + breadcrumbsManager.info('test'); + breadcrumbsManager.info('test2'); - const attachment = JSON.parse(breadcrumbManager.breadcrumbStorage.get() as string); + const attachment = JSON.parse(breadcrumbsManager.breadcrumbsStorage.get() as string); expect(attachment[0].id).toBeLessThan(attachment[1].id); }); it('Should update breadcrumb id every time after adding a breadcrumb', () => { - const breadcrumbManager = new BreadcrumbManager(); + const breadcrumbsManager = new BreadcrumbsManager(); - breadcrumbManager.info('test'); - const attributes1 = breadcrumbManager.get(); - breadcrumbManager.info('test2'); - const attributes2 = breadcrumbManager.get(); + breadcrumbsManager.info('test'); + const attributes1 = breadcrumbsManager.get(); + breadcrumbsManager.info('test2'); + const attributes2 = breadcrumbsManager.get(); - expect(attributes1[breadcrumbManager.BREADCRUMB_ATTRIBUTE_NAME] as number).toBeLessThan( - attributes2[breadcrumbManager.BREADCRUMB_ATTRIBUTE_NAME] as number, + expect(attributes1[breadcrumbsManager.BREADCRUMB_ATTRIBUTE_NAME] as number).toBeLessThan( + attributes2[breadcrumbsManager.BREADCRUMB_ATTRIBUTE_NAME] as number, ); }); it('Should set expected breadcrumb message', () => { const message = 'test'; - const breadcrumbManager = new BreadcrumbManager(); - breadcrumbManager.info(message); - const [breadcrumb] = JSON.parse(breadcrumbManager.breadcrumbStorage.get() as string); + const breadcrumbsManager = new BreadcrumbsManager(); + breadcrumbsManager.info(message); + const [breadcrumb] = JSON.parse(breadcrumbsManager.breadcrumbsStorage.get() as string); expect(breadcrumb.message).toEqual(message); }); @@ -47,9 +47,9 @@ describe('Breadcrumbs creation tests', () => { it('Should set expected breadcrumb level', () => { const message = 'test'; const level = BreadcrumbLogLevel.Warning; - const breadcrumbManager = new BreadcrumbManager(); - breadcrumbManager.log(message, level); - const [breadcrumb] = JSON.parse(breadcrumbManager.breadcrumbStorage.get() as string); + const breadcrumbsManager = new BreadcrumbsManager(); + breadcrumbsManager.log(message, level); + const [breadcrumb] = JSON.parse(breadcrumbsManager.breadcrumbsStorage.get() as string); expect(breadcrumb.level).toEqual(BreadcrumbLogLevel[level].toLowerCase()); }); @@ -58,9 +58,9 @@ describe('Breadcrumbs creation tests', () => { const message = 'test'; const level = BreadcrumbLogLevel.Warning; const type = BreadcrumbType.Configuration; - const breadcrumbManager = new BreadcrumbManager(); - breadcrumbManager.addBreadcrumb(message, level, type); - const [breadcrumb] = JSON.parse(breadcrumbManager.breadcrumbStorage.get() as string); + const breadcrumbsManager = new BreadcrumbsManager(); + breadcrumbsManager.addBreadcrumb(message, level, type); + const [breadcrumb] = JSON.parse(breadcrumbsManager.breadcrumbsStorage.get() as string); expect(breadcrumb.type).toEqual(BreadcrumbType[type].toLowerCase()); }); @@ -69,9 +69,9 @@ describe('Breadcrumbs creation tests', () => { const message = 'test'; const level = BreadcrumbLogLevel.Warning; const attributes = { foo: 'bar', baz: 1 }; - const breadcrumbManager = new BreadcrumbManager(); - breadcrumbManager.log(message, level, attributes); - const [breadcrumb] = JSON.parse(breadcrumbManager.breadcrumbStorage.get() as string); + const breadcrumbsManager = new BreadcrumbsManager(); + breadcrumbsManager.log(message, level, attributes); + const [breadcrumb] = JSON.parse(breadcrumbsManager.breadcrumbsStorage.get() as string); expect(breadcrumb.attributes).toMatchObject(attributes); }); diff --git a/packages/sdk-core/tests/breadcrumbs/breadcrumbsFilteringOptionsTests.spec.ts b/packages/sdk-core/tests/breadcrumbs/breadcrumbsFilteringOptionsTests.spec.ts index 8bc8477c..672de4b2 100644 --- a/packages/sdk-core/tests/breadcrumbs/breadcrumbsFilteringOptionsTests.spec.ts +++ b/packages/sdk-core/tests/breadcrumbs/breadcrumbsFilteringOptionsTests.spec.ts @@ -1,16 +1,16 @@ import { BreadcrumbLogLevel, BreadcrumbType } from '../../lib/modules/breadcrumbs'; -import { BreadcrumbManager } from '../../lib/modules/breadcrumbs/BreadcrumbManager'; +import { BreadcrumbsManager } from '../../lib/modules/breadcrumbs/BreadcrumbsManager'; describe('Breadcrumbs filtering options tests', () => { describe('Event type tests', () => { it('Should filter out breadcrumbs based on the event type', () => { const message = 'test'; - const breadcrumbManager = new BreadcrumbManager({ + const breadcrumbsManager = new BreadcrumbsManager({ eventType: BreadcrumbType.Configuration, }); - const result = breadcrumbManager.addBreadcrumb(message, BreadcrumbLogLevel.Debug, BreadcrumbType.Http); - const breadcrumbs = JSON.parse(breadcrumbManager.breadcrumbStorage.get() as string); + const result = breadcrumbsManager.addBreadcrumb(message, BreadcrumbLogLevel.Debug, BreadcrumbType.Http); + const breadcrumbs = JSON.parse(breadcrumbsManager.breadcrumbsStorage.get() as string); expect(result).toBeFalsy(); expect(breadcrumbs.length).toEqual(0); }); @@ -18,12 +18,12 @@ describe('Breadcrumbs filtering options tests', () => { it('Should allow to add a breadcrumb with allowed event type', () => { const message = 'test'; const allowedBreadcrumbType = BreadcrumbType.Configuration; - const breadcrumbManager = new BreadcrumbManager({ + const breadcrumbsManager = new BreadcrumbsManager({ eventType: allowedBreadcrumbType, }); - const result = breadcrumbManager.addBreadcrumb(message, BreadcrumbLogLevel.Debug, allowedBreadcrumbType); - const [breadcrumb] = JSON.parse(breadcrumbManager.breadcrumbStorage.get() as string); + const result = breadcrumbsManager.addBreadcrumb(message, BreadcrumbLogLevel.Debug, allowedBreadcrumbType); + const [breadcrumb] = JSON.parse(breadcrumbsManager.breadcrumbsStorage.get() as string); expect(result).toBeTruthy(); expect(breadcrumb.type).toEqual(BreadcrumbType[allowedBreadcrumbType].toLowerCase()); }); @@ -32,12 +32,12 @@ describe('Breadcrumbs filtering options tests', () => { describe('Log level tests', () => { it('Should filter out breadcrumbs based on the log level', () => { const message = 'test'; - const breadcrumbManager = new BreadcrumbManager({ + const breadcrumbsManager = new BreadcrumbsManager({ logLevel: BreadcrumbLogLevel.Error, }); - const result = breadcrumbManager.addBreadcrumb(message, BreadcrumbLogLevel.Debug, BreadcrumbType.Http); - const breadcrumbs = JSON.parse(breadcrumbManager.breadcrumbStorage.get() as string); + const result = breadcrumbsManager.addBreadcrumb(message, BreadcrumbLogLevel.Debug, BreadcrumbType.Http); + const breadcrumbs = JSON.parse(breadcrumbsManager.breadcrumbsStorage.get() as string); expect(result).toBeFalsy(); expect(breadcrumbs.length).toEqual(0); }); @@ -45,12 +45,12 @@ describe('Breadcrumbs filtering options tests', () => { it('Should allow to add a breadcrumb with allowed log level', () => { const message = 'test'; const allowedLogLevel = BreadcrumbLogLevel.Debug; - const breadcrumbManager = new BreadcrumbManager({ + const breadcrumbsManager = new BreadcrumbsManager({ logLevel: allowedLogLevel, }); - const result = breadcrumbManager.addBreadcrumb(message, allowedLogLevel, BreadcrumbType.Http); - const [breadcrumb] = JSON.parse(breadcrumbManager.breadcrumbStorage.get() as string); + const result = breadcrumbsManager.addBreadcrumb(message, allowedLogLevel, BreadcrumbType.Http); + const [breadcrumb] = JSON.parse(breadcrumbsManager.breadcrumbsStorage.get() as string); expect(result).toBeTruthy(); expect(breadcrumb.level).toEqual(BreadcrumbLogLevel[allowedLogLevel].toLowerCase()); }); @@ -58,32 +58,32 @@ describe('Breadcrumbs filtering options tests', () => { it('Should filter out warn breadcrumb if allowed log level is error or debug', () => { const message = 'test'; const allowedLogLevel = BreadcrumbLogLevel.Debug | BreadcrumbLogLevel.Error; - const breadcrumbManager = new BreadcrumbManager({ + const breadcrumbsManager = new BreadcrumbsManager({ logLevel: allowedLogLevel, }); - const result = breadcrumbManager.warn(message); + const result = breadcrumbsManager.warn(message); expect(result).toBeFalsy(); }); it('Should allow to store breadcrumb if user selected multiple log levels', () => { const message = 'test'; const allowedLogLevel = BreadcrumbLogLevel.Debug | BreadcrumbLogLevel.Error; - const breadcrumbManager = new BreadcrumbManager({ + const breadcrumbsManager = new BreadcrumbsManager({ logLevel: allowedLogLevel, }); - const result = breadcrumbManager.error(message); + const result = breadcrumbsManager.error(message); expect(result).toBeTruthy(); }); }); describe('Disabled breadcrumbs integration', () => { it('Should not accept breadcrumbs after breadcrumbs dispose', () => { - const breadcrumbManager = new BreadcrumbManager(); - breadcrumbManager.dispose(); + const breadcrumbsManager = new BreadcrumbsManager(); + breadcrumbsManager.dispose(); - const result = breadcrumbManager.error('test'); + const result = breadcrumbsManager.error('test'); expect(result).toBeFalsy(); }); }); @@ -91,19 +91,19 @@ describe('Breadcrumbs filtering options tests', () => { describe('Breadcrumbs overflow tests', () => { it('Should always store maximum breadcrumbs', () => { const maximumBreadcrumbs = 2; - const breadcrumbManager = new BreadcrumbManager({ + const breadcrumbsManager = new BreadcrumbsManager({ maximumBreadcrumbs, }); for (let index = 0; index < maximumBreadcrumbs; index++) { - breadcrumbManager.error(index.toString()); + breadcrumbsManager.error(index.toString()); } - const addResult = breadcrumbManager.addBreadcrumb( + const addResult = breadcrumbsManager.addBreadcrumb( 'after free space', BreadcrumbLogLevel.Debug, BreadcrumbType.Configuration, ); - const breadcrumbs = JSON.parse(breadcrumbManager.breadcrumbStorage.get() as string); + const breadcrumbs = JSON.parse(breadcrumbsManager.breadcrumbsStorage.get() as string); expect(addResult).toBeTruthy(); expect(breadcrumbs.length).toEqual(maximumBreadcrumbs); @@ -111,20 +111,20 @@ describe('Breadcrumbs filtering options tests', () => { it('Should drop the oldest event to free up the space for the new one', () => { const maximumBreadcrumbs = 2; - const breadcrumbManager = new BreadcrumbManager({ + const breadcrumbsManager = new BreadcrumbsManager({ maximumBreadcrumbs, }); const expectedBreadcrumbMessage = 'after free space'; for (let index = 0; index < maximumBreadcrumbs; index++) { - breadcrumbManager.error(index.toString()); + breadcrumbsManager.error(index.toString()); } - const addResult = breadcrumbManager.addBreadcrumb( + const addResult = breadcrumbsManager.addBreadcrumb( expectedBreadcrumbMessage, BreadcrumbLogLevel.Debug, BreadcrumbType.Configuration, ); - const breadcrumbs = JSON.parse(breadcrumbManager.breadcrumbStorage.get() as string); + const breadcrumbs = JSON.parse(breadcrumbsManager.breadcrumbsStorage.get() as string); expect(addResult).toBeTruthy(); expect(breadcrumbs[breadcrumbs.length - 1].message).toEqual(expectedBreadcrumbMessage); From a2419e5639cb9e1f9abb5d38f8056eb62cd67dfd Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Fri, 4 Aug 2023 14:14:23 +0200 Subject: [PATCH 3/4] Move from array to overwriting array data structure --- .../src/dataStructures/OverwritingArray.ts | 47 +++++++++++++++++++ .../OverwritingArrayIterator.ts | 25 ++++++++++ .../storage/InMemoryBreadcrumbsStorage.ts | 15 +++--- 3 files changed, 79 insertions(+), 8 deletions(-) create mode 100644 packages/sdk-core/src/dataStructures/OverwritingArray.ts create mode 100644 packages/sdk-core/src/dataStructures/OverwritingArrayIterator.ts diff --git a/packages/sdk-core/src/dataStructures/OverwritingArray.ts b/packages/sdk-core/src/dataStructures/OverwritingArray.ts new file mode 100644 index 00000000..d4589284 --- /dev/null +++ b/packages/sdk-core/src/dataStructures/OverwritingArray.ts @@ -0,0 +1,47 @@ +import { OverwritingArrayIterator } from './OverwritingArrayIterator'; + +export class OverwritingArray { + private _array: T[]; + private _index = 0; + private _size = 0; + private _startIndex = 0; + constructor(public readonly capacity: number) { + this._array = this.createArray(); + } + public add(value: T): this { + this._array[this._index] = value; + this._index = this.incrementIndex(this._index); + this._startIndex = this.incrementStartingIndex(); + this._size = this.incrementSize(); + return this; + } + + public clear(): void { + this._array = this.createArray(); + } + + public values(): IterableIterator { + return new OverwritingArrayIterator(this._array, this._startIndex, this._size); + } + + [Symbol.iterator](): IterableIterator { + return new OverwritingArrayIterator(this._array, this._startIndex, this._size); + } + + private incrementIndex(index: number) { + return (index + 1) % this.capacity; + } + + private incrementStartingIndex() { + if (this._size !== this.capacity) { + return this._startIndex; + } + return this.incrementIndex(this._startIndex); + } + private incrementSize() { + return Math.min(this.capacity, this._size + 1); + } + private createArray() { + return new Array(this.capacity); + } +} diff --git a/packages/sdk-core/src/dataStructures/OverwritingArrayIterator.ts b/packages/sdk-core/src/dataStructures/OverwritingArrayIterator.ts new file mode 100644 index 00000000..f037cb03 --- /dev/null +++ b/packages/sdk-core/src/dataStructures/OverwritingArrayIterator.ts @@ -0,0 +1,25 @@ +export class OverwritingArrayIterator implements IterableIterator { + private _index?: number; + + constructor(private readonly _source: T[], private readonly _offset: number, private readonly _size: number) {} + + [Symbol.iterator](): IterableIterator { + return new OverwritingArrayIterator(this._source, this._offset, this._size); + } + next(): IteratorResult { + if (this._index === undefined) { + this._index = 0; + } else if (this._index === this._size - 1) { + return { + done: true, + value: undefined, + }; + } else { + this._index++; + } + return { + done: false, + value: this._source[(this._index + this._offset) % this._size], + }; + } +} diff --git a/packages/sdk-core/src/modules/breadcrumbs/storage/InMemoryBreadcrumbsStorage.ts b/packages/sdk-core/src/modules/breadcrumbs/storage/InMemoryBreadcrumbsStorage.ts index 87897ad2..483a913d 100644 --- a/packages/sdk-core/src/modules/breadcrumbs/storage/InMemoryBreadcrumbsStorage.ts +++ b/packages/sdk-core/src/modules/breadcrumbs/storage/InMemoryBreadcrumbsStorage.ts @@ -1,4 +1,5 @@ import { TimeHelper } from '../../../common/TimeHelper'; +import { OverwritingArray } from '../../../dataStructures/OverwritingArray'; import { AttributeType } from '../../../model/data/BacktraceData'; import { Breadcrumb } from '../model/Breadcrumb'; import { BreadcrumbLogLevel } from '../model/BreadcrumbLogLevel'; @@ -15,16 +16,18 @@ export class InMemoryBreadcrumbsStorage implements BreadcrumbsStorage { public readonly name: string = 'bt-breadcrumbs-0'; private _lastBreadcrumbId: number = TimeHelper.toTimestampInSec(TimeHelper.now()); - private _breadcrumbs: Breadcrumb[] = []; + private _breadcrumbs: OverwritingArray; - constructor(private readonly _maximumBreadcrumbs: number = 100) {} + constructor(maximumBreadcrumbs = 100) { + this._breadcrumbs = new OverwritingArray(maximumBreadcrumbs); + } /** * Returns breadcrumbs in the JSON format * @returns Breadcrumbs JSON */ public get(): string { - return JSON.stringify(this._breadcrumbs); + return JSON.stringify([...this._breadcrumbs.values()]); } public add( @@ -47,11 +50,7 @@ export class InMemoryBreadcrumbsStorage implements BreadcrumbsStorage { breadcrumb.attributes = attributes; } - this._breadcrumbs.push(breadcrumb); - - if (this._maximumBreadcrumbs < this._breadcrumbs.length) { - this._breadcrumbs = this._breadcrumbs.slice(this._breadcrumbs.length - this._maximumBreadcrumbs); - } + this._breadcrumbs.add(breadcrumb); return id; } From d4aa2da8f4038778375ef07298549f63b4a13913 Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Fri, 4 Aug 2023 14:29:49 +0200 Subject: [PATCH 4/4] Return if array is empty --- .../sdk-core/src/dataStructures/OverwritingArrayIterator.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/sdk-core/src/dataStructures/OverwritingArrayIterator.ts b/packages/sdk-core/src/dataStructures/OverwritingArrayIterator.ts index f037cb03..879656d8 100644 --- a/packages/sdk-core/src/dataStructures/OverwritingArrayIterator.ts +++ b/packages/sdk-core/src/dataStructures/OverwritingArrayIterator.ts @@ -7,6 +7,12 @@ export class OverwritingArrayIterator implements IterableIterator { return new OverwritingArrayIterator(this._source, this._offset, this._size); } next(): IteratorResult { + if (this._size === 0) { + return { + done: true, + value: undefined, + }; + } if (this._index === undefined) { this._index = 0; } else if (this._index === this._size - 1) {