@mongosh/logging 1.0.7

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,403 @@
1
+ import { expect } from 'chai';
2
+ import { MongoLogWriter } from 'mongodb-log-writer';
3
+ import { setupLoggerAndTelemetry } from './';
4
+ import { EventEmitter } from 'events';
5
+ import { MongoshInvalidInputError } from '@mongosh/errors';
6
+ import { MongoshBus } from '@mongosh/types';
7
+
8
+ describe('setupLoggerAndTelemetry', () => {
9
+ let logOutput: any[];
10
+ let analyticsOutput: ['identify'|'track'|'log', any][];
11
+ let bus: MongoshBus;
12
+
13
+ const userId = '53defe995fa47e6c13102d9d';
14
+ const logId = '5fb3c20ee1507e894e5340f3';
15
+
16
+ const logger = new MongoLogWriter(logId, `/tmp/${logId}_log`, {
17
+ write(chunk: string, cb: () => void) { logOutput.push(JSON.parse(chunk)); cb(); },
18
+ end(cb: () => void) { cb(); }
19
+ } as any);
20
+ const analytics = {
21
+ identify(info: any) { analyticsOutput.push(['identify', info]); },
22
+ track(info: any) { analyticsOutput.push(['track', info]); }
23
+ };
24
+
25
+ beforeEach(() => {
26
+ logOutput = [];
27
+ analyticsOutput = [];
28
+ bus = new EventEmitter();
29
+ });
30
+
31
+ it('works', () => {
32
+ setupLoggerAndTelemetry(bus, logger, () => analytics, {
33
+ platform: process.platform,
34
+ arch: process.arch
35
+ }, '1.0.0');
36
+ expect(logOutput).to.have.lengthOf(0);
37
+ expect(analyticsOutput).to.be.empty;
38
+
39
+ bus.emit('mongosh:new-user', userId, false);
40
+ bus.emit('mongosh:new-user', userId, true);
41
+
42
+ // Test some events with and without telemetry enabled
43
+ for (const telemetry of [ false, true ]) {
44
+ bus.emit('mongosh:update-user', userId, telemetry);
45
+ bus.emit('mongosh:connect', {
46
+ uri: 'mongodb://localhost/',
47
+ is_localhost: true,
48
+ is_atlas: false,
49
+ node_version: 'v12.19.0'
50
+ } as any);
51
+ bus.emit('mongosh:error', new MongoshInvalidInputError('meow', 'CLIREPL-1005', { cause: 'x' }), 'repl');
52
+ bus.emit('mongosh:use', { db: 'admin' });
53
+ bus.emit('mongosh:show', { method: 'dbs' });
54
+ }
55
+
56
+ bus.emit('mongosh:setCtx', { method: 'setCtx' });
57
+ bus.emit('mongosh:api-call', { method: 'auth', class: 'Database', db: 'test-1603986682000', arguments: { } });
58
+ bus.emit('mongosh:api-call', { method: 'redactable', arguments: { filter: { email: 'mongosh@example.com' } } });
59
+ bus.emit('mongosh:evaluate-input', { input: '1+1' });
60
+
61
+ const circular: any = {};
62
+ circular.circular = circular;
63
+ bus.emit('mongosh:api-call', { method: 'circulararg', arguments: { options: { circular } } });
64
+ expect(circular.circular).to.equal(circular); // Make sure the argument is still intact afterwards
65
+
66
+ bus.emit('mongosh:start-loading-cli-scripts', { usesShellOption: true });
67
+ bus.emit('mongosh:api-load-file', { nested: true, filename: 'foobar.js' });
68
+ bus.emit('mongosh:start-mongosh-repl', { version: '1.0.0' });
69
+ bus.emit('mongosh:api-load-file', { nested: false, filename: 'foobar.js' });
70
+ bus.emit('mongosh:mongoshrc-load');
71
+ bus.emit('mongosh:mongoshrc-mongorc-warn');
72
+ bus.emit('mongosh:eval-cli-script');
73
+
74
+ bus.emit('mongosh-snippets:loaded', { installdir: '/' });
75
+ bus.emit('mongosh-snippets:npm-lookup', { existingVersion: 'v1.2.3' });
76
+ bus.emit('mongosh-snippets:npm-lookup-stopped');
77
+ bus.emit('mongosh-snippets:npm-download-failed', { npmMetadataURL: 'https://example.com' });
78
+ bus.emit('mongosh-snippets:npm-download-active', { npmMetadataURL: 'https://example.com', npmTarballURL: 'https://example.net' });
79
+ bus.emit('mongosh-snippets:fetch-index', { refreshMode: 'always' });
80
+ bus.emit('mongosh-snippets:fetch-cache-invalid');
81
+ bus.emit('mongosh-snippets:fetch-index-error', { action: 'fetch', url: 'https://localhost' });
82
+ bus.emit('mongosh-snippets:fetch-index-done');
83
+ bus.emit('mongosh-snippets:package-json-edit-error', { error: 'failed' });
84
+ bus.emit('mongosh-snippets:spawn-child', { args: ['npm', 'install'] });
85
+ bus.emit('mongosh-snippets:load-snippet', { source: 'load-all', name: 'foo' });
86
+ bus.emit('mongosh-snippets:snippet-command', { args: ['install', 'foo'] });
87
+ bus.emit('mongosh-snippets:transform-error', { error: 'failed', addition: 'oh no', name: 'foo' });
88
+
89
+ const connAttemptData = {
90
+ driver: { name: 'nodejs', version: '3.6.1' },
91
+ serviceProviderVersion: '1.0.0',
92
+ host: 'localhost'
93
+ };
94
+ bus.emit('mongosh-sp:connect-attempt-initialized', connAttemptData);
95
+ bus.emit('mongosh-sp:connect-heartbeat-failure', { connectionId: 'localhost', failure: new Error('cause'), isFailFast: true, isKnownServer: true });
96
+ bus.emit('mongosh-sp:connect-heartbeat-succeeded', { connectionId: 'localhost' });
97
+ bus.emit('mongosh-sp:connect-fail-early');
98
+ bus.emit('mongosh-sp:connect-attempt-finished');
99
+ bus.emit('mongosh-sp:resolve-srv-error', { from: 'mongodb+srv://foo:bar@hello.world/', error: new Error('failed'), duringLoad: false });
100
+ bus.emit('mongosh-sp:resolve-srv-succeeded', { from: 'mongodb+srv://foo:bar@hello.world/', to: 'mongodb://foo:bar@db.hello.world/' });
101
+ bus.emit('mongosh-sp:reset-connection-options');
102
+ bus.emit('mongosh-sp:missing-optional-dependency', { name: 'kerberos', error: new Error('no kerberos') });
103
+
104
+ let i = 0;
105
+ expect(logOutput[i].msg).to.equal('User updated');
106
+ expect(logOutput[i++].attr).to.deep.equal({ enableTelemetry: false });
107
+ expect(logOutput[i].msg).to.equal('Connecting to server');
108
+ expect(logOutput[i].attr.session_id).to.equal('5fb3c20ee1507e894e5340f3');
109
+ expect(logOutput[i].attr.userId).to.equal('53defe995fa47e6c13102d9d');
110
+ expect(logOutput[i].attr.connectionUri).to.equal('mongodb://localhost/');
111
+ expect(logOutput[i].attr.is_localhost).to.equal(true);
112
+ expect(logOutput[i].attr.is_atlas).to.equal(false);
113
+ expect(logOutput[i++].attr.node_version).to.equal('v12.19.0');
114
+ expect(logOutput[i].s).to.equal('E');
115
+ expect(logOutput[i++].attr.message).to.match(/meow/);
116
+ expect(logOutput[i].msg).to.equal('Used "use" command');
117
+ expect(logOutput[i++].attr).to.deep.equal({ db: 'admin' });
118
+ expect(logOutput[i].msg).to.equal('Used "show" command');
119
+ expect(logOutput[i++].attr).to.deep.equal({ method: 'dbs' });
120
+ expect(logOutput[i].msg).to.equal('User updated');
121
+ expect(logOutput[i++].attr).to.deep.equal({ enableTelemetry: true });
122
+ expect(logOutput[i++].msg).to.equal('Connecting to server');
123
+ expect(logOutput[i].s).to.equal('E');
124
+ expect(logOutput[i++].attr.message).to.match(/meow/);
125
+ expect(logOutput[i].msg).to.equal('Used "use" command');
126
+ expect(logOutput[i++].attr).to.deep.equal({ db: 'admin' });
127
+ expect(logOutput[i].msg).to.equal('Used "show" command');
128
+ expect(logOutput[i++].attr).to.deep.equal({ method: 'dbs' });
129
+ expect(logOutput[i++].msg).to.equal('Initialized context');
130
+ expect(logOutput[i].msg).to.equal('Performed API call');
131
+ expect(logOutput[i++].attr.db).to.equal('test-1603986682000');
132
+ expect(logOutput[i].msg).to.equal('Performed API call');
133
+ expect(logOutput[i++].attr.arguments.filter.email).to.equal('<email>');
134
+ expect(logOutput[i].msg).to.equal('Evaluating input');
135
+ expect(logOutput[i++].attr.input).to.equal('1+1');
136
+ expect(logOutput[i].msg).to.equal('Performed API call');
137
+ expect(logOutput[i++].attr._inspected).to.match(/circular/);
138
+ expect(logOutput[i++].msg).to.equal('Start loading CLI scripts');
139
+ expect(logOutput[i].msg).to.equal('Loading file via load()');
140
+ expect(logOutput[i].attr.nested).to.equal(true);
141
+ expect(logOutput[i++].attr.filename).to.equal('foobar.js');
142
+ expect(logOutput[i].msg).to.equal('Started REPL');
143
+ expect(logOutput[i++].attr.version).to.equal('1.0.0');
144
+ expect(logOutput[i].attr.nested).to.equal(false);
145
+ expect(logOutput[i++].attr.filename).to.equal('foobar.js');
146
+ expect(logOutput[i++].msg).to.equal('Loading .mongoshrc.js');
147
+ expect(logOutput[i++].msg).to.equal('Warning about .mongorc.js/.mongoshrc.js mismatch');
148
+ expect(logOutput[i++].msg).to.equal('Evaluating script passed on the command line');
149
+ expect(logOutput[i].msg).to.equal('Loaded snippets');
150
+ expect(logOutput[i++].attr).to.deep.equal({ installdir: '/' });
151
+ expect(logOutput[i].msg).to.equal('Performing npm lookup');
152
+ expect(logOutput[i++].attr).to.deep.equal({ existingVersion: 'v1.2.3' });
153
+ expect(logOutput[i++].msg).to.equal('npm lookup stopped');
154
+ expect(logOutput[i].msg).to.equal('npm download failed');
155
+ expect(logOutput[i++].attr.npmMetadataURL).to.equal('https://example.com');
156
+ expect(logOutput[i].msg).to.equal('npm download active');
157
+ expect(logOutput[i].attr.npmMetadataURL).to.equal('https://example.com');
158
+ expect(logOutput[i++].attr.npmTarballURL).to.equal('https://example.net');
159
+ expect(logOutput[i].msg).to.equal('Fetching snippet index');
160
+ expect(logOutput[i++].attr.refreshMode).to.equal('always');
161
+ expect(logOutput[i++].msg).to.equal('Snippet cache invalid');
162
+ expect(logOutput[i].msg).to.equal('Fetching snippet index failed');
163
+ expect(logOutput[i++].attr).to.deep.equal({ action: 'fetch', url: 'https://localhost' });
164
+ expect(logOutput[i++].msg).to.equal('Fetching snippet index done');
165
+ expect(logOutput[i].msg).to.equal('Modifying snippets package.json failed');
166
+ expect(logOutput[i++].attr).to.deep.equal({ error: 'failed' });
167
+ expect(logOutput[i].msg).to.equal('Spawning helper');
168
+ expect(logOutput[i++].attr).to.deep.equal({ args: ['npm', 'install'] });
169
+ expect(logOutput[i].msg).to.equal('Loading snippet');
170
+ expect(logOutput[i++].attr).to.deep.equal({ source: 'load-all', name: 'foo' });
171
+ expect(logOutput[i].msg).to.equal('Running snippet command');
172
+ expect(logOutput[i++].attr).to.deep.equal({ args: ['install', 'foo'] });
173
+ expect(logOutput[i].msg).to.equal('Rewrote error message');
174
+ expect(logOutput[i++].attr).to.deep.equal({ error: 'failed', addition: 'oh no', name: 'foo' });
175
+ expect(logOutput[i].msg).to.equal('Initiating connection attempt');
176
+ expect(logOutput[i++].attr).to.deep.equal(connAttemptData);
177
+ expect(logOutput[i].msg).to.equal('Server heartbeat failure');
178
+ expect(logOutput[i++].attr).to.deep.equal({ connectionId: 'localhost', failure: 'cause', isFailFast: true, isKnownServer: true });
179
+ expect(logOutput[i].msg).to.equal('Server heartbeat succeeded');
180
+ expect(logOutput[i++].attr).to.deep.equal({ connectionId: 'localhost' });
181
+ expect(logOutput[i++].msg).to.equal('Aborting connection attempt as irrecoverable');
182
+ expect(logOutput[i++].msg).to.equal('Connection attempt finished');
183
+ expect(logOutput[i].msg).to.equal('Resolving SRV record failed');
184
+ expect(logOutput[i++].attr).to.deep.equal({ from: 'mongodb+srv://<credentials>@hello.world/', error: 'failed', duringLoad: false });
185
+ expect(logOutput[i].msg).to.equal('Resolving SRV record succeeded');
186
+ expect(logOutput[i++].attr).to.deep.equal({ from: 'mongodb+srv://<credentials>@hello.world/', to: 'mongodb://<credentials>@db.hello.world/' });
187
+ expect(logOutput[i++].msg).to.equal('Reconnect because of changed connection options');
188
+ expect(logOutput[i].msg).to.equal('Missing optional dependency');
189
+ expect(logOutput[i++].attr).to.deep.equal({ name: 'kerberos', error: 'no kerberos' });
190
+ expect(i).to.equal(logOutput.length);
191
+
192
+ expect(analyticsOutput).to.deep.equal([
193
+ [
194
+ 'identify',
195
+ {
196
+ userId: '53defe995fa47e6c13102d9d',
197
+ traits: {
198
+ platform: process.platform,
199
+ arch: process.arch
200
+ }
201
+ }
202
+ ],
203
+ [
204
+ 'identify',
205
+ {
206
+ userId: '53defe995fa47e6c13102d9d',
207
+ traits: {
208
+ platform: process.platform,
209
+ arch: process.arch
210
+ }
211
+ }
212
+ ],
213
+ [
214
+ 'track',
215
+ {
216
+ userId: '53defe995fa47e6c13102d9d',
217
+ event: 'New Connection',
218
+ properties: {
219
+ mongosh_version: '1.0.0',
220
+ session_id: '5fb3c20ee1507e894e5340f3',
221
+ is_localhost: true,
222
+ is_atlas: false,
223
+ node_version: 'v12.19.0'
224
+ }
225
+ }
226
+ ],
227
+ [
228
+ 'track',
229
+ {
230
+ userId: '53defe995fa47e6c13102d9d',
231
+ event: 'Error',
232
+ properties: {
233
+ mongosh_version: '1.0.0',
234
+ name: 'MongoshInvalidInputError',
235
+ code: 'CLIREPL-1005',
236
+ scope: 'CLIREPL',
237
+ metadata: { cause: 'x' }
238
+ }
239
+ }
240
+ ],
241
+ [
242
+ 'track',
243
+ {
244
+ userId: '53defe995fa47e6c13102d9d',
245
+ event: 'Use',
246
+ properties: { mongosh_version: '1.0.0' }
247
+ }
248
+ ],
249
+ [
250
+ 'track',
251
+ {
252
+ userId: '53defe995fa47e6c13102d9d',
253
+ event: 'Show',
254
+ properties: {
255
+ mongosh_version: '1.0.0',
256
+ method: 'dbs'
257
+ }
258
+ }
259
+ ],
260
+ [
261
+ 'track',
262
+ {
263
+ event: 'Script Loaded CLI',
264
+ properties: {
265
+ mongosh_version: '1.0.0',
266
+ nested: true,
267
+ shell: true
268
+ },
269
+ userId: '53defe995fa47e6c13102d9d'
270
+ }
271
+ ],
272
+ [
273
+ 'track',
274
+ {
275
+ event: 'Script Loaded',
276
+ properties: {
277
+ mongosh_version: '1.0.0',
278
+ nested: false
279
+ },
280
+ userId: '53defe995fa47e6c13102d9d'
281
+ }
282
+ ],
283
+ [
284
+ 'track',
285
+ {
286
+ event: 'Mongoshrc Loaded',
287
+ properties: {
288
+ mongosh_version: '1.0.0',
289
+ },
290
+ userId: '53defe995fa47e6c13102d9d'
291
+ }
292
+ ],
293
+ [
294
+ 'track',
295
+ {
296
+ event: 'Mongorc Warning',
297
+ properties: {
298
+ mongosh_version: '1.0.0',
299
+ },
300
+ userId: '53defe995fa47e6c13102d9d'
301
+ }
302
+ ],
303
+ [
304
+ 'track',
305
+ {
306
+ event: 'Script Evaluated',
307
+ properties: {
308
+ mongosh_version: '1.0.0',
309
+ shell: true
310
+ },
311
+ userId: '53defe995fa47e6c13102d9d'
312
+ }
313
+ ],
314
+ [
315
+ 'track',
316
+ {
317
+ userId: '53defe995fa47e6c13102d9d',
318
+ event: 'Snippet Install',
319
+ properties: {
320
+ mongosh_version: '1.0.0'
321
+ }
322
+ }
323
+ ]
324
+ ]);
325
+ });
326
+
327
+ it('buffers deprecated API calls', () => {
328
+ setupLoggerAndTelemetry(bus, logger, () => analytics, {}, '1.0.0');
329
+ expect(logOutput).to.have.lengthOf(0);
330
+ expect(analyticsOutput).to.be.empty;
331
+
332
+ bus.emit('mongosh:new-user', userId, true);
333
+
334
+ logOutput = [];
335
+ analyticsOutput = [];
336
+
337
+ bus.emit('mongosh:deprecated-api-call', { method: 'cloneDatabase', class: 'Database' });
338
+ bus.emit('mongosh:deprecated-api-call', { method: 'cloneDatabase', class: 'Database' });
339
+ bus.emit('mongosh:deprecated-api-call', { method: 'copyDatabase', class: 'Database' });
340
+ bus.emit('mongosh:deprecated-api-call', { method: 'cloneDatabase', class: 'Database' });
341
+
342
+ expect(logOutput).to.be.empty;
343
+ expect(analyticsOutput).to.be.empty;
344
+
345
+ bus.emit('mongosh:evaluate-finished');
346
+ expect(logOutput).to.have.length(2);
347
+ expect(analyticsOutput).to.have.length(2);
348
+
349
+ expect(logOutput[0].msg).to.equal('Deprecated API call');
350
+ expect(logOutput[0].attr).to.deep.equal({ class: 'Database', method: 'cloneDatabase' });
351
+ expect(logOutput[1].msg).to.equal('Deprecated API call');
352
+ expect(logOutput[1].attr).to.deep.equal({ class: 'Database', method: 'copyDatabase' });
353
+ expect(analyticsOutput).to.deep.equal([
354
+ [
355
+ 'track',
356
+ {
357
+ userId: '53defe995fa47e6c13102d9d',
358
+ event: 'Deprecated Method',
359
+ properties: {
360
+ mongosh_version: '1.0.0',
361
+ class: 'Database',
362
+ method: 'cloneDatabase',
363
+ }
364
+ }
365
+ ],
366
+ [
367
+ 'track',
368
+ {
369
+ userId: '53defe995fa47e6c13102d9d',
370
+ event: 'Deprecated Method',
371
+ properties: {
372
+ mongosh_version: '1.0.0',
373
+ class: 'Database',
374
+ method: 'copyDatabase',
375
+ }
376
+ }
377
+ ]
378
+ ]);
379
+
380
+ bus.emit('mongosh:new-user', userId, false);
381
+ logOutput = [];
382
+ analyticsOutput = [];
383
+
384
+ bus.emit('mongosh:deprecated-api-call', { method: 'cloneDatabase', class: 'Database' });
385
+
386
+ expect(logOutput).to.be.empty;
387
+ expect(analyticsOutput).to.be.empty;
388
+
389
+ bus.emit('mongosh:evaluate-finished');
390
+ expect(logOutput).to.have.length(1);
391
+ expect(logOutput[0].msg).to.equal('Deprecated API call');
392
+ expect(logOutput[0].attr).to.deep.equal({ class: 'Database', method: 'cloneDatabase' });
393
+ expect(analyticsOutput).to.be.empty;
394
+ });
395
+
396
+ it('works when analytics are not available', () => {
397
+ setupLoggerAndTelemetry(bus, logger, () => { throw new Error(); }, {}, '1.0.0');
398
+ bus.emit('mongosh:new-user', userId, true);
399
+ expect(analyticsOutput).to.be.empty;
400
+ expect(logOutput).to.have.lengthOf(1);
401
+ expect(logOutput[0].s).to.equal('E');
402
+ });
403
+ });