Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/browser/tests/client/clientTests.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ describe('Client tests', () => {
metrics: {
enable: false,
},
breadcrumbs: {
enable: false,
},
};

let client: BacktraceClient;
Expand Down
3 changes: 3 additions & 0 deletions packages/node/tests/client/clientTests.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ describe('Client tests', () => {
metrics: {
enable: false,
},
breadcrumbs: {
enable: false,
},
};
let client: BacktraceClient;
it('Should create a client', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/node/tests/common/nodeOptionReaderTests.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
Expand Down
17 changes: 17 additions & 0 deletions packages/sdk-core/src/BacktraceCoreClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, 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';
Expand Down Expand Up @@ -59,11 +61,16 @@ export abstract class BacktraceCoreClient {
return this._metrics;
}

public get breadcrumbs(): BacktraceBreadcrumbs | undefined {
return this.breadcrumbsManager;
}

/**
* Client cached attachments
*/
public readonly attachments: BacktraceAttachment[];

protected readonly breadcrumbsManager?: BreadcrumbsManager;
private readonly _dataBuilder: BacktraceDataBuilder;
private readonly _reportSubmission: BacktraceReportSubmission;
private readonly _rateLimitWatcher: RateLimitWatcher;
Expand All @@ -78,6 +85,7 @@ export abstract class BacktraceCoreClient {
stackTraceConverter: BacktraceStackTraceConverter = new V8StackTraceConverter(),
private readonly _sessionProvider: BacktraceSessionProvider = new SingleSessionProvider(),
debugIdMapProvider?: DebugIdMapProvider,
breadcrumbsSetup?: BreadcrumbsSetup,
) {
this._dataBuilder = new BacktraceDataBuilder(
this._sdkOptions,
Expand All @@ -102,6 +110,13 @@ export abstract class BacktraceCoreClient {
this._metrics = metrics;
this._metrics.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();
}
}

/**
Expand Down Expand Up @@ -153,6 +168,8 @@ export abstract class BacktraceCoreClient {
: new BacktraceReport(data, reportAttributes, [], {
skipFrames: this.skipFrameOnMessage(data),
});

this.breadcrumbsManager?.logReport(report);
if (this.options.skipReport && this.options.skipReport(report)) {
return;
}
Expand Down
47 changes: 47 additions & 0 deletions packages/sdk-core/src/dataStructures/OverwritingArray.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { OverwritingArrayIterator } from './OverwritingArrayIterator';

export class OverwritingArray<T> {
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<T> {
return new OverwritingArrayIterator<T>(this._array, this._startIndex, this._size);
}

[Symbol.iterator](): IterableIterator<T> {
return new OverwritingArrayIterator<T>(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);
}
}
31 changes: 31 additions & 0 deletions packages/sdk-core/src/dataStructures/OverwritingArrayIterator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export class OverwritingArrayIterator<T> implements IterableIterator<T> {
private _index?: number;

constructor(private readonly _source: T[], private readonly _offset: number, private readonly _size: number) {}

[Symbol.iterator](): IterableIterator<T> {
return new OverwritingArrayIterator(this._source, this._offset, this._size);
}
next(): IteratorResult<T> {
if (this._size === 0) {
return {
done: true,
value: undefined,
};
}
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],
};
}
}
1 change: 1 addition & 0 deletions packages/sdk-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { BreadcrumbLogLevel, BreadcrumbType } from '../../modules/breadcrumbs';
import { BacktraceAttachment } from '../attachment';
import { BacktraceData } from '../data/BacktraceData';
import { BacktraceReport } from '../report/BacktraceReport';
Expand Down Expand Up @@ -26,6 +27,29 @@ export interface BacktraceMetricsOptions {
size?: number;
}

export interface BacktraceBreadcrumbsSettings {
/**
* 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.
Expand Down Expand Up @@ -90,6 +114,11 @@ export interface BacktraceConfiguration {
* Metrics such as crash free users and crash free sessions
*/
metrics?: BacktraceMetricsOptions;

/**
* Breadcrumbs settings
*/
breadcrumbs?: BacktraceBreadcrumbsSettings;
/**
* Offline database settings
*/
Expand Down
27 changes: 27 additions & 0 deletions packages/sdk-core/src/modules/breadcrumbs/BacktraceBreadcrumbs.ts
Original file line number Diff line number Diff line change
@@ -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<string, AttributeType>): void;
debug(message: string, attributes?: Record<string, AttributeType>): void;
info(message: string, attributes?: Record<string, AttributeType>): void;
warn(message: string, attributes?: Record<string, AttributeType>): void;
error(message: string, attributes?: Record<string, AttributeType>): void;
log(message: string, level: BreadcrumbLogLevel, attributes?: Record<string, AttributeType>): void;
}
118 changes: 118 additions & 0 deletions packages/sdk-core/src/modules/breadcrumbs/BreadcrumbsManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import {
BacktraceBreadcrumbs,
BreadcrumbLogLevel,
BreadcrumbType,
defaultBreadcrumbsLogLevel,
defaultBreadcurmbType,
} from '.';
import { BacktraceBreadcrumbsSettings } from '../../model/configuration/BacktraceConfiguration';
import { AttributeType } from '../../model/data/BacktraceData';
import { BacktraceReport } from '../../model/report/BacktraceReport';
import { BreadcrumbsSetup } from './BreadcrumbsSetup';
import { BreadcrumbsEventSubscriber } from './events/BreadcrurmbsEventSubscriber';
import { ConsoleEventSubscriber } from './events/ConsoleEventSubscriber';
import { BreadcrumbsStorage } from './storage/BreadcrumbsStorage';
import { InMemoryBreadcrumbsStorage } from './storage/InMemoryBreadcrumbsStorage';

export class BreadcrumbsManager 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 breadcrumbsStorage: BreadcrumbsStorage;

/**
* Determines if the breadcrumb manager is enabled.
*/
private _enabled = true;
private readonly _eventSubscribers: BreadcrumbsEventSubscriber[] = [new ConsoleEventSubscriber()];

constructor(configuration?: BacktraceBreadcrumbsSettings, options?: BreadcrumbsSetup) {
this.breadcrumbsType = configuration?.eventType ?? defaultBreadcurmbType;
this.logLevel = configuration?.logLevel ?? defaultBreadcrumbsLogLevel;
this.breadcrumbsStorage = 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<string, number> {
return {
[this.BREADCRUMB_ATTRIBUTE_NAME]: this.breadcrumbsStorage.lastBreadcrumbId,
};
}

public start() {
for (const subscriber of this._eventSubscribers) {
subscriber.start(this);
}
}

public verbose(message: string, attributes?: Record<string, AttributeType> | undefined): boolean {
return this.log(message, BreadcrumbLogLevel.Verbose, attributes);
}
public debug(message: string, attributes?: Record<string, AttributeType> | undefined): boolean {
return this.log(message, BreadcrumbLogLevel.Debug, attributes);
}
public info(message: string, attributes?: Record<string, AttributeType> | undefined): boolean {
return this.log(message, BreadcrumbLogLevel.Info, attributes);
}
public warn(message: string, attributes?: Record<string, AttributeType> | undefined): boolean {
return this.log(message, BreadcrumbLogLevel.Warning, attributes);
}
public error(message: string, attributes?: Record<string, AttributeType> | undefined): boolean {
return this.log(message, BreadcrumbLogLevel.Error, attributes);
}
public log(
message: string,
level: BreadcrumbLogLevel,
attributes?: Record<string, AttributeType> | undefined,
): boolean {
return this.addBreadcrumb(message, level, BreadcrumbType.Manual, attributes);
}

public logReport(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<string, AttributeType> | undefined,
): boolean {
if (!this._enabled) {
return false;
}
if ((this.logLevel & level) !== level) {
return false;
}

if ((this.breadcrumbsType & type) !== type) {
return false;
}

this.breadcrumbsStorage.add(message, level, type, attributes);
return true;
}
}
7 changes: 7 additions & 0 deletions packages/sdk-core/src/modules/breadcrumbs/BreadcrumbsSetup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { BreadcrumbsEventSubscriber } from './events/BreadcrurmbsEventSubscriber';
import { BreadcrumbsStorage } from './storage/BreadcrumbsStorage';

export interface BreadcrumbsSetup {
storage?: BreadcrumbsStorage;
subscribers?: BreadcrumbsEventSubscriber[];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { BreadcrumbsManager } from '../BreadcrumbsManager';

export interface BreadcrumbsEventSubscriber {
/**
* Set up breadcrumbs listener
* @param breadcrumbsManager breadcrumbs manager
*/
start(breadcrumbsManager: BreadcrumbsManager): void;

/**
* Dispose all breadcrumbs events
*/
dispose(): void;
}
Loading