Skip to content

Commit 50e0889

Browse files
committed
Merge remote-tracking branch 'origin/main' into MONGOSH-1914-parallel-log-cleanup-issue
# Conflicts: # packages/mongodb-log-writer/src/mongo-log-manager.ts
2 parents 478527a + 65bf3a1 commit 50e0889

File tree

5 files changed

+149
-28
lines changed

5 files changed

+149
-28
lines changed

package-lock.json

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/devtools-connect/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@mongodb-js/devtools-connect",
3-
"version": "3.5.0",
3+
"version": "3.6.0",
44
"description": "A connection establishment utility for MongoDB developer tools",
55
"homepage": "https://github.com/mongodb-js/devtools-shared/tree/main/packages/devtools-connect",
66
"repository": {
@@ -56,7 +56,7 @@
5656
"peerDependencies": {
5757
"@mongodb-js/oidc-plugin": "^1.1.0",
5858
"mongodb": "^6.9.0",
59-
"mongodb-log-writer": "^2.2.0"
59+
"mongodb-log-writer": "^2.3.0"
6060
},
6161
"devDependencies": {
6262
"@mongodb-js/oidc-plugin": "^1.1.0",
@@ -76,7 +76,7 @@
7676
"gen-esm-wrapper": "^1.1.0",
7777
"mocha": "^8.4.0",
7878
"mongodb": "^6.9.0",
79-
"mongodb-log-writer": "^2.2.0",
79+
"mongodb-log-writer": "^2.3.0",
8080
"nyc": "^15.1.0",
8181
"os-dns-native": "^1.2.0",
8282
"resolve-mongodb-srv": "^1.1.1",

packages/mongodb-log-writer/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "mongodb-log-writer",
33
"description": "A library for writing MongoDB logv2 messages",
4-
"version": "2.2.0",
4+
"version": "2.3.0",
55
"author": {
66
"name": "MongoDB Inc",
77
"email": "[email protected]"

packages/mongodb-log-writer/src/mongo-log-manager.spec.ts

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,48 @@ describe('MongoLogManager', function () {
3232
sinon.restore();
3333
});
3434

35+
it('constructor throws with invalid prefixes', function () {
36+
expect(() => {
37+
new MongoLogManager({
38+
directory,
39+
retentionDays,
40+
prefix: '%asdabs/',
41+
onwarn,
42+
onerror,
43+
});
44+
}).to.throw();
45+
46+
expect(() => {
47+
new MongoLogManager({
48+
directory,
49+
retentionDays,
50+
prefix: '$$$$',
51+
onwarn,
52+
onerror,
53+
});
54+
}).to.throw();
55+
56+
expect(() => {
57+
new MongoLogManager({
58+
directory,
59+
retentionDays,
60+
prefix: 'abc_',
61+
onwarn,
62+
onerror,
63+
});
64+
}).not.to.throw();
65+
66+
expect(() => {
67+
new MongoLogManager({
68+
directory,
69+
retentionDays,
70+
prefix: 'something',
71+
onwarn,
72+
onerror,
73+
});
74+
}).not.to.throw();
75+
});
76+
3577
it('allows creating and writing to log files', async function () {
3678
const manager = new MongoLogManager({
3779
directory,
@@ -62,6 +104,19 @@ describe('MongoLogManager', function () {
62104
expect(log[0].t.$date).to.be.a('string');
63105
});
64106

107+
it('can take a custom prefix for log files', async function () {
108+
const manager = new MongoLogManager({
109+
directory,
110+
retentionDays,
111+
prefix: 'custom_',
112+
onwarn,
113+
onerror,
114+
});
115+
116+
const writer = await manager.createLogWriter();
117+
expect(writer.logFilePath as string).to.match(/custom_/);
118+
});
119+
65120
it('cleans up old log files when requested', async function () {
66121
retentionDays = 0.000001; // 86.4 ms
67122
const manager = new MongoLogManager({
@@ -179,7 +234,57 @@ describe('MongoLogManager', function () {
179234
expect(leftoverFiles).deep.equals([faultyFile, ...validFiles.slice(3)]);
180235
});
181236

182-
it('cleans up least recent log files when over a storage limit', async function () {
237+
it('cleanup only applies to files with the prefix, if set', async function () {
238+
const manager = new MongoLogManager({
239+
directory,
240+
retentionDays,
241+
maxLogFileCount: 7,
242+
prefix: 'custom_',
243+
onwarn,
244+
onerror,
245+
});
246+
247+
const paths: string[] = [];
248+
const offset = Math.floor(Date.now() / 1000);
249+
250+
// Create 4 files: 2 with a different prefix and 2 with no prefix
251+
for (let i = 1; i >= 0; i--) {
252+
const withoutPrefix = path.join(
253+
directory,
254+
ObjectId.createFromTime(offset - i).toHexString() + '_log'
255+
);
256+
await fs.writeFile(withoutPrefix, '');
257+
paths.push(withoutPrefix);
258+
259+
const withDifferentPrefix = path.join(
260+
directory,
261+
'different_' +
262+
ObjectId.createFromTime(offset - i).toHexString() +
263+
'_log'
264+
);
265+
await fs.writeFile(withDifferentPrefix, '');
266+
paths.push(withDifferentPrefix);
267+
}
268+
269+
// Create 10 files with the prefix
270+
for (let i = 9; i >= 0; i--) {
271+
const filename = path.join(
272+
directory,
273+
`custom_${ObjectId.createFromTime(offset - i).toHexString()}_log`
274+
);
275+
await fs.writeFile(filename, '');
276+
paths.push(filename);
277+
}
278+
279+
expect(await getFilesState(paths)).to.equal('11111111111111');
280+
await manager.cleanupOldLogFiles();
281+
282+
// The first 4 files without the right prefix should still be there.
283+
// The next (oldest) 3 files with the prefix should be deleted.
284+
expect(await getFilesState(paths)).to.equal('11110001111111');
285+
});
286+
287+
it('cleans up least recent log files when requested with a storage limit', async function () {
183288
const manager = new MongoLogManager({
184289
directory,
185290
retentionDays,

packages/mongodb-log-writer/src/mongo-log-manager.ts

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ interface MongoLogOptions {
1919
maxLogFileCount?: number;
2020
/** The maximal size of log files which are kept. */
2121
retentionGB?: number;
22+
/** Prefix to use for the log files */
23+
prefix?: string;
2224
/** A handler for errors related to a specific filesystem path. */
2325
onerror: (err: Error, path: string) => unknown | Promise<void>;
2426
/** A handler for warnings related to a specific filesystem path. */
@@ -34,6 +36,13 @@ export class MongoLogManager {
3436
_options: MongoLogOptions;
3537

3638
constructor(options: MongoLogOptions) {
39+
if (options.prefix) {
40+
if (!/^[a-z0-9_]+$/i.test(options.prefix)) {
41+
throw new Error(
42+
'Prefix must only contain letters, numbers, and underscores'
43+
);
44+
}
45+
}
3746
this._options = options;
3847
}
3948

@@ -48,6 +57,10 @@ export class MongoLogManager {
4857
}
4958
}
5059

60+
private get prefix() {
61+
return this._options.prefix ?? '';
62+
}
63+
5164
/** Clean up log files older than `retentionDays`. */
5265
async cleanupOldLogFiles(maxDurationMs = 5_000, remainingRetries = 1): Promise<void> {
5366
const dir = this._options.directory;
@@ -81,8 +94,11 @@ export class MongoLogManager {
8194
if (Date.now() - deletionStartTimestamp > maxDurationMs) break;
8295

8396
if (!dirent.isFile()) continue;
84-
const { id } =
85-
/^(?<id>[a-f0-9]{24})_log(\.gz)?$/i.exec(dirent.name)?.groups ?? {};
97+
const logRegExp = new RegExp(
98+
`^${this.prefix}(?<id>[a-f0-9]{24})_log(\\.gz)?$`,
99+
'i'
100+
);
101+
const { id } = logRegExp.exec(dirent.name)?.groups ?? {};
86102
if (!id) continue;
87103

88104
const fileTimestamp = +new ObjectId(id).getTimestamp();
@@ -121,21 +137,6 @@ export class MongoLogManager {
121137
usedStorageSize -= toDelete.fileSize ?? 0;
122138
}
123139
}
124-
125-
if (this._options.retentionGB) {
126-
const storageSizeLimit = this._options.retentionGB * 1024 * 1024 * 1024;
127-
128-
for (const file of leastRecentFileHeap) {
129-
if (Date.now() - deletionStartTimestamp > maxDurationMs) break;
130-
131-
if (usedStorageSize <= storageSizeLimit) break;
132-
133-
if (!file.fileSize) continue;
134-
135-
await this.deleteFile(file.fullPath);
136-
usedStorageSize -= file.fileSize;
137-
}
138-
}
139140
} catch (statErr: any) {
140141
// Multiple processes may attempt to clean up log files in parallel.
141142
// A situation can arise where one process tries to read a file
@@ -146,6 +147,21 @@ export class MongoLogManager {
146147
await this.cleanupOldLogFiles(maxDurationMs - Date.now() - deletionStartTimestamp, remainingRetries - 1);
147148
}
148149
}
150+
151+
if (this._options.retentionGB) {
152+
const storageSizeLimit = this._options.retentionGB * 1024 * 1024 * 1024;
153+
154+
for (const file of leastRecentFileHeap) {
155+
if (Date.now() - deletionStartTimestamp > maxDurationMs) break;
156+
157+
if (usedStorageSize <= storageSizeLimit) break;
158+
159+
if (!file.fileSize) continue;
160+
161+
await this.deleteFile(file.fullPath);
162+
usedStorageSize -= file.fileSize;
163+
}
164+
}
149165
}
150166

151167
/** Create a MongoLogWriter stream for a new log file. */
@@ -154,7 +170,7 @@ export class MongoLogManager {
154170
const doGzip = !!this._options.gzip;
155171
const logFilePath = path.join(
156172
this._options.directory,
157-
`${logId}_log${doGzip ? '.gz' : ''}`
173+
`${this.prefix}${logId}_log${doGzip ? '.gz' : ''}`
158174
);
159175

160176
let originalTarget: Writable;

0 commit comments

Comments
 (0)