Skip to content

Commit bd1ca01

Browse files
committed
node: add BacktraceFileAttachmentFactory [INT-355]
1 parent ccbb394 commit bd1ca01

8 files changed

+82
-53
lines changed

packages/node/src/BacktraceClient.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ import nodeFs from 'fs';
1111
import { BacktraceConfiguration, BacktraceSetupConfiguration } from './BacktraceConfiguration.js';
1212
import { BacktraceNodeRequestHandler } from './BacktraceNodeRequestHandler.js';
1313
import { AGENT } from './agentDefinition.js';
14+
import {
15+
BacktraceFileAttachmentFactory,
16+
NodeFsBacktraceFileAttachmentFactory,
17+
} from './attachment/BacktraceFileAttachment.js';
1418
import { FileAttachmentsManager } from './attachment/FileAttachmentsManager.js';
1519
import { transformAttachment } from './attachment/transformAttachments.js';
1620
import { FileBreadcrumbsStorage } from './breadcrumbs/FileBreadcrumbsStorage.js';
@@ -37,6 +41,7 @@ export class BacktraceClient extends BacktraceCoreClient<BacktraceConfiguration>
3741
private _listeners: Record<string, NodeJS.UnhandledRejectionListener | NodeJS.UncaughtExceptionListener> = {};
3842

3943
protected readonly storageFactory: BacktraceStorageModuleFactory;
44+
protected readonly fileAttachmentFactory: BacktraceFileAttachmentFactory;
4045
protected readonly fs: typeof nodeFs;
4146

4247
protected get databaseNodeFsStorage() {
@@ -46,6 +51,7 @@ export class BacktraceClient extends BacktraceCoreClient<BacktraceConfiguration>
4651
constructor(clientSetup: BacktraceNodeClientSetup) {
4752
const storageFactory = clientSetup.storageFactory ?? new NodeFsBacktraceStorageModuleFactory();
4853
const fs = clientSetup.fs ?? nodeFs;
54+
const fileAttachmentFactory = new NodeFsBacktraceFileAttachmentFactory(fs);
4955
const storage =
5056
clientSetup.database?.storage ??
5157
(clientSetup.options.database?.enable
@@ -72,30 +78,33 @@ export class BacktraceClient extends BacktraceCoreClient<BacktraceConfiguration>
7278
...clientSetup.database?.recordSenders?.(submission),
7379
}),
7480
recordSerializers: {
75-
report: new ReportBacktraceDatabaseRecordWithAttachmentsSerializer(storage),
76-
attachment: new AttachmentBacktraceDatabaseRecordSerializer(fs),
81+
report: new ReportBacktraceDatabaseRecordWithAttachmentsSerializer(fileAttachmentFactory),
82+
attachment: new AttachmentBacktraceDatabaseRecordSerializer(fileAttachmentFactory),
7783
...clientSetup.database?.recordSerializers,
7884
},
7985
}
8086
: undefined,
8187
...clientSetup,
8288
options: {
8389
...clientSetup.options,
84-
attachments: clientSetup.options.attachments?.map(transformAttachment),
90+
attachments: clientSetup.options.attachments?.map(transformAttachment(fileAttachmentFactory)),
8591
},
8692
});
8793

8894
this.storageFactory = storageFactory;
95+
this.fileAttachmentFactory = fileAttachmentFactory;
8996
this.fs = fs;
9097

9198
const breadcrumbsManager = this.modules.get(BreadcrumbsManager);
9299
if (breadcrumbsManager && this.sessionFiles && storage) {
93-
breadcrumbsManager.setStorage(FileBreadcrumbsStorage.factory(this.sessionFiles, storage));
100+
breadcrumbsManager.setStorage(
101+
FileBreadcrumbsStorage.factory(this.sessionFiles, storage, this.fileAttachmentFactory),
102+
);
94103
}
95104

96105
if (this.sessionFiles && storage && clientSetup.options.database?.captureNativeCrashes) {
97106
this.addModule(FileAttributeManager, FileAttributeManager.create(storage));
98-
this.addModule(FileAttachmentsManager, FileAttachmentsManager.create(storage));
107+
this.addModule(FileAttachmentsManager, FileAttachmentsManager.create(storage, fileAttachmentFactory));
99108
}
100109
}
101110

@@ -346,12 +355,18 @@ export class BacktraceClient extends BacktraceCoreClient<BacktraceConfiguration>
346355
for (const [recordName, report, session] of reports) {
347356
try {
348357
if (session) {
349-
report.attachments.push(...FileBreadcrumbsStorage.getSessionAttachments(session, storage));
358+
report.attachments.push(
359+
...FileBreadcrumbsStorage.getSessionAttachments(session, this.fileAttachmentFactory),
360+
);
350361

351362
const fileAttributes = FileAttributeManager.createFromSession(session, storage);
352363
Object.assign(report.attributes, await fileAttributes.get());
353364

354-
const fileAttachments = FileAttachmentsManager.createFromSession(session, storage);
365+
const fileAttachments = FileAttachmentsManager.createFromSession(
366+
session,
367+
storage,
368+
this.fileAttachmentFactory,
369+
);
355370
report.attachments.push(...(await fileAttachments.get()));
356371

357372
report.attributes['application.session'] = session.sessionId;
Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,36 @@
1-
import { BacktraceAttachment, BacktraceSyncStorage } from '@backtrace/sdk-core';
2-
import fs from 'fs';
1+
import { BacktraceAttachment } from '@backtrace/sdk-core';
2+
import nodeFs from 'fs';
33
import path from 'path';
44
import { Readable } from 'stream';
5-
import { BacktraceStreamStorage } from '../storage/BacktraceStorage.js';
65

76
export class BacktraceFileAttachment implements BacktraceAttachment<Readable> {
87
public readonly name: string;
98

109
constructor(
1110
public readonly filePath: string,
1211
name?: string,
13-
private readonly _fs: typeof fs | (BacktraceSyncStorage & BacktraceStreamStorage) = fs,
12+
private readonly _fs: typeof nodeFs = nodeFs,
1413
) {
1514
this.name = name ?? path.basename(this.filePath);
1615
}
1716

1817
public get(): Readable | undefined {
19-
if ('hasSync' in this._fs) {
20-
if (!this._fs.hasSync(this.filePath)) {
21-
return undefined;
22-
}
23-
} else {
24-
if (!this._fs.existsSync(this.filePath)) {
25-
return undefined;
26-
}
18+
if (!this._fs.existsSync(this.filePath)) {
19+
return undefined;
2720
}
2821

2922
return this._fs.createReadStream(this.filePath);
3023
}
3124
}
25+
26+
export interface BacktraceFileAttachmentFactory {
27+
create(filePath: string, name?: string): BacktraceFileAttachment;
28+
}
29+
30+
export class NodeFsBacktraceFileAttachmentFactory implements BacktraceFileAttachmentFactory {
31+
constructor(private readonly _fs: typeof nodeFs = nodeFs) {}
32+
33+
public create(filePath: string, name?: string): BacktraceFileAttachment {
34+
return new BacktraceFileAttachment(filePath, name, this._fs);
35+
}
36+
}

packages/node/src/attachment/FileAttachmentsManager.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
BacktraceStorage,
66
SessionFiles,
77
} from '@backtrace/sdk-core';
8-
import { BacktraceFileAttachment } from './BacktraceFileAttachment.js';
8+
import { BacktraceFileAttachment, BacktraceFileAttachmentFactory } from './BacktraceFileAttachment.js';
99

1010
const ATTACHMENT_FILE_NAME = 'bt-attachments';
1111

@@ -16,16 +16,21 @@ export class FileAttachmentsManager implements BacktraceModule {
1616

1717
constructor(
1818
private readonly _storage: BacktraceStorage,
19+
private readonly _fileAttachmentFactory: BacktraceFileAttachmentFactory,
1920
private _fileName?: string,
2021
) {}
2122

22-
public static create(storage: BacktraceStorage) {
23-
return new FileAttachmentsManager(storage);
23+
public static create(storage: BacktraceStorage, fileAttachmentFactory: BacktraceFileAttachmentFactory) {
24+
return new FileAttachmentsManager(storage, fileAttachmentFactory);
2425
}
2526

26-
public static createFromSession(sessionFiles: SessionFiles, fileSystem: BacktraceStorage) {
27+
public static createFromSession(
28+
sessionFiles: SessionFiles,
29+
fileSystem: BacktraceStorage,
30+
fileAttachmentFactory: BacktraceFileAttachmentFactory,
31+
) {
2732
const fileName = sessionFiles.getFileName(ATTACHMENT_FILE_NAME);
28-
return new FileAttachmentsManager(fileSystem, fileName);
33+
return new FileAttachmentsManager(fileSystem, fileAttachmentFactory, fileName);
2934
}
3035

3136
public initialize(): void {
@@ -61,7 +66,7 @@ export class FileAttachmentsManager implements BacktraceModule {
6166
return [];
6267
}
6368
const attachments = JSON.parse(content) as SavedAttachment[];
64-
return attachments.map(([path, name]) => new BacktraceFileAttachment(path, name));
69+
return attachments.map(([path, name]) => this._fileAttachmentFactory.create(path, name));
6570
} catch {
6671
return [];
6772
}
Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import { BacktraceAttachment } from '@backtrace/sdk-core';
22
import { Readable } from 'stream';
33
import { BacktraceSetupConfiguration } from '../BacktraceConfiguration.js';
4-
import { BacktraceFileAttachment } from './BacktraceFileAttachment.js';
4+
import { BacktraceFileAttachmentFactory } from './BacktraceFileAttachment.js';
55

66
/**
77
* Transform a client attachment into the attachment model.
88
*/
9-
export function transformAttachment(
10-
attachment: NonNullable<BacktraceSetupConfiguration['attachments']>[number] | BacktraceAttachment,
11-
): BacktraceAttachment<Buffer | Readable | string | Uint8Array> {
12-
return typeof attachment === 'string'
13-
? new BacktraceFileAttachment(attachment)
14-
: (attachment as BacktraceAttachment<Buffer | Readable | string | Uint8Array>);
15-
}
9+
export const transformAttachment =
10+
(fileAttachmentFactory: BacktraceFileAttachmentFactory) =>
11+
(
12+
attachment: NonNullable<BacktraceSetupConfiguration['attachments']>[number] | BacktraceAttachment,
13+
): BacktraceAttachment<Buffer | Readable | string | Uint8Array> => {
14+
return typeof attachment === 'string'
15+
? fileAttachmentFactory.create(attachment)
16+
: (attachment as BacktraceAttachment<Buffer | Readable | string | Uint8Array>);
17+
};

packages/node/src/breadcrumbs/FileBreadcrumbsStorage.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
} from '@backtrace/sdk-core';
1717
import path from 'path';
1818
import { Readable, Writable } from 'stream';
19-
import { BacktraceFileAttachment } from '../attachment/index.js';
19+
import { BacktraceFileAttachmentFactory } from '../attachment/index.js';
2020
import { BacktraceStreamStorage } from '../storage/BacktraceStorage.js';
2121
import { chunkifier, ChunkSplitterFactory } from '../streams/chunkifier.js';
2222
import { combinedChunkSplitter } from '../streams/combinedChunkSplitter.js';
@@ -39,6 +39,7 @@ export class FileBreadcrumbsStorage implements BreadcrumbsStorage {
3939
constructor(
4040
session: SessionFiles,
4141
private readonly _storage: BacktraceStorage & BacktraceSyncStorage & BacktraceStreamStorage,
42+
private readonly _fileAttachmentFactory: BacktraceFileAttachmentFactory,
4243
private readonly _limits: BreadcrumbsStorageLimits,
4344
) {
4445
const splitters: ChunkSplitterFactory[] = [];
@@ -73,28 +74,26 @@ export class FileBreadcrumbsStorage implements BreadcrumbsStorage {
7374
});
7475
}
7576

76-
public static getSessionAttachments(
77-
session: SessionFiles,
78-
storage?: BacktraceStorage & BacktraceSyncStorage & BacktraceStreamStorage,
79-
) {
77+
public static getSessionAttachments(session: SessionFiles, fileAttachmentFactory: BacktraceFileAttachmentFactory) {
8078
const files = session
8179
.getSessionFiles()
8280
.filter((f) => path.basename(f).startsWith(FILE_PREFIX))
8381
.slice(0, 2);
8482

85-
return files.map((file) => new BacktraceFileAttachment(file, path.basename(file), storage));
83+
return files.map((file) => fileAttachmentFactory.create(file, path.basename(file)));
8684
}
8785

8886
public static factory(
8987
session: SessionFiles,
9088
storage: BacktraceStorage & BacktraceSyncStorage & BacktraceStreamStorage,
89+
fileAttachmentFactory: BacktraceFileAttachmentFactory,
9190
): BreadcrumbsStorageFactory {
92-
return ({ limits }) => new FileBreadcrumbsStorage(session, storage, limits);
91+
return ({ limits }) => new FileBreadcrumbsStorage(session, storage, fileAttachmentFactory, limits);
9392
}
9493

9594
public getAttachments(): BacktraceAttachment<Readable>[] {
9695
const files = [...this._sink.files].map((f) => f.path.toString('utf-8'));
97-
return files.map((f) => new BacktraceFileAttachment(f, path.basename(f), this._storage));
96+
return files.map((f) => this._fileAttachmentFactory.create(f, path.basename(f)));
9897
}
9998

10099
public getAttachmentProviders(): BacktraceAttachmentProvider[] {

packages/node/src/builder/BacktraceClientBuilder.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { BacktraceCoreClientBuilder } from '@backtrace/sdk-core';
2-
import { transformAttachment } from '../attachment/transformAttachments.js';
32
import {
43
ApplicationInformationAttributeProvider,
54
LinuxProcessStatusAttributeProvider,
@@ -9,18 +8,22 @@ import {
98
ProcessStatusAttributeProvider,
109
} from '../attributes/index.js';
1110
import { BacktraceClient } from '../BacktraceClient.js';
11+
import { BacktraceSetupConfiguration } from '../BacktraceConfiguration.js';
1212
import { BacktraceStorageModuleFactory } from '../storage/BacktraceStorageModuleFactory.js';
1313
import { BacktraceClientSetup, BacktraceNodeClientSetup } from './BacktraceClientSetup.js';
1414

1515
export class BacktraceClientBuilder extends BacktraceCoreClientBuilder<BacktraceClientSetup> {
16+
private attachments: BacktraceSetupConfiguration['attachments'];
1617
private storageFactory?: BacktraceStorageModuleFactory;
1718

1819
constructor(clientSetup: BacktraceNodeClientSetup) {
1920
super({
2021
...clientSetup,
21-
options: { ...clientSetup.options, attachments: clientSetup.options.attachments?.map(transformAttachment) },
22+
options: { ...clientSetup.options, attachments: [] },
2223
});
2324

25+
this.attachments = clientSetup.options.attachments;
26+
2427
this.addAttributeProvider(new ApplicationInformationAttributeProvider());
2528
this.addAttributeProvider(new ProcessStatusAttributeProvider());
2629
this.addAttributeProvider(new MachineAttributeProvider());
@@ -38,6 +41,10 @@ export class BacktraceClientBuilder extends BacktraceCoreClientBuilder<Backtrace
3841
const instance = new BacktraceClient({
3942
...this.clientSetup,
4043
storageFactory: this.storageFactory,
44+
options: {
45+
...this.clientSetup.options,
46+
attachments: this.attachments,
47+
},
4148
} as BacktraceNodeClientSetup);
4249
instance.initialize();
4350
return instance;

packages/node/src/database/AttachmentBacktraceDatabaseRecord.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ import {
99
} from '@backtrace/sdk-core';
1010
import { BacktraceDatabaseRecordSender } from '@backtrace/sdk-core/lib/modules/database/BacktraceDatabaseRecordSender.js';
1111
import { BacktraceDatabaseRecordSerializer } from '@backtrace/sdk-core/lib/modules/database/BacktraceDatabaseRecordSerializer.js';
12-
import nodeFs from 'fs';
13-
import { BacktraceFileAttachment } from '../attachment/BacktraceFileAttachment.js';
12+
import { BacktraceFileAttachmentFactory } from '../attachment/BacktraceFileAttachment.js';
1413
import { isFileAttachment } from '../attachment/isFileAttachment.js';
1514

1615
export interface AttachmentBacktraceDatabaseRecord extends BacktraceDatabaseRecord<'attachment'> {
@@ -24,7 +23,7 @@ export class AttachmentBacktraceDatabaseRecordSerializer
2423
{
2524
public readonly type = 'attachment';
2625

27-
constructor(private readonly _fs: typeof nodeFs) {}
26+
constructor(private readonly _fileAttachmentFactory: BacktraceFileAttachmentFactory) {}
2827

2928
public save(record: AttachmentBacktraceDatabaseRecord): string | undefined {
3029
if (!isFileAttachment(record.attachment)) {
@@ -46,10 +45,9 @@ export class AttachmentBacktraceDatabaseRecordSerializer
4645
return undefined;
4746
}
4847

49-
const attachment = new BacktraceFileAttachment(
48+
const attachment = this._fileAttachmentFactory.create(
5049
attachmentRecord.attachment.filePath,
5150
attachmentRecord.attachment.name,
52-
this._fs,
5351
);
5452

5553
return {

packages/node/src/database/ReportBacktraceDatabaseRecordWithAttachments.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,15 @@ import {
55
BacktraceReportSubmission,
66
BacktraceReportSubmissionResult,
77
BacktraceSubmitResponse,
8-
BacktraceSyncStorage,
98
DefaultReportBacktraceDatabaseRecordFactory,
109
jsonEscaper,
1110
ReportBacktraceDatabaseRecord,
1211
ReportBacktraceDatabaseRecordFactory,
1312
} from '@backtrace/sdk-core';
1413
import { BacktraceDatabaseRecordSender } from '@backtrace/sdk-core/lib/modules/database/BacktraceDatabaseRecordSender.js';
1514
import { BacktraceDatabaseRecordSerializer } from '@backtrace/sdk-core/lib/modules/database/BacktraceDatabaseRecordSerializer.js';
16-
import { BacktraceFileAttachment } from '../attachment/BacktraceFileAttachment.js';
15+
import { BacktraceFileAttachment, BacktraceFileAttachmentFactory } from '../attachment/BacktraceFileAttachment.js';
1716
import { isFileAttachment } from '../attachment/isFileAttachment.js';
18-
import { BacktraceStreamStorage } from '../storage/BacktraceStorage.js';
1917

2018
export interface ReportBacktraceDatabaseRecordWithAttachments extends ReportBacktraceDatabaseRecord {
2119
readonly attachments: BacktraceAttachment[];
@@ -26,7 +24,7 @@ export class ReportBacktraceDatabaseRecordWithAttachmentsSerializer
2624
{
2725
public readonly type = 'report';
2826

29-
constructor(private readonly _storage: BacktraceSyncStorage & BacktraceStreamStorage) {}
27+
constructor(private readonly _fileAttachmentFactory: BacktraceFileAttachmentFactory) {}
3028

3129
public save(record: ReportBacktraceDatabaseRecordWithAttachments): string {
3230
return JSON.stringify(
@@ -51,7 +49,7 @@ export class ReportBacktraceDatabaseRecordWithAttachmentsSerializer
5149
...reportRecord,
5250
attachments: reportRecord.attachments
5351
.filter(isFileAttachment)
54-
.map((a) => new BacktraceFileAttachment(a.filePath, a.name, this._storage)),
52+
.map((a) => this._fileAttachmentFactory.create(a.filePath, a.name)),
5553
};
5654
}
5755

0 commit comments

Comments
 (0)