diff --git a/src/connection_string.ts b/src/connection_string.ts index a1dd0614251..1d178d3b837 100644 --- a/src/connection_string.ts +++ b/src/connection_string.ts @@ -523,6 +523,7 @@ export function parseOptions( MONGODB_LOG_TOPOLOGY: process.env.MONGODB_LOG_TOPOLOGY, MONGODB_LOG_SERVER_SELECTION: process.env.MONGODB_LOG_SERVER_SELECTION, MONGODB_LOG_CONNECTION: process.env.MONGODB_LOG_CONNECTION, + MONGODB_LOG_CLIENT: process.env.MONGODB_LOG_CLIENT, MONGODB_LOG_ALL: process.env.MONGODB_LOG_ALL, MONGODB_LOG_MAX_DOCUMENT_LENGTH: process.env.MONGODB_LOG_MAX_DOCUMENT_LENGTH, MONGODB_LOG_PATH: process.env.MONGODB_LOG_PATH, diff --git a/src/mongo_client.ts b/src/mongo_client.ts index 2122530c7f4..1a06f250d35 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -33,8 +33,13 @@ import type { SrvPoller } from './sdam/srv_polling'; import { Topology, type TopologyEvents } from './sdam/topology'; import { ClientSession, type ClientSessionOptions, ServerSessionPool } from './sessions'; import { + COSMOS_DB_CHECK, + COSMOS_DB_MSG, + DOCUMENT_DB_CHECK, + DOCUMENT_DB_MSG, type HostAddress, hostMatchesWildcards, + isHostMatch, type MongoDBNamespace, ns, resolveOptions @@ -364,6 +369,26 @@ export class MongoClient extends TypedEventEmitter { return true; } }; + this.checkForNonGenuineHosts(); + } + + /** @internal */ + private checkForNonGenuineHosts() { + const documentDBHostnames = this[kOptions].hosts.filter((hostAddress: HostAddress) => + isHostMatch(DOCUMENT_DB_CHECK, hostAddress.host) + ); + const srvHostIsDocumentDB = isHostMatch(DOCUMENT_DB_CHECK, this[kOptions].srvHost); + + const cosmosDBHostnames = this[kOptions].hosts.filter((hostAddress: HostAddress) => + isHostMatch(COSMOS_DB_CHECK, hostAddress.host) + ); + const srvHostIsCosmosDB = isHostMatch(COSMOS_DB_CHECK, this[kOptions].srvHost); + + if (documentDBHostnames.length !== 0 || srvHostIsDocumentDB) { + this.mongoLogger.info('client', DOCUMENT_DB_MSG); + } else if (cosmosDBHostnames.length !== 0 || srvHostIsCosmosDB) { + this.mongoLogger.info('client', COSMOS_DB_MSG); + } } /** @see MongoOptions */ diff --git a/src/mongo_logger.ts b/src/mongo_logger.ts index d5704655eb2..3e26d403690 100644 --- a/src/mongo_logger.ts +++ b/src/mongo_logger.ts @@ -98,7 +98,8 @@ export const MongoLoggableComponent = Object.freeze({ COMMAND: 'command', TOPOLOGY: 'topology', SERVER_SELECTION: 'serverSelection', - CONNECTION: 'connection' + CONNECTION: 'connection', + CLIENT: 'client' } as const); /** @internal */ @@ -115,6 +116,8 @@ export interface MongoLoggerEnvOptions { MONGODB_LOG_SERVER_SELECTION?: string; /** Severity level for CMAP */ MONGODB_LOG_CONNECTION?: string; + /** Severity level for client */ + MONGODB_LOG_CLIENT?: string; /** Default severity level to be if any of the above are unset */ MONGODB_LOG_ALL?: string; /** Max length of embedded EJSON docs. Setting to 0 disables truncation. Defaults to 1000. */ @@ -140,6 +143,8 @@ export interface MongoLoggerOptions { serverSelection: SeverityLevel; /** Severity level for connection component */ connection: SeverityLevel; + /** Severity level for client component */ + client: SeverityLevel; /** Default severity level to be used if any of the above are unset */ default: SeverityLevel; }; @@ -528,6 +533,7 @@ export class MongoLogger { parseSeverityFromString(combinedOptions.MONGODB_LOG_SERVER_SELECTION) ?? defaultSeverity, connection: parseSeverityFromString(combinedOptions.MONGODB_LOG_CONNECTION) ?? defaultSeverity, + client: parseSeverityFromString(combinedOptions.MONGODB_LOG_CLIENT) ?? defaultSeverity, default: defaultSeverity }, maxDocumentLength: diff --git a/src/utils.ts b/src/utils.ts index 8ae7f26381f..8bf4425c18b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1282,3 +1282,20 @@ export class TimeoutController extends AbortController { this.timeoutId = null; } } + +/** @internal */ +export const DOCUMENT_DB_CHECK = /(\.docdb\.amazonaws\.com$)|(\.docdb-elastic\.amazonaws\.com$)/; +/** @internal */ +export const COSMOS_DB_CHECK = /\.cosmos\.azure\.com$/; + +/** @internal */ +export const DOCUMENT_DB_MSG = + 'You appear to be connected to a DocumentDB cluster. For more information regarding feature compatibility and support please visit https://www.mongodb.com/supportability/documentdb'; +/** @internal */ +export const COSMOS_DB_MSG = + 'You appear to be connected to a CosmosDB cluster. For more information regarding feature compatibility and support please visit https://www.mongodb.com/supportability/cosmosdb'; + +/** @internal */ +export function isHostMatch(match: RegExp, host?: string): boolean { + return host && match.test(host.toLowerCase()) ? true : false; +} diff --git a/test/unit/connection_string.test.ts b/test/unit/connection_string.test.ts index 372e9f27b54..1d9c8eae79d 100644 --- a/test/unit/connection_string.test.ts +++ b/test/unit/connection_string.test.ts @@ -9,7 +9,9 @@ import { inspect } from 'util'; import { AUTH_MECHS_AUTH_SRC_EXTERNAL, AuthMechanism, + COSMOS_DB_MSG, DEFAULT_ALLOWED_HOSTS, + DOCUMENT_DB_MSG, FEATURE_FLAGS, type Log, MongoAPIError, @@ -883,4 +885,58 @@ describe('Connection String', function () { }); }); }); + + describe('non-genuine hosts', () => { + beforeEach(() => { + process.env.MONGODB_LOG_CLIENT = 'info'; + }); + + afterEach(() => { + process.env.MONGODB_LOG_CLIENT = undefined; + }); + + const loggerFeatureFlag = Symbol.for('@@mdb.enableMongoLogger'); + const test_cases = [ + ['non-SRV example uri', 'mongodb://a.example.com:27017,b.example.com:27017/', ''], + ['non-SRV default uri', 'mongodb://a.mongodb.net:27017', ''], + ['SRV example uri', 'mongodb+srv://a.example.com/', ''], + ['SRV default uri', 'mongodb+srv://a.mongodb.net/', ''], + // ensure case insensitity + ['non-SRV cosmosDB uri', 'mongodb://a.mongo.COSmos.aZure.com:19555/', COSMOS_DB_MSG], + ['non-SRV documentDB uri', 'mongodb://a.docDB.AmazonAws.com:27017/', DOCUMENT_DB_MSG], + [ + 'non-SRV documentDB uri ', + 'mongodb://a.docdB-eLasTic.amazonaws.com:27017/', + DOCUMENT_DB_MSG + ], + ['SRV cosmosDB uri', 'mongodb+srv://a.mongo.COSmos.aZure.com/', COSMOS_DB_MSG], + ['SRV documentDB uri', 'mongodb+srv://a.docDB.AmazonAws.com/', DOCUMENT_DB_MSG], + ['SRV documentDB uri 2', 'mongodb+srv://a.docdB-eLastic.amazonaws.com/', DOCUMENT_DB_MSG] + ]; + + context('when logging is turned on', () => { + for (const [name, uri, message] of test_cases) { + it(`${name} triggers ${message.length === 0 ? 'no' : 'correct info'} msg`, () => { + const stream = { + buffer: [], + write(log) { + this.buffer.push(log); + } + }; + new MongoClient(uri, { + [loggerFeatureFlag]: true, + mongodbLogPath: stream + }); + + if (message.length > 0) { + expect(stream.buffer).to.have.lengthOf(1); + expect(stream.buffer[0]).to.have.property('c', 'client'); + expect(stream.buffer[0]).to.have.property('message', message); + } else { + expect(stream.buffer).to.have.lengthOf(0); + } + }); + } + }); + }); }); diff --git a/test/unit/mongo_logger.test.ts b/test/unit/mongo_logger.test.ts index ee8d3a2bd00..0d419164536 100644 --- a/test/unit/mongo_logger.test.ts +++ b/test/unit/mongo_logger.test.ts @@ -116,7 +116,8 @@ describe('class MongoLogger', function () { ['MONGODB_LOG_COMMAND', 'command'], ['MONGODB_LOG_TOPOLOGY', 'topology'], ['MONGODB_LOG_SERVER_SELECTION', 'serverSelection'], - ['MONGODB_LOG_CONNECTION', 'connection'] + ['MONGODB_LOG_CONNECTION', 'connection'], + ['MONGODB_LOG_CLIENT', 'client'] ]); function* makeValidOptions(): Generator<[string, string]> {