Skip to content

Commit 8bd9777

Browse files
authored
fix: use emitWarning API for internal messages (#2743)
Updates all logging statements to use node's process.emitWarning API. Allows for filtering, capturing and silencing of warnings. NODE-2317, NODE-3114
1 parent d67ffa7 commit 8bd9777

File tree

18 files changed

+138
-47
lines changed

18 files changed

+138
-47
lines changed

.eslintrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"es/no-destructuring": "error",
2222
"es/no-rest-spread-properties": "error",
2323
"es/no-spread-elements": "error",
24-
"no-console": "off",
24+
"no-console": "error",
2525
"eqeqeq": ["error", "always", { "null": "ignore" }],
2626
"strict": ["error", "global"]
2727
},

lib/collection.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const deprecate = require('util').deprecate;
44
const deprecateOptions = require('./utils').deprecateOptions;
5+
const emitWarningOnce = require('./utils').emitWarningOnce;
56
const checkCollectionName = require('./utils').checkCollectionName;
67
const ObjectID = require('./core').BSON.ObjectID;
78
const MongoError = require('./core').MongoError;
@@ -323,7 +324,7 @@ Collection.prototype.find = deprecateOptions(
323324
function(query, options, callback) {
324325
if (typeof callback === 'object') {
325326
// TODO(MAJOR): throw in the future
326-
console.warn('Third parameter to `find()` must be a callback or undefined');
327+
emitWarningOnce('Third parameter to `find()` must be a callback or undefined');
327328
}
328329

329330
let selector = query;
@@ -1092,7 +1093,7 @@ Collection.prototype.findOne = deprecateOptions(
10921093
function(query, options, callback) {
10931094
if (typeof callback === 'object') {
10941095
// TODO(MAJOR): throw in the future
1095-
console.warn('Third parameter to `findOne()` must be a callback or undefined');
1096+
emitWarningOnce('Third parameter to `findOne()` must be a callback or undefined');
10961097
}
10971098

10981099
if (typeof query === 'function') (callback = query), (query = {}), (options = {});

lib/core/auth/scram.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const Buffer = require('safe-buffer').Buffer;
44
const retrieveBSON = require('../connection/utils').retrieveBSON;
55
const MongoError = require('../error').MongoError;
66
const AuthProvider = require('./auth_provider').AuthProvider;
7+
const emitWarningOnce = require('../../utils').emitWarning;
78

89
const BSON = retrieveBSON();
910
const Binary = BSON.Binary;
@@ -24,7 +25,7 @@ class ScramSHA extends AuthProvider {
2425
prepare(handshakeDoc, authContext, callback) {
2526
const cryptoMethod = this.cryptoMethod;
2627
if (cryptoMethod === 'sha256' && saslprep == null) {
27-
console.warn('Warning: no saslprep library specified. Passwords will not be sanitized');
28+
emitWarningOnce('Warning: no saslprep library specified. Passwords will not be sanitized');
2829
}
2930

3031
crypto.randomBytes(24, (err, nonce) => {

lib/core/connection/logger.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ var Logger = function(className, options) {
3737
if (options.logger) {
3838
currentLogger = options.logger;
3939
} else if (currentLogger == null) {
40+
// eslint-disable-next-line no-console
4041
currentLogger = console.log;
4142
}
4243

lib/core/connection/msg.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const Buffer = require('safe-buffer').Buffer;
3131
const opcodes = require('../wireprotocol/shared').opcodes;
3232
const databaseNamespace = require('../wireprotocol/shared').databaseNamespace;
3333
const ReadPreference = require('../topologies/read_preference');
34+
const MongoError = require('../../core/error').MongoError;
3435

3536
// Incrementing request id
3637
let _requestId = 0;
@@ -196,7 +197,8 @@ class BinMsg {
196197
while (this.index < this.data.length) {
197198
const payloadType = this.data.readUInt8(this.index++);
198199
if (payloadType === 1) {
199-
console.error('TYPE 1');
200+
// It was decided that no driver makes use of payload type 1
201+
throw new MongoError('OP_MSG Payload Type 1 detected unsupported protocol');
200202
} else if (payloadType === 0) {
201203
const bsonSize = this.data.readUInt32LE(this.index);
202204
const bin = this.data.slice(this.index, this.index + bsonSize);

lib/core/sdam/topology.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const ServerSessionPool = require('../sessions').ServerSessionPool;
2727
const makeClientMetadata = require('../utils').makeClientMetadata;
2828
const CMAP_EVENT_NAMES = require('../../cmap/events').CMAP_EVENT_NAMES;
2929
const compareTopologyVersion = require('./server_description').compareTopologyVersion;
30+
const emitWarning = require('../../utils').emitWarning;
3031

3132
const common = require('./common');
3233
const drainTimerQueue = common.drainTimerQueue;
@@ -739,7 +740,7 @@ class Topology extends EventEmitter {
739740
}
740741

741742
unref() {
742-
console.log('not implemented: `unref`');
743+
emitWarning('not implemented: `unref`');
743744
}
744745

745746
// NOTE: There are many places in code where we explicitly check the last isMaster

lib/core/tools/smoke_plugin.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ exports.attachToRunner = function(runner, outputFile) {
5252
fs.writeFileSync(outputFile, JSON.stringify(smokeOutput));
5353

5454
// Standard NodeJS uncaught exception handler
55+
// eslint-disable-next-line no-console
5556
console.error(err.stack);
5657
process.exit(1);
5758
});

lib/core/topologies/read_preference.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
'use strict';
2+
const emitWarningOnce = require('../../utils').emitWarningOnce;
23

34
/**
45
* The **ReadPreference** class is a class that represents a MongoDB ReadPreference and is
@@ -20,7 +21,7 @@ const ReadPreference = function(mode, tags, options) {
2021

2122
// TODO(major): tags MUST be an array of tagsets
2223
if (tags && !Array.isArray(tags)) {
23-
console.warn(
24+
emitWarningOnce(
2425
'ReadPreference tags must be an array, this will change in the next major version'
2526
);
2627

lib/core/uri_parser.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const qs = require('querystring');
44
const dns = require('dns');
55
const MongoParseError = require('./error').MongoParseError;
66
const ReadPreference = require('./topologies/read_preference');
7+
const emitWarningOnce = require('../utils').emitWarningOnce;
78

89
/**
910
* The following regular expression validates a connection string and breaks the
@@ -438,7 +439,7 @@ function parseQueryString(query, options) {
438439
// special cases for known deprecated options
439440
if (result.wtimeout && result.wtimeoutms) {
440441
delete result.wtimeout;
441-
console.warn('Unsupported option `wtimeout` specified');
442+
emitWarningOnce('Unsupported option `wtimeout` specified');
442443
}
443444

444445
return Object.keys(result).length ? result : null;

lib/core/wireprotocol/kill_cursors.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const MongoError = require('../error').MongoError;
55
const MongoNetworkError = require('../error').MongoNetworkError;
66
const collectionNamespace = require('./shared').collectionNamespace;
77
const maxWireVersion = require('../utils').maxWireVersion;
8+
const emitWarning = require('../utils').emitWarning;
89
const command = require('./command');
910

1011
function killCursors(server, ns, cursorState, callback) {
@@ -31,7 +32,7 @@ function killCursors(server, ns, cursorState, callback) {
3132
if (typeof callback === 'function') {
3233
callback(err, null);
3334
} else {
34-
console.warn(err);
35+
emitWarning(err);
3536
}
3637
}
3738
}

lib/db.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const MongoError = require('./core').MongoError;
1212
const ObjectID = require('./core').ObjectID;
1313
const Logger = require('./core').Logger;
1414
const Collection = require('./collection');
15-
const mergeOptionsAndWriteConcern = require('./utils').mergeOptionsAndWriteConcern;
15+
const conditionallyMergeWriteConcern = require('./utils').conditionallyMergeWriteConcern;
1616
const executeLegacyOperation = require('./utils').executeLegacyOperation;
1717
const ChangeStream = require('./change_stream');
1818
const deprecate = require('util').deprecate;
@@ -382,7 +382,7 @@ Db.prototype.admin = function() {
382382
* @param {AggregationCursor} cursor The cursor if the aggregation command was executed successfully.
383383
*/
384384

385-
const collectionKeys = [
385+
const COLLECTION_OPTION_KEYS = [
386386
'pkFactory',
387387
'readPreference',
388388
'serializeFunctions',
@@ -433,8 +433,14 @@ Db.prototype.collection = function(name, options, callback) {
433433
options.ignoreUndefined = this.s.options.ignoreUndefined;
434434
}
435435

436+
for (const collectionOptionKey of COLLECTION_OPTION_KEYS) {
437+
if (!(collectionOptionKey in options) && this.s.options[collectionOptionKey] !== undefined) {
438+
options[collectionOptionKey] = this.s.options[collectionOptionKey];
439+
}
440+
}
441+
436442
// Merge in all needed options and ensure correct writeConcern merging from db level
437-
options = mergeOptionsAndWriteConcern(options, this.s.options, collectionKeys, true);
443+
options = conditionallyMergeWriteConcern(options, this.s.options, COLLECTION_OPTION_KEYS, true);
438444

439445
// Execute
440446
if (options == null || !options.strict) {

lib/operations/add_user.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const defineAspects = require('./operation').defineAspects;
66
const crypto = require('crypto');
77
const handleCallback = require('../utils').handleCallback;
88
const toError = require('../utils').toError;
9+
const emitWarning = require('../utils').emitWarning;
910

1011
class AddUserOperation extends CommandOperation {
1112
constructor(db, username, password, options) {
@@ -29,7 +30,7 @@ class AddUserOperation extends CommandOperation {
2930
// If not roles defined print deprecated message
3031
// TODO: handle deprecation properly
3132
if (roles.length === 0) {
32-
console.log('Creating a user without roles is deprecated in MongoDB >= 2.6');
33+
emitWarning('Creating a user without roles is deprecated in MongoDB >= 2.6');
3334
}
3435

3536
// Check the db name and add roles if needed

lib/operations/connect.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const ReplSet = require('../topologies/replset');
1313
const Server = require('../topologies/server');
1414
const ServerSessionPool = require('../core').Sessions.ServerSessionPool;
1515
const emitDeprecationWarning = require('../utils').emitDeprecationWarning;
16+
const emitWarningOnce = require('../utils').emitWarningOnce;
1617
const fs = require('fs');
1718
const WriteConcern = require('../write_concern');
1819
const BSON = require('../core/connection/utils').retrieveBSON();
@@ -182,12 +183,12 @@ function validOptions(options) {
182183
if (options.validateOptions) {
183184
return new MongoError(`option ${name} is not supported`);
184185
} else {
185-
console.warn(`the options [${name}] is not supported`);
186+
emitWarningOnce(`the options [${name}] is not supported`);
186187
}
187188
}
188189

189190
if (legacyOptionNames.indexOf(name) !== -1) {
190-
console.warn(
191+
emitWarningOnce(
191192
`the server/replset/mongos/db options are deprecated, ` +
192193
`all their options are supported at the top level of the options object [${validOptionNames}]`
193194
);
@@ -257,9 +258,6 @@ function resolveTLSOptions(options) {
257258
});
258259
}
259260

260-
const emitDeprecationForNonUnifiedTopology = deprecate(() => {},
261-
'current Server Discovery and Monitoring engine is deprecated, and will be removed in a future version. ' + 'To use the new Server Discover and Monitoring engine, pass option { useUnifiedTopology: true } to the MongoClient constructor.');
262-
263261
function connect(mongoClient, url, options, callback) {
264262
options = Object.assign({}, options);
265263

@@ -335,7 +333,9 @@ function connect(mongoClient, url, options, callback) {
335333
return createTopology(mongoClient, 'unified', _finalOptions, connectCallback);
336334
}
337335

338-
emitDeprecationForNonUnifiedTopology();
336+
emitWarningOnce(
337+
'Current Server Discovery and Monitoring engine is deprecated, and will be removed in a future version. To use the new Server Discover and Monitoring engine, pass option { useUnifiedTopology: true } to the MongoClient constructor.'
338+
);
339339

340340
// Do we have a replicaset then skip discovery and go straight to connectivity
341341
if (_finalOptions.replicaSet || _finalOptions.rs_name) {

lib/utils.js

Lines changed: 71 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -287,39 +287,38 @@ var filterOptions = function(options, names) {
287287
};
288288

289289
// Write concern keys
290-
var writeConcernKeys = ['w', 'j', 'wtimeout', 'fsync'];
290+
const WRITE_CONCERN_KEYS = ['w', 'j', 'wtimeout', 'fsync', 'writeConcern'];
291291

292-
// Merge the write concern options
293-
var mergeOptionsAndWriteConcern = function(targetOptions, sourceOptions, keys, mergeWriteConcern) {
294-
// Mix in any allowed options
295-
for (var i = 0; i < keys.length; i++) {
296-
if (!targetOptions[keys[i]] && sourceOptions[keys[i]] !== undefined) {
297-
targetOptions[keys[i]] = sourceOptions[keys[i]];
298-
}
299-
}
300-
301-
// No merging of write concern
302-
if (!mergeWriteConcern) return targetOptions;
303-
304-
// Found no write Concern options
305-
var found = false;
306-
for (i = 0; i < writeConcernKeys.length; i++) {
307-
if (targetOptions[writeConcernKeys[i]]) {
292+
/**
293+
* If there is no WriteConcern related options defined on target then inherit from source.
294+
* Otherwise, do not inherit **any** options from source.
295+
* @internal
296+
* @param {object} target - options object conditionally receiving the writeConcern options
297+
* @param {object} source - options object containing the potentially inherited writeConcern options
298+
*/
299+
function conditionallyMergeWriteConcern(target, source) {
300+
let found = false;
301+
for (const wcKey of WRITE_CONCERN_KEYS) {
302+
if (wcKey in target) {
303+
// Found a writeConcern option
308304
found = true;
309305
break;
310306
}
311307
}
312308

313309
if (!found) {
314-
for (i = 0; i < writeConcernKeys.length; i++) {
315-
if (sourceOptions[writeConcernKeys[i]]) {
316-
targetOptions[writeConcernKeys[i]] = sourceOptions[writeConcernKeys[i]];
310+
for (const wcKey of WRITE_CONCERN_KEYS) {
311+
if (source[wcKey]) {
312+
if (!('writeConcern' in target)) {
313+
target.writeConcern = {};
314+
}
315+
target.writeConcern[wcKey] = source[wcKey];
317316
}
318317
}
319318
}
320319

321-
return targetOptions;
322-
};
320+
return target;
321+
}
323322

324323
/**
325324
* Executes the given operation with provided arguments.
@@ -534,7 +533,9 @@ function decorateWithExplain(command, explain) {
534533
return { explain: command, verbosity: explain.verbosity };
535534
}
536535

537-
const emitProcessWarning = msg => process.emitWarning(msg, 'DeprecationWarning');
536+
const emitProcessWarning = msg =>
537+
process.emitWarning(msg, { type: 'DeprecationWarning', code: MONGODB_WARNING_CODE });
538+
// eslint-disable-next-line no-console
538539
const emitConsoleWarning = msg => console.error(msg);
539540
const emitDeprecationWarning = process.emitWarning ? emitProcessWarning : emitConsoleWarning;
540541

@@ -816,6 +817,48 @@ function hasAtomicOperators(doc) {
816817
);
817818
}
818819

820+
/**
821+
* When the driver used emitWarning the code will be equal to this.
822+
* @public
823+
*
824+
* @example
825+
* ```js
826+
* process.on('warning', (warning) => {
827+
* if (warning.code === MONGODB_WARNING_CODE) console.error('Ah an important warning! :)')
828+
* })
829+
* ```
830+
*/
831+
const MONGODB_WARNING_CODE = 'MONGODB DRIVER';
832+
833+
/**
834+
* @internal
835+
* @param {string} message - message to warn about
836+
*/
837+
function emitWarning(message) {
838+
if (process.emitWarning) {
839+
return process.emitWarning(message, { code: MONGODB_WARNING_CODE });
840+
} else {
841+
// Approximate the style of print out on node versions pre 8.x
842+
// eslint-disable-next-line no-console
843+
return console.error(`[${MONGODB_WARNING_CODE}] Warning:`, message);
844+
}
845+
}
846+
847+
const emittedWarnings = new Set();
848+
/**
849+
* Will emit a warning once for the duration of the application.
850+
* Uses the message to identify if it has already been emitted
851+
* so using string interpolation can cause multiple emits
852+
* @internal
853+
* @param {string} message - message to warn about
854+
*/
855+
function emitWarningOnce(message) {
856+
if (!emittedWarnings.has(message)) {
857+
emittedWarnings.add(message);
858+
return emitWarning(message);
859+
}
860+
}
861+
819862
module.exports = {
820863
filterOptions,
821864
mergeOptions,
@@ -832,7 +875,7 @@ module.exports = {
832875
isObject,
833876
debugOptions,
834877
MAX_JS_INT: Number.MAX_SAFE_INTEGER + 1,
835-
mergeOptionsAndWriteConcern,
878+
conditionallyMergeWriteConcern,
836879
executeLegacyOperation,
837880
applyRetryableWrites,
838881
applyWriteConcern,
@@ -849,5 +892,8 @@ module.exports = {
849892
now,
850893
calculateDurationInMs,
851894
makeInterruptableAsyncInterval,
852-
hasAtomicOperators
895+
hasAtomicOperators,
896+
MONGODB_WARNING_CODE,
897+
emitWarning,
898+
emitWarningOnce
853899
};

0 commit comments

Comments
 (0)