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
107 changes: 106 additions & 1 deletion packages/mongodb-log-writer/src/mongo-log-manager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,48 @@ describe('MongoLogManager', function () {
sinon.restore();
});

it('constructor throws with invalid prefixes', function () {
expect(() => {
new MongoLogManager({
directory,
retentionDays,
prefix: '%asdabs/',
onwarn,
onerror,
});
}).to.throw();

expect(() => {
new MongoLogManager({
directory,
retentionDays,
prefix: '$$$$',
onwarn,
onerror,
});
}).to.throw();

expect(() => {
new MongoLogManager({
directory,
retentionDays,
prefix: 'abc_',
onwarn,
onerror,
});
}).not.to.throw();

expect(() => {
new MongoLogManager({
directory,
retentionDays,
prefix: 'something',
onwarn,
onerror,
});
}).not.to.throw();
});

it('allows creating and writing to log files', async function () {
const manager = new MongoLogManager({
directory,
Expand Down Expand Up @@ -61,6 +103,19 @@ describe('MongoLogManager', function () {
expect(log[0].t.$date).to.be.a('string');
});

it('can take a custom prefix for log files', async function () {
const manager = new MongoLogManager({
directory,
retentionDays,
prefix: 'custom_',
onwarn,
onerror,
});

const writer = await manager.createLogWriter();
expect(writer.logFilePath as string).to.match(/custom_/);
});

it('cleans up old log files when requested', async function () {
retentionDays = 0.000001; // 86.4 ms
const manager = new MongoLogManager({
Expand Down Expand Up @@ -178,7 +233,57 @@ describe('MongoLogManager', function () {
expect(leftoverFiles).deep.equals([faultyFile, ...validFiles.slice(3)]);
});

it('cleans up least recent log files when over a storage limit', async function () {
it('cleanup only applies to files with the prefix, if set', async function () {
const manager = new MongoLogManager({
directory,
retentionDays,
maxLogFileCount: 7,
prefix: 'custom_',
onwarn,
onerror,
});

const paths: string[] = [];
const offset = Math.floor(Date.now() / 1000);

// Create 4 files: 2 with a different prefix and 2 with no prefix
for (let i = 1; i >= 0; i--) {
const withoutPrefix = path.join(
directory,
ObjectId.createFromTime(offset - i).toHexString() + '_log'
);
await fs.writeFile(withoutPrefix, '');
paths.push(withoutPrefix);

const withDifferentPrefix = path.join(
directory,
'different_' +
ObjectId.createFromTime(offset - i).toHexString() +
'_log'
);
await fs.writeFile(withDifferentPrefix, '');
paths.push(withDifferentPrefix);
}

// Create 10 files with the prefix
for (let i = 9; i >= 0; i--) {
const filename = path.join(
directory,
`custom_${ObjectId.createFromTime(offset - i).toHexString()}_log`
);
await fs.writeFile(filename, '');
paths.push(filename);
}

expect(await getFilesState(paths)).to.equal('11111111111111');
await manager.cleanupOldLogFiles();

// The first 4 files without the right prefix should still be there.
// The next (oldest) 3 files with the prefix should be deleted.
expect(await getFilesState(paths)).to.equal('11110001111111');
});

it('cleans up least recent log files when requested with a storage limit', async function () {
const manager = new MongoLogManager({
directory,
retentionDays,
Expand Down
22 changes: 19 additions & 3 deletions packages/mongodb-log-writer/src/mongo-log-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ interface MongoLogOptions {
maxLogFileCount?: number;
/** The maximal size of log files which are kept. */
retentionGB?: number;
/** Prefix to use for the log files */
prefix?: string;
/** A handler for errors related to a specific filesystem path. */
onerror: (err: Error, path: string) => unknown | Promise<void>;
/** A handler for warnings related to a specific filesystem path. */
Expand All @@ -34,6 +36,13 @@ export class MongoLogManager {
_options: MongoLogOptions;

constructor(options: MongoLogOptions) {
if (options.prefix) {
if (!/^[a-z0-9_]+$/i.test(options.prefix)) {
throw new Error(
'Prefix must only contain letters, numbers, and underscores'
);
}
}
this._options = options;
}

Expand All @@ -48,6 +57,10 @@ export class MongoLogManager {
}
}

private get prefix() {
return this._options.prefix ?? '';
}

/** Clean up log files older than `retentionDays`. */
async cleanupOldLogFiles(maxDurationMs = 5_000): Promise<void> {
const dir = this._options.directory;
Expand Down Expand Up @@ -80,8 +93,11 @@ export class MongoLogManager {
if (Date.now() - deletionStartTimestamp > maxDurationMs) break;

if (!dirent.isFile()) continue;
const { id } =
/^(?<id>[a-f0-9]{24})_log(\.gz)?$/i.exec(dirent.name)?.groups ?? {};
const logRegExp = new RegExp(
`^${this.prefix}(?<id>[a-f0-9]{24})_log(\\.gz)?$`,
'i'
);
const { id } = logRegExp.exec(dirent.name)?.groups ?? {};
if (!id) continue;

const fileTimestamp = +new ObjectId(id).getTimestamp();
Expand Down Expand Up @@ -143,7 +159,7 @@ export class MongoLogManager {
const doGzip = !!this._options.gzip;
const logFilePath = path.join(
this._options.directory,
`${logId}_log${doGzip ? '.gz' : ''}`
`${this.prefix}${logId}_log${doGzip ? '.gz' : ''}`
);

let originalTarget: Writable;
Expand Down
Loading