Skip to content

Commit bb038a4

Browse files
authored
feat(mongodb-runner): pass log events through to caller COMPASS-10017 (#593)
1 parent 52d9d81 commit bb038a4

File tree

4 files changed

+93
-9
lines changed

4 files changed

+93
-9
lines changed
Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1-
export { MongoServer, MongoServerOptions } from './mongoserver';
2-
3-
export { MongoCluster, MongoClusterOptions } from './mongocluster';
1+
export {
2+
MongoServer,
3+
type MongoServerEvents,
4+
MongoServerOptions,
5+
} from './mongoserver';
6+
export {
7+
MongoCluster,
8+
type MongoClusterEvents,
9+
MongoClusterOptions,
10+
} from './mongocluster';
11+
export type { LogEntry } from './mongologreader';
412
export type { ConnectionString } from 'mongodb-connection-string-url';
513
export { prune, start, stop, exec, instances } from './runner-helpers';

packages/mongodb-runner/src/mongocluster.spec.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import path from 'path';
55
import os from 'os';
66
import createDebug from 'debug';
77
import sinon from 'sinon';
8+
import type { LogEntry } from './mongologreader';
89

910
if (process.env.CI) {
1011
createDebug.enable('mongodb-runner,mongodb-downloader');
@@ -298,4 +299,48 @@ describe('MongoCluster', function () {
298299
expect(doc?._id).to.be.a('string');
299300
await cluster.close();
300301
});
302+
303+
it('can let callers listen for server log events', async function () {
304+
cluster = await MongoCluster.start({
305+
version: '6.x',
306+
topology: 'replset',
307+
tmpDir,
308+
secondaries: 1,
309+
});
310+
const logs: LogEntry[] = [];
311+
cluster.on('mongoLog', (uuid, entry) => logs.push(entry));
312+
await cluster.withClient(async (client) => {
313+
const coll = await client.db('test').createCollection<any>('test', {
314+
validationAction: 'warn',
315+
validationLevel: 'strict',
316+
validator: {
317+
$jsonSchema: {
318+
bsonType: 'object',
319+
required: ['phone'],
320+
properties: {
321+
phone: {
322+
bsonType: 'string',
323+
},
324+
},
325+
},
326+
},
327+
});
328+
await coll.insertOne({ _id: 42, baddoc: 1 });
329+
});
330+
expect(
331+
logs.find(
332+
(entry) =>
333+
entry.id === 20320 /* create collection */ &&
334+
entry.attr.namespace === 'test.test',
335+
),
336+
).to.exist;
337+
const validatorLogEntry = logs.find(
338+
(entry) => entry.id === 20294 /* fail validation */,
339+
);
340+
expect(validatorLogEntry?.attr.namespace).to.equal('test.test');
341+
expect(validatorLogEntry?.attr.document).to.deep.equal({
342+
_id: 42,
343+
baddoc: 1,
344+
});
345+
});
301346
});

packages/mongodb-runner/src/mongocluster.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { MongoServerOptions } from './mongoserver';
1+
import type { MongoServerEvents, MongoServerOptions } from './mongoserver';
22
import { MongoServer } from './mongoserver';
33
import { ConnectionString } from 'mongodb-connection-string-url';
44
import type { DownloadOptions } from '@mongodb-js/mongodb-downloader';
@@ -7,6 +7,7 @@ import type { MongoClientOptions } from 'mongodb';
77
import { MongoClient } from 'mongodb';
88
import { sleep, range, uuid, debug } from './util';
99
import { OIDCMockProviderProcess } from './oidc';
10+
import { EventEmitter } from 'events';
1011

1112
export interface MongoClusterOptions
1213
extends Pick<
@@ -23,14 +24,34 @@ export interface MongoClusterOptions
2324
oidc?: string;
2425
}
2526

26-
export class MongoCluster {
27+
export type MongoClusterEvents = {
28+
[k in keyof MongoServerEvents]: [serverUUID: string, ...MongoServerEvents[k]];
29+
} & {
30+
newListener: [keyof MongoClusterEvents];
31+
removeListener: [keyof MongoClusterEvents];
32+
};
33+
34+
export class MongoCluster extends EventEmitter<MongoClusterEvents> {
2735
private topology: MongoClusterOptions['topology'] = 'standalone';
2836
private replSetName?: string;
2937
private servers: MongoServer[] = []; // mongod/mongos
3038
private shards: MongoCluster[] = []; // replsets
3139
private oidcMockProviderProcess?: OIDCMockProviderProcess;
3240

3341
private constructor() {
42+
super();
43+
// NB: This will not retroactively add listeners to new server instances.
44+
// This should be fine, as we only pass "fully initialized" clusters to
45+
// callers, with all child clusters and individual servers already in place.
46+
this.on('newListener', (name) => {
47+
if (name === 'newListener' || name === 'removeListener') return;
48+
if (this.listenerCount(name) === 0) {
49+
for (const child of this.servers)
50+
child.on(name, (...args) => this.emit(name, child.id, ...args));
51+
for (const child of this.shards)
52+
child.on(name, (...args) => this.emit(name, ...args));
53+
}
54+
});
3455
/* see .start() */
3556
}
3657

packages/mongodb-runner/src/mongoserver.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { Readable } from 'stream';
1212
import type { Document, MongoClientOptions } from 'mongodb';
1313
import { MongoClient } from 'mongodb';
1414
import path from 'path';
15-
import { once } from 'events';
15+
import { EventEmitter, once } from 'events';
1616
import { uuid, debug, pick, debugVerbose } from './util';
1717

1818
export interface MongoServerOptions {
@@ -33,8 +33,12 @@ interface SerializedServerProperties {
3333
hasInsertedMetadataCollEntry: boolean;
3434
}
3535

36-
export class MongoServer {
37-
private uuid: string = uuid();
36+
export interface MongoServerEvents {
37+
mongoLog: [LogEntry];
38+
}
39+
40+
export class MongoServer extends EventEmitter<MongoServerEvents> {
41+
public uuid: string = uuid();
3842
private buildInfo?: Document;
3943
private childProcess?: ChildProcess;
4044
private pid?: number;
@@ -44,7 +48,12 @@ export class MongoServer {
4448
private startTime = new Date().toISOString();
4549
private hasInsertedMetadataCollEntry = false;
4650

51+
get id(): string {
52+
return this.uuid;
53+
}
54+
4755
private constructor() {
56+
super();
4857
/* see .start() */
4958
}
5059

@@ -212,7 +221,8 @@ export class MongoServer {
212221
const errorLogEntries: LogEntry[] = [];
213222
try {
214223
const logEntryStream = Readable.from(createLogEntryIterator(stdout));
215-
logEntryStream.on('data', (entry) => {
224+
logEntryStream.on('data', (entry: LogEntry) => {
225+
srv.emit('mongoLog', entry);
216226
if (!srv.closing && ['E', 'F'].includes(entry.severity)) {
217227
errorLogEntries.push(entry);
218228
debug('mongodb server output', entry);

0 commit comments

Comments
 (0)