Skip to content

Commit 24894f3

Browse files
committed
feat(cli-repl): add option to disable logging MONGOSH-1988
Some tests still need to be fixed
1 parent e85c465 commit 24894f3

File tree

6 files changed

+382
-169
lines changed

6 files changed

+382
-169
lines changed

packages/cli-repl/src/cli-repl.spec.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import { CliRepl } from './cli-repl';
3131
import { CliReplErrors } from './error-codes';
3232
import type { DevtoolsConnectOptions } from '@mongosh/service-provider-node-driver';
3333
import type { AddressInfo } from 'net';
34+
import sinon from 'sinon';
35+
import type { CliUserConfig } from '@mongosh/types';
3436
const { EJSON } = bson;
3537

3638
const delay = promisify(setTimeout);
@@ -297,7 +299,8 @@ describe('CliRepl', function () {
297299
'oidcTrustedEndpoints',
298300
'browser',
299301
'updateURL',
300-
]);
302+
'disableLogging',
303+
] satisfies (keyof CliUserConfig)[]);
301304
});
302305

303306
it('fails when trying to overwrite mongosh-owned config settings', async function () {
@@ -1310,6 +1313,34 @@ describe('CliRepl', function () {
13101313
hasDatabaseNames: true,
13111314
});
13121315

1316+
context('logging configuration', function () {
1317+
afterEach(function () {
1318+
sinon.restore();
1319+
});
1320+
1321+
it('logging occurs when it is not disabled', async function () {
1322+
const emitSpy = sinon.spy(cliRepl.bus, 'emit');
1323+
1324+
await cliRepl.start(await testServer.connectionString(), {});
1325+
1326+
expect(cliRepl.getConfig('disableLogging')).is.false;
1327+
1328+
expect(emitSpy).calledWith('mongosh:log-initialized');
1329+
});
1330+
1331+
it('uses the disable logging setting', async function () {
1332+
const emitSpy = sinon.spy(cliRepl.bus, 'emit');
1333+
cliRepl.config.disableLogging = true;
1334+
1335+
await cliRepl.start(await testServer.connectionString(), {});
1336+
1337+
expect(cliRepl.getConfig('disableLogging')).is.true;
1338+
1339+
expect(emitSpy).called;
1340+
expect(emitSpy).not.calledWith('mongosh:log-initialized');
1341+
});
1342+
});
1343+
13131344
context('analytics integration', function () {
13141345
context('with network connectivity', function () {
13151346
let srv: http.Server;
@@ -1333,6 +1364,7 @@ describe('CliRepl', function () {
13331364
.on('data', (chunk) => {
13341365
body += chunk;
13351366
})
1367+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
13361368
.on('end', async () => {
13371369
requests.push({ req, body });
13381370
totalEventsTracked += JSON.parse(body).batch.length;
@@ -1343,7 +1375,7 @@ describe('CliRepl', function () {
13431375
})
13441376
.listen(0);
13451377
await once(srv, 'listening');
1346-
host = `http://localhost:${(srv.address() as any).port}`;
1378+
host = `http://localhost:${(srv.address() as AddressInfo).port}`;
13471379
cliReplOptions.analyticsOptions = {
13481380
host,
13491381
apiKey: '🔑',

packages/cli-repl/src/cli-repl.ts

Lines changed: 65 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import type {
5353
DevtoolsProxyOptions,
5454
} from '@mongodb-js/devtools-proxy-support';
5555
import { useOrCreateAgent } from '@mongodb-js/devtools-proxy-support';
56+
import { setupMongoLogWriter } from '@mongosh/logging/lib/setup-logger-and-telemetry';
5657

5758
/**
5859
* Connecting text key.
@@ -256,6 +257,52 @@ export class CliRepl implements MongoshIOProvider {
256257
);
257258
}
258259

260+
/** Setup analytics, logging and telemetry. */
261+
private async startLoggingAndTelemetry() {
262+
// Read the global config with log settings, e.g. logLocation, disableLogging
263+
const disableLogging = this.getConfig('disableLogging');
264+
if (!disableLogging) {
265+
await this.logManager.cleanupOldLogFiles();
266+
markTime(TimingCategories.Logging, 'cleaned up log files');
267+
268+
const logger = await this.logManager.createLogWriter();
269+
const { quiet } = CliRepl.getFileAndEvalInfo(this.cliOptions);
270+
if (!quiet) {
271+
this.output.write(`Current Mongosh Log ID:\t${logger.logId}\n`);
272+
}
273+
274+
this.logWriter = logger;
275+
setupMongoLogWriter(logger);
276+
markTime(TimingCategories.Logging, 'instantiated log writer');
277+
this.bus.emit('mongosh:log-initialized');
278+
logger.info('MONGOSH', mongoLogId(1_000_000_000), 'log', 'Starting log', {
279+
execPath: process.execPath,
280+
envInfo: redactSensitiveData(this.getLoggedEnvironmentVariables()),
281+
...(await buildInfo()),
282+
});
283+
284+
markTime(TimingCategories.Logging, 'logged initial message');
285+
}
286+
287+
markTime(TimingCategories.Telemetry, 'completed telemetry setup');
288+
289+
// Create analytics instance
290+
let analyticsSetupError: Error | null = null;
291+
try {
292+
await this.setupAnalytics();
293+
} catch (err: unknown) {
294+
// Need to delay emitting the error on the bus so that logging is in place
295+
// as well
296+
analyticsSetupError = err as Error;
297+
}
298+
299+
markTime(TimingCategories.Telemetry, 'created analytics instance');
300+
301+
if (analyticsSetupError) {
302+
this.bus.emit('mongosh:error', analyticsSetupError, 'analytics');
303+
}
304+
}
305+
259306
/**
260307
* Setup CLI environment: serviceProvider, ShellEvaluator, log connection
261308
* information, external editor, and finally start the repl.
@@ -267,7 +314,8 @@ export class CliRepl implements MongoshIOProvider {
267314
driverUri: string,
268315
driverOptions: DevtoolsConnectOptions
269316
): Promise<void> {
270-
const { version } = require('../package.json');
317+
// eslint-disable-next-line @typescript-eslint/no-var-requires
318+
const { version }: { version: string } = require('../package.json');
271319
await this.verifyNodeVersion();
272320
markTime(TimingCategories.REPLInstantiation, 'verified node version');
273321

@@ -302,42 +350,19 @@ export class CliRepl implements MongoshIOProvider {
302350

303351
try {
304352
await this.shellHomeDirectory.ensureExists();
305-
} catch (err: any) {
306-
this.warnAboutInaccessibleFile(err);
353+
} catch (err: unknown) {
354+
this.warnAboutInaccessibleFile(err as Error);
307355
}
308356
markTime(TimingCategories.REPLInstantiation, 'ensured shell homedir');
309357

310-
await this.logManager.cleanupOldLogFiles();
311-
markTime(TimingCategories.Logging, 'cleaned up log files');
312-
const logger = await this.logManager.createLogWriter();
313-
const { quiet } = CliRepl.getFileAndEvalInfo(this.cliOptions);
314-
if (!quiet) {
315-
this.output.write(`Current Mongosh Log ID:\t${logger.logId}\n`);
316-
}
317-
this.logWriter = logger;
318-
markTime(TimingCategories.Logging, 'instantiated log writer');
319-
320-
logger.info('MONGOSH', mongoLogId(1_000_000_000), 'log', 'Starting log', {
321-
execPath: process.execPath,
322-
envInfo: redactSensitiveData(this.getLoggedEnvironmentVariables()),
323-
...(await buildInfo()),
324-
});
325-
markTime(TimingCategories.Logging, 'logged initial message');
326-
327-
let analyticsSetupError: Error | null = null;
328-
try {
329-
await this.setupAnalytics();
330-
} catch (err: any) {
331-
// Need to delay emitting the error on the bus so that logging is in place
332-
// as well
333-
analyticsSetupError = err;
334-
}
335-
336-
markTime(TimingCategories.Telemetry, 'created analytics instance');
358+
// Setup telemetry
337359
setupLoggerAndTelemetry(
338360
this.bus,
339-
logger,
340361
this.toggleableAnalytics,
362+
{
363+
userId: this.getConfig('userId'),
364+
telemetryAnonymousId: this.getConfig('telemetryAnonymousId'),
365+
},
341366
{
342367
platform: process.platform,
343368
arch: process.arch,
@@ -346,23 +371,21 @@ export class CliRepl implements MongoshIOProvider {
346371
},
347372
version
348373
);
349-
markTime(TimingCategories.Telemetry, 'completed telemetry setup');
350-
351-
if (analyticsSetupError) {
352-
this.bus.emit('mongosh:error', analyticsSetupError, 'analytics');
353-
}
354374

375+
// Get configuration
355376
try {
356377
this.config = await this.configDirectory.generateOrReadConfig(
357378
this.config
358379
);
359-
} catch (err: any) {
360-
this.warnAboutInaccessibleFile(err);
380+
} catch (err: unknown) {
381+
this.warnAboutInaccessibleFile(err as Error);
361382
}
362383

363384
this.globalConfig = await this.loadGlobalConfigFile();
364385
markTime(TimingCategories.UserConfigLoading, 'read global config files');
365386

387+
await this.startLoggingAndTelemetry();
388+
366389
// Needs to happen after loading the mongosh config file(s)
367390
void this.fetchMongoshUpdateUrl();
368391

@@ -483,7 +506,7 @@ export class CliRepl implements MongoshIOProvider {
483506
if (!this.cliOptions.shell) {
484507
// We flush the telemetry data as part of exiting. Make sure we have
485508
// the right config value.
486-
this.setTelemetryEnabled(await this.getConfig('enableTelemetry'));
509+
this.setTelemetryEnabled(this.getConfig('enableTelemetry'));
487510
await this.exit(0);
488511
return;
489512
}
@@ -516,7 +539,7 @@ export class CliRepl implements MongoshIOProvider {
516539

517540
// We only enable/disable here, since the rc file/command line scripts
518541
// can disable the telemetry setting.
519-
this.setTelemetryEnabled(await this.getConfig('enableTelemetry'));
542+
this.setTelemetryEnabled(this.getConfig('enableTelemetry'));
520543
this.bus.emit('mongosh:start-mongosh-repl', { version });
521544
markTime(TimingCategories.REPLInstantiation, 'starting repl');
522545
await this.mongoshRepl.startRepl(initialized);
@@ -856,9 +879,7 @@ export class CliRepl implements MongoshIOProvider {
856879
* Implements getConfig from the {@link ConfigProvider} interface.
857880
*/
858881
// eslint-disable-next-line @typescript-eslint/require-await
859-
async getConfig<K extends keyof CliUserConfig>(
860-
key: K
861-
): Promise<CliUserConfig[K]> {
882+
getConfig<K extends keyof CliUserConfig>(key: K): CliUserConfig[K] {
862883
return (
863884
(this.config as CliUserConfig)[key] ??
864885
(this.globalConfig as CliUserConfig)?.[key] ??
@@ -1259,7 +1280,7 @@ export class CliRepl implements MongoshIOProvider {
12591280
}
12601281

12611282
try {
1262-
const updateURL = (await this.getConfig('updateURL')).trim();
1283+
const updateURL = this.getConfig('updateURL').trim();
12631284
if (!updateURL) return;
12641285

12651286
const localFilePath = this.shellHomeDirectory.localPath(

packages/logging/src/multi-set.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* A helper class for keeping track of how often specific events occurred.
3+
*/
4+
export class MultiSet<T extends Record<string, unknown>> {
5+
_entries: Map<string, number> = new Map();
6+
7+
add(entry: T): void {
8+
const key = JSON.stringify(Object.entries(entry).sort());
9+
this._entries.set(key, (this._entries.get(key) ?? 0) + 1);
10+
}
11+
12+
clear(): void {
13+
this._entries.clear();
14+
}
15+
16+
*[Symbol.iterator](): Iterator<[T, number]> {
17+
for (const [key, count] of this._entries) {
18+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
19+
yield [Object.fromEntries(JSON.parse(key)) as T, count];
20+
}
21+
}
22+
}

0 commit comments

Comments
 (0)