@mongosh/logging 3.2.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,871 @@
1
+ import redactInfo from 'mongodb-redact';
2
+ import { redactURICredentials } from '@mongosh/history';
3
+ import type {
4
+ MongoshBus,
5
+ ApiEventWithArguments,
6
+ ApiEvent,
7
+ UseEvent,
8
+ EvaluateInputEvent,
9
+ ShowEvent,
10
+ ConnectEvent,
11
+ ScriptLoadFileEvent,
12
+ StartLoadingCliScriptsEvent,
13
+ StartMongoshReplEvent,
14
+ GlobalConfigFileLoadEvent,
15
+ CryptLibrarySkipEvent,
16
+ CryptLibraryFoundEvent,
17
+ SnippetsCommandEvent,
18
+ SnippetsErrorEvent,
19
+ SnippetsFetchIndexErrorEvent,
20
+ SnippetsFetchIndexEvent,
21
+ SnippetsLoadedEvent,
22
+ SnippetsLoadSnippetEvent,
23
+ SnippetsNpmDownloadActiveEvent,
24
+ SnippetsNpmDownloadFailedEvent,
25
+ SnippetsNpmLookupEvent,
26
+ SnippetsRunNpmEvent,
27
+ SnippetsTransformErrorEvent,
28
+ EditorRunEditCommandEvent,
29
+ EditorReadVscodeExtensionsDoneEvent,
30
+ EditorReadVscodeExtensionsFailedEvent,
31
+ FetchingUpdateMetadataEvent,
32
+ FetchingUpdateMetadataCompleteEvent,
33
+ SessionStartedEvent,
34
+ MongoshBusEventsMap,
35
+ WriteCustomLogEvent,
36
+ } from '@mongosh/types';
37
+ import { inspect } from 'util';
38
+ import { MongoLogWriter } from 'mongodb-log-writer';
39
+ import { mongoLogId } from 'mongodb-log-writer';
40
+ import type {
41
+ AnalyticsIdentifyMessage,
42
+ MongoshAnalytics,
43
+ MongoshAnalyticsIdentity,
44
+ } from './analytics-helpers';
45
+ import type { ConnectEventMap } from '@mongodb-js/devtools-connect';
46
+ import { hookLogger } from '@mongodb-js/devtools-connect';
47
+ import { MultiSet, toSnakeCase } from './helpers';
48
+ import { Writable } from 'stream';
49
+ import type {
50
+ LoggingAndTelemetryBusEventState,
51
+ MongoshLoggingAndTelemetry,
52
+ MongoshLoggingAndTelemetryArguments,
53
+ MongoshTrackingProperties,
54
+ } from './types';
55
+
56
+ export function setupLoggingAndTelemetry(
57
+ props: MongoshLoggingAndTelemetryArguments
58
+ ): MongoshLoggingAndTelemetry {
59
+ const loggingAndTelemetry = new LoggingAndTelemetry(props);
60
+
61
+ loggingAndTelemetry.setup();
62
+ return loggingAndTelemetry;
63
+ }
64
+
65
+ class LoggingAndTelemetry implements MongoshLoggingAndTelemetry {
66
+ private static dummyLogger = new MongoLogWriter(
67
+ '',
68
+ null,
69
+ new Writable({
70
+ write(chunk, encoding, callback) {
71
+ callback();
72
+ },
73
+ })
74
+ );
75
+
76
+ private readonly bus: MongoshBus;
77
+ private readonly analytics: MongoshAnalytics;
78
+ private readonly userTraits: {
79
+ platform: string;
80
+ [key: string]: unknown;
81
+ };
82
+ private readonly mongoshVersion: string;
83
+
84
+ private log: MongoLogWriter;
85
+ private pendingLogEvents: CallableFunction[] = [];
86
+ private isSetup = false;
87
+ private isBufferingEvents = false;
88
+
89
+ constructor({
90
+ bus,
91
+ analytics,
92
+ userTraits,
93
+ mongoshVersion,
94
+ }: MongoshLoggingAndTelemetryArguments) {
95
+ this.bus = bus;
96
+ this.analytics = analytics;
97
+ this.log = LoggingAndTelemetry.dummyLogger;
98
+ this.userTraits = userTraits;
99
+ this.mongoshVersion = mongoshVersion;
100
+ }
101
+
102
+ public setup(): void {
103
+ if (this.isSetup) {
104
+ throw new Error('Setup can only be called once.');
105
+ }
106
+ this.setupBusEventListeners();
107
+ this.isSetup = true;
108
+ this.isBufferingEvents = true;
109
+ }
110
+
111
+ public attachLogger(logger: MongoLogWriter): void {
112
+ if (!this.isSetup) {
113
+ throw new Error('Run setup() before setting up the log writer.');
114
+ }
115
+ /** Setup can only be run when overriding a dummy log or a null log. */
116
+ if (this.log !== LoggingAndTelemetry.dummyLogger) {
117
+ throw new Error(
118
+ 'Previously set logger has not been detached. Run detachLogger() before setting.'
119
+ );
120
+ }
121
+ this.log = logger;
122
+ this.isBufferingEvents = false;
123
+
124
+ this.runAndClearPendingEvents();
125
+
126
+ this.bus.emit('mongosh:log-initialized');
127
+ }
128
+
129
+ public detachLogger() {
130
+ this.log = LoggingAndTelemetry.dummyLogger;
131
+ // Still run any remaining pending events with the dummy log for telemetry purposes.
132
+ this.runAndClearPendingEvents();
133
+ }
134
+
135
+ private runAndClearPendingEvents() {
136
+ let pendingEvent: CallableFunction | undefined;
137
+ while ((pendingEvent = this.pendingLogEvents.shift())) {
138
+ pendingEvent();
139
+ }
140
+ }
141
+
142
+ /** Information used and set by different bus events. */
143
+ private busEventState: LoggingAndTelemetryBusEventState = {
144
+ /** We emit different analytics events for loading files and evaluating scripts
145
+ * depending on whether we're already in the REPL or not yet. We store the
146
+ * state here so that the places where the events are emitted don't have to
147
+ * be aware of this distinction. */
148
+ hasStartedMongoshRepl: false,
149
+ apiCallTracking: {
150
+ isEnabled: false,
151
+ apiCalls: new MultiSet<Pick<ApiEvent, 'class' | 'method'>>(),
152
+ deprecatedApiCalls: new MultiSet<Pick<ApiEvent, 'class' | 'method'>>(),
153
+ },
154
+ usesShellOption: false,
155
+ telemetryAnonymousId: undefined,
156
+ userId: undefined,
157
+ };
158
+
159
+ private setupBusEventListeners(): void {
160
+ const onBus = <
161
+ EventsMap extends Record<
162
+ keyof MongoshBusEventsMap | keyof ConnectEventMap,
163
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
164
+ (...args: any[]) => unknown
165
+ > = MongoshBusEventsMap,
166
+ K extends
167
+ | keyof MongoshBusEventsMap
168
+ | keyof ConnectEventMap = keyof MongoshBusEventsMap
169
+ >(
170
+ event: K,
171
+ listener: (...args: Parameters<EventsMap[K]>) => void
172
+ ) => {
173
+ this.bus.on(event, ((...args: Parameters<EventsMap[K]>) => {
174
+ if (this.isBufferingEvents) {
175
+ this.pendingLogEvents.push(() => listener(...args));
176
+ return;
177
+ }
178
+
179
+ listener(...args);
180
+ }) as MongoshBusEventsMap[K]);
181
+ return this.bus;
182
+ };
183
+
184
+ const getUserTraits = (): AnalyticsIdentifyMessage['traits'] => ({
185
+ ...this.userTraits,
186
+ session_id: this.log.logId,
187
+ });
188
+
189
+ const getTrackingProperties = (): MongoshTrackingProperties => ({
190
+ mongosh_version: this.mongoshVersion,
191
+ session_id: this.log.logId,
192
+ });
193
+
194
+ const getTelemetryUserIdentity = (): MongoshAnalyticsIdentity => {
195
+ return {
196
+ anonymousId:
197
+ this.busEventState.telemetryAnonymousId ??
198
+ (this.busEventState.userId as string),
199
+ };
200
+ };
201
+
202
+ onBus('mongosh:start-mongosh-repl', (ev: StartMongoshReplEvent) => {
203
+ this.log.info(
204
+ 'MONGOSH',
205
+ mongoLogId(1_000_000_002),
206
+ 'repl',
207
+ 'Started REPL',
208
+ ev
209
+ );
210
+ this.busEventState.hasStartedMongoshRepl = true;
211
+ });
212
+
213
+ onBus(
214
+ 'mongosh:start-loading-cli-scripts',
215
+ (event: StartLoadingCliScriptsEvent) => {
216
+ this.log.info(
217
+ 'MONGOSH',
218
+ mongoLogId(1_000_000_003),
219
+ 'repl',
220
+ 'Start loading CLI scripts'
221
+ );
222
+ this.busEventState.usesShellOption = event.usesShellOption;
223
+ }
224
+ );
225
+
226
+ onBus('mongosh:connect', (args: ConnectEvent) => {
227
+ const { uri, resolved_hostname, ...argsWithoutUriAndHostname } = args;
228
+ const connectionUri = uri && redactURICredentials(uri);
229
+ const atlasHostname = {
230
+ atlas_hostname: args.is_atlas ? resolved_hostname : null,
231
+ };
232
+ const properties = {
233
+ ...getTrackingProperties(),
234
+ ...argsWithoutUriAndHostname,
235
+ ...atlasHostname,
236
+ };
237
+
238
+ this.log.info(
239
+ 'MONGOSH',
240
+ mongoLogId(1_000_000_004),
241
+ 'connect',
242
+ 'Connecting to server',
243
+ {
244
+ userId: this.busEventState.userId,
245
+ telemetryAnonymousId: this.busEventState.telemetryAnonymousId,
246
+ connectionUri,
247
+ ...properties,
248
+ }
249
+ );
250
+
251
+ this.analytics.track({
252
+ ...getTelemetryUserIdentity(),
253
+ event: 'New Connection',
254
+ properties,
255
+ });
256
+ });
257
+
258
+ onBus('mongosh:start-session', (args: SessionStartedEvent) => {
259
+ const normalizedTimingsArray = Object.entries(args.timings).map(
260
+ ([key, duration]) => {
261
+ const snakeCaseKey = toSnakeCase(key);
262
+ return [snakeCaseKey, duration];
263
+ }
264
+ );
265
+
266
+ const normalizedTimings = Object.fromEntries(normalizedTimingsArray);
267
+ this.analytics.track({
268
+ ...getTelemetryUserIdentity(),
269
+ event: 'Startup Time',
270
+ properties: {
271
+ ...getTrackingProperties(),
272
+ is_interactive: args.isInteractive,
273
+ js_context: args.jsContext,
274
+ ...normalizedTimings,
275
+ },
276
+ });
277
+ });
278
+
279
+ onBus(
280
+ 'mongosh:new-user',
281
+ (newTelemetryUserIdentity: { userId: string; anonymousId: string }) => {
282
+ if (!newTelemetryUserIdentity.anonymousId) {
283
+ this.busEventState.userId = newTelemetryUserIdentity.userId;
284
+ }
285
+ this.busEventState.telemetryAnonymousId =
286
+ newTelemetryUserIdentity.anonymousId;
287
+ this.analytics.identify({
288
+ anonymousId: newTelemetryUserIdentity.anonymousId,
289
+ traits: getUserTraits(),
290
+ });
291
+ }
292
+ );
293
+
294
+ onBus(
295
+ 'mongosh:update-user',
296
+ (updatedTelemetryUserIdentity: {
297
+ userId: string;
298
+ anonymousId?: string;
299
+ }) => {
300
+ if (updatedTelemetryUserIdentity.anonymousId) {
301
+ this.busEventState.telemetryAnonymousId =
302
+ updatedTelemetryUserIdentity.anonymousId;
303
+ } else {
304
+ this.busEventState.userId = updatedTelemetryUserIdentity.userId;
305
+ }
306
+ this.analytics.identify({
307
+ ...getTelemetryUserIdentity(),
308
+ traits: getUserTraits(),
309
+ });
310
+ this.log.info(
311
+ 'MONGOSH',
312
+ mongoLogId(1_000_000_005),
313
+ 'config',
314
+ 'User updated'
315
+ );
316
+ }
317
+ );
318
+
319
+ onBus('mongosh:error', (error: Error, context: string) => {
320
+ const mongoshError = error as {
321
+ name: string;
322
+ message: string;
323
+ code: unknown;
324
+ scope: unknown;
325
+ metadata: unknown;
326
+ };
327
+
328
+ this.log[context === 'fatal' ? 'fatal' : 'error'](
329
+ 'MONGOSH',
330
+ mongoLogId(1_000_000_006),
331
+ context,
332
+ `${mongoshError.name}: ${mongoshError.message}`,
333
+ error
334
+ );
335
+
336
+ if (error.name.includes('Mongosh')) {
337
+ this.analytics.track({
338
+ ...getTelemetryUserIdentity(),
339
+ event: 'Error',
340
+ properties: {
341
+ ...getTrackingProperties(),
342
+ name: mongoshError.name,
343
+ code: mongoshError.code,
344
+ scope: mongoshError.scope,
345
+ metadata: mongoshError.metadata,
346
+ },
347
+ });
348
+ }
349
+ });
350
+
351
+ onBus('mongosh:write-custom-log', (event: WriteCustomLogEvent) => {
352
+ this.log[event.method](
353
+ 'MONGOSH-SCRIPTS',
354
+ mongoLogId(1_000_000_054),
355
+ 'custom-log',
356
+ event.message,
357
+ event.attr,
358
+ event.level
359
+ );
360
+ });
361
+
362
+ onBus('mongosh:globalconfig-load', (args: GlobalConfigFileLoadEvent) => {
363
+ this.log.info(
364
+ 'MONGOSH',
365
+ mongoLogId(1_000_000_048),
366
+ 'config',
367
+ 'Loading global configuration file',
368
+ args
369
+ );
370
+ });
371
+
372
+ onBus('mongosh:evaluate-input', (args: EvaluateInputEvent) => {
373
+ this.log.info(
374
+ 'MONGOSH',
375
+ mongoLogId(1_000_000_007),
376
+ 'repl',
377
+ 'Evaluating input',
378
+ args
379
+ );
380
+ });
381
+
382
+ onBus('mongosh:use', (args: UseEvent) => {
383
+ this.log.info(
384
+ 'MONGOSH',
385
+ mongoLogId(1_000_000_008),
386
+ 'shell-api',
387
+ 'Used "use" command',
388
+ args
389
+ );
390
+
391
+ this.analytics.track({
392
+ ...getTelemetryUserIdentity(),
393
+ event: 'Use',
394
+ properties: {
395
+ ...getTrackingProperties(),
396
+ },
397
+ });
398
+ });
399
+
400
+ onBus('mongosh:show', (args: ShowEvent) => {
401
+ this.log.info(
402
+ 'MONGOSH',
403
+ mongoLogId(1_000_000_009),
404
+ 'shell-api',
405
+ 'Used "show" command',
406
+ args
407
+ );
408
+
409
+ this.analytics.track({
410
+ ...getTelemetryUserIdentity(),
411
+ event: 'Show',
412
+ properties: {
413
+ ...getTrackingProperties(),
414
+ method: args.method,
415
+ },
416
+ });
417
+ });
418
+
419
+ onBus('mongosh:setCtx', (args: ApiEventWithArguments) => {
420
+ this.log.info(
421
+ 'MONGOSH',
422
+ mongoLogId(1_000_000_010),
423
+ 'shell-api',
424
+ 'Initialized context',
425
+ args
426
+ );
427
+ });
428
+
429
+ onBus('mongosh:api-call-with-arguments', (args: ApiEventWithArguments) => {
430
+ // TODO: redactInfo cannot handle circular or otherwise nontrivial input
431
+ let arg;
432
+ try {
433
+ arg = JSON.parse(JSON.stringify(args));
434
+ } catch {
435
+ arg = { _inspected: inspect(args) };
436
+ }
437
+ this.log.info(
438
+ 'MONGOSH',
439
+ mongoLogId(1_000_000_011),
440
+ 'shell-api',
441
+ 'Performed API call',
442
+ redactInfo(arg)
443
+ );
444
+ });
445
+
446
+ onBus('mongosh:api-load-file', (args: ScriptLoadFileEvent) => {
447
+ this.log.info(
448
+ 'MONGOSH',
449
+ mongoLogId(1_000_000_012),
450
+ 'shell-api',
451
+ 'Loading file via load()',
452
+ args
453
+ );
454
+
455
+ this.analytics.track({
456
+ ...getTelemetryUserIdentity(),
457
+ event: this.busEventState.hasStartedMongoshRepl
458
+ ? 'Script Loaded'
459
+ : 'Script Loaded CLI',
460
+ properties: {
461
+ ...getTrackingProperties(),
462
+ nested: args.nested,
463
+ ...(this.busEventState.hasStartedMongoshRepl
464
+ ? {}
465
+ : { shell: this.busEventState.usesShellOption }),
466
+ },
467
+ });
468
+ });
469
+
470
+ onBus('mongosh:eval-cli-script', () => {
471
+ this.log.info(
472
+ 'MONGOSH',
473
+ mongoLogId(1_000_000_013),
474
+ 'repl',
475
+ 'Evaluating script passed on the command line'
476
+ );
477
+
478
+ this.analytics.track({
479
+ ...getTelemetryUserIdentity(),
480
+ event: 'Script Evaluated',
481
+ properties: {
482
+ ...getTrackingProperties(),
483
+ shell: this.busEventState.usesShellOption,
484
+ },
485
+ });
486
+ });
487
+
488
+ onBus('mongosh:mongoshrc-load', () => {
489
+ this.log.info(
490
+ 'MONGOSH',
491
+ mongoLogId(1_000_000_014),
492
+ 'repl',
493
+ 'Loading .mongoshrc.js'
494
+ );
495
+
496
+ this.analytics.track({
497
+ ...getTelemetryUserIdentity(),
498
+ event: 'Mongoshrc Loaded',
499
+ properties: {
500
+ ...getTrackingProperties(),
501
+ },
502
+ });
503
+ });
504
+
505
+ onBus('mongosh:mongoshrc-mongorc-warn', () => {
506
+ this.log.info(
507
+ 'MONGOSH',
508
+ mongoLogId(1_000_000_015),
509
+ 'repl',
510
+ 'Warning about .mongorc.js/.mongoshrc.js mismatch'
511
+ );
512
+
513
+ this.analytics.track({
514
+ ...getTelemetryUserIdentity(),
515
+ event: 'Mongorc Warning',
516
+ properties: {
517
+ ...getTrackingProperties(),
518
+ },
519
+ });
520
+ });
521
+
522
+ onBus('mongosh:crypt-library-load-skip', (ev: CryptLibrarySkipEvent) => {
523
+ this.log.info(
524
+ 'AUTO-ENCRYPTION',
525
+ mongoLogId(1_000_000_050),
526
+ 'crypt-library',
527
+ 'Skipping shared library candidate',
528
+ ev
529
+ );
530
+ });
531
+
532
+ onBus('mongosh:crypt-library-load-found', (ev: CryptLibraryFoundEvent) => {
533
+ this.log.warn(
534
+ 'AUTO-ENCRYPTION',
535
+ mongoLogId(1_000_000_051),
536
+ 'crypt-library',
537
+ 'Accepted shared library candidate',
538
+ {
539
+ cryptSharedLibPath: ev.cryptSharedLibPath,
540
+ expectedVersion: ev.expectedVersion.versionStr,
541
+ }
542
+ );
543
+ });
544
+
545
+ onBus('mongosh-snippets:loaded', (ev: SnippetsLoadedEvent) => {
546
+ this.log.info(
547
+ 'MONGOSH-SNIPPETS',
548
+ mongoLogId(1_000_000_019),
549
+ 'snippets',
550
+ 'Loaded snippets',
551
+ ev
552
+ );
553
+ });
554
+
555
+ onBus('mongosh-snippets:npm-lookup', (ev: SnippetsNpmLookupEvent) => {
556
+ this.log.info(
557
+ 'MONGOSH-SNIPPETS',
558
+ mongoLogId(1_000_000_020),
559
+ 'snippets',
560
+ 'Performing npm lookup',
561
+ ev
562
+ );
563
+ });
564
+
565
+ onBus('mongosh-snippets:npm-lookup-stopped', () => {
566
+ this.log.info(
567
+ 'MONGOSH-SNIPPETS',
568
+ mongoLogId(1_000_000_021),
569
+ 'snippets',
570
+ 'npm lookup stopped'
571
+ );
572
+ });
573
+
574
+ onBus(
575
+ 'mongosh-snippets:npm-download-failed',
576
+ (ev: SnippetsNpmDownloadFailedEvent) => {
577
+ this.log.info(
578
+ 'MONGOSH-SNIPPETS',
579
+ mongoLogId(1_000_000_022),
580
+ 'snippets',
581
+ 'npm download failed',
582
+ ev
583
+ );
584
+ }
585
+ );
586
+
587
+ onBus(
588
+ 'mongosh-snippets:npm-download-active',
589
+ (ev: SnippetsNpmDownloadActiveEvent) => {
590
+ this.log.info(
591
+ 'MONGOSH-SNIPPETS',
592
+ mongoLogId(1_000_000_023),
593
+ 'snippets',
594
+ 'npm download active',
595
+ ev
596
+ );
597
+ }
598
+ );
599
+
600
+ onBus('mongosh-snippets:fetch-index', (ev: SnippetsFetchIndexEvent) => {
601
+ this.log.info(
602
+ 'MONGOSH-SNIPPETS',
603
+ mongoLogId(1_000_000_024),
604
+ 'snippets',
605
+ 'Fetching snippet index',
606
+ ev
607
+ );
608
+ });
609
+
610
+ onBus('mongosh-snippets:fetch-cache-invalid', () => {
611
+ this.log.info(
612
+ 'MONGOSH-SNIPPETS',
613
+ mongoLogId(1_000_000_025),
614
+ 'snippets',
615
+ 'Snippet cache invalid'
616
+ );
617
+ });
618
+
619
+ onBus(
620
+ 'mongosh-snippets:fetch-index-error',
621
+ (ev: SnippetsFetchIndexErrorEvent) => {
622
+ this.log.info(
623
+ 'MONGOSH-SNIPPETS',
624
+ mongoLogId(1_000_000_026),
625
+ 'snippets',
626
+ 'Fetching snippet index failed',
627
+ ev
628
+ );
629
+ }
630
+ );
631
+
632
+ onBus('mongosh-snippets:fetch-index-done', () => {
633
+ this.log.info(
634
+ 'MONGOSH-SNIPPETS',
635
+ mongoLogId(1_000_000_027),
636
+ 'snippets',
637
+ 'Fetching snippet index done'
638
+ );
639
+ });
640
+ onBus(
641
+ 'mongosh-snippets:package-json-edit-error',
642
+ (ev: SnippetsErrorEvent) => {
643
+ this.log.info(
644
+ 'MONGOSH-SNIPPETS',
645
+ mongoLogId(1_000_000_028),
646
+ 'snippets',
647
+ 'Modifying snippets package.json failed',
648
+ ev
649
+ );
650
+ }
651
+ );
652
+
653
+ onBus('mongosh-snippets:spawn-child', (ev: SnippetsRunNpmEvent) => {
654
+ this.log.info(
655
+ 'MONGOSH-SNIPPETS',
656
+ mongoLogId(1_000_000_029),
657
+ 'snippets',
658
+ 'Spawning helper',
659
+ ev
660
+ );
661
+ });
662
+
663
+ onBus('mongosh-snippets:load-snippet', (ev: SnippetsLoadSnippetEvent) => {
664
+ this.log.info(
665
+ 'MONGOSH-SNIPPETS',
666
+ mongoLogId(1_000_000_030),
667
+ 'snippets',
668
+ 'Loading snippet',
669
+ ev
670
+ );
671
+ });
672
+
673
+ onBus('mongosh-snippets:snippet-command', (ev: SnippetsCommandEvent) => {
674
+ this.log.info(
675
+ 'MONGOSH-SNIPPETS',
676
+ mongoLogId(1_000_000_031),
677
+ 'snippets',
678
+ 'Running snippet command',
679
+ ev
680
+ );
681
+
682
+ if (ev.args[0] === 'install') {
683
+ this.analytics.track({
684
+ ...getTelemetryUserIdentity(),
685
+ event: 'Snippet Install',
686
+ properties: {
687
+ ...getTrackingProperties(),
688
+ },
689
+ });
690
+ }
691
+ });
692
+
693
+ onBus(
694
+ 'mongosh-snippets:transform-error',
695
+ (ev: SnippetsTransformErrorEvent) => {
696
+ this.log.info(
697
+ 'MONGOSH-SNIPPETS',
698
+ mongoLogId(1_000_000_032),
699
+ 'snippets',
700
+ 'Rewrote error message',
701
+ ev
702
+ );
703
+ }
704
+ );
705
+
706
+ onBus('mongosh:api-call', (ev: ApiEvent) => {
707
+ // Only track if we have previously seen a mongosh:evaluate-started call
708
+ if (!this.busEventState.apiCallTracking.isEnabled) return;
709
+ const { apiCalls, deprecatedApiCalls } =
710
+ this.busEventState.apiCallTracking;
711
+ if (ev.deprecated) {
712
+ deprecatedApiCalls.add({ class: ev.class, method: ev.method });
713
+ }
714
+ if (ev.callDepth === 0 && ev.isAsync) {
715
+ apiCalls.add({ class: ev.class, method: ev.method });
716
+ }
717
+ });
718
+ onBus('mongosh:evaluate-started', () => {
719
+ const { apiCalls, deprecatedApiCalls } =
720
+ this.busEventState.apiCallTracking;
721
+ this.busEventState.apiCallTracking.isEnabled = true;
722
+ // Clear API calls before evaluation starts. This is important because
723
+ // some API calls are also emitted by mongosh CLI repl internals,
724
+ // but we only care about those emitted from user code (i.e. during
725
+ // evaluation).
726
+ deprecatedApiCalls.clear();
727
+ apiCalls.clear();
728
+ });
729
+ onBus('mongosh:evaluate-finished', () => {
730
+ const { apiCalls, deprecatedApiCalls } =
731
+ this.busEventState.apiCallTracking;
732
+ for (const [entry] of deprecatedApiCalls) {
733
+ this.log.warn(
734
+ 'MONGOSH',
735
+ mongoLogId(1_000_000_033),
736
+ 'shell-api',
737
+ 'Deprecated API call',
738
+ entry
739
+ );
740
+
741
+ this.analytics.track({
742
+ ...getTelemetryUserIdentity(),
743
+ event: 'Deprecated Method',
744
+ properties: {
745
+ ...getTrackingProperties(),
746
+ ...entry,
747
+ },
748
+ });
749
+ }
750
+ for (const [entry, count] of apiCalls) {
751
+ this.analytics.track({
752
+ ...getTelemetryUserIdentity(),
753
+ event: 'API Call',
754
+ properties: {
755
+ ...getTrackingProperties(),
756
+ ...entry,
757
+ count,
758
+ },
759
+ });
760
+ }
761
+ deprecatedApiCalls.clear();
762
+ apiCalls.clear();
763
+ this.busEventState.apiCallTracking.isEnabled = false;
764
+ });
765
+
766
+ // Log ids 1_000_000_034 through 1_000_000_042 are reserved for the
767
+ // devtools-connect package which was split out from mongosh.
768
+ // 'mongodb' is not supported in startup snapshots yet.
769
+
770
+ onBus('mongosh-sp:reset-connection-options', () => {
771
+ this.log.info(
772
+ 'MONGOSH-SP',
773
+ mongoLogId(1_000_000_040),
774
+ 'connect',
775
+ 'Reconnect because of changed connection options'
776
+ );
777
+ });
778
+
779
+ onBus(
780
+ 'mongosh-editor:run-edit-command',
781
+ (ev: EditorRunEditCommandEvent) => {
782
+ this.log.error(
783
+ 'MONGOSH-EDITOR',
784
+ mongoLogId(1_000_000_047),
785
+ 'editor',
786
+ 'Open external editor',
787
+ redactInfo(ev)
788
+ );
789
+ }
790
+ );
791
+
792
+ onBus(
793
+ 'mongosh-editor:read-vscode-extensions-done',
794
+ (ev: EditorReadVscodeExtensionsDoneEvent) => {
795
+ this.log.error(
796
+ 'MONGOSH-EDITOR',
797
+ mongoLogId(1_000_000_043),
798
+ 'editor',
799
+ 'Reading vscode extensions from file system succeeded',
800
+ ev
801
+ );
802
+ }
803
+ );
804
+
805
+ onBus(
806
+ 'mongosh-editor:read-vscode-extensions-failed',
807
+ (ev: EditorReadVscodeExtensionsFailedEvent) => {
808
+ this.log.error(
809
+ 'MONGOSH-EDITOR',
810
+ mongoLogId(1_000_000_044),
811
+ 'editor',
812
+ 'Reading vscode extensions from file system failed',
813
+ {
814
+ ...ev,
815
+ error: ev.error.message,
816
+ }
817
+ );
818
+ }
819
+ );
820
+
821
+ onBus(
822
+ 'mongosh:fetching-update-metadata',
823
+ (ev: FetchingUpdateMetadataEvent) => {
824
+ this.log.info(
825
+ 'MONGOSH',
826
+ mongoLogId(1_000_000_052),
827
+ 'startup',
828
+ 'Fetching update metadata',
829
+ {
830
+ ...ev,
831
+ }
832
+ );
833
+ }
834
+ );
835
+
836
+ onBus(
837
+ 'mongosh:fetching-update-metadata-complete',
838
+ (ev: FetchingUpdateMetadataCompleteEvent) => {
839
+ this.log.info(
840
+ 'MONGOSH',
841
+ mongoLogId(1_000_000_053),
842
+ 'startup',
843
+ 'Fetching update metadata complete',
844
+ {
845
+ ...ev,
846
+ }
847
+ );
848
+ }
849
+ );
850
+
851
+ hookLogger(
852
+ this.bus,
853
+ {
854
+ info: (...args: Parameters<typeof this.log.info>) => {
855
+ return this.log.info(...args);
856
+ },
857
+ warn: (...args: Parameters<typeof this.log.warn>) => {
858
+ return this.log.warn(...args);
859
+ },
860
+ error: (...args: Parameters<typeof this.log.error>) => {
861
+ return this.log.error(...args);
862
+ },
863
+ mongoLogId: (...args: Parameters<typeof this.log.mongoLogId>) => {
864
+ return this.log.mongoLogId(...args);
865
+ },
866
+ },
867
+ 'mongosh',
868
+ (uri) => redactURICredentials(uri)
869
+ );
870
+ }
871
+ }