diff --git a/src/cmap/connection.ts b/src/cmap/connection.ts index e036457a586..3406fed594b 100644 --- a/src/cmap/connection.ts +++ b/src/cmap/connection.ts @@ -280,7 +280,7 @@ export class Connection extends TypedEventEmitter { (this.monitorCommands || (this.established && !this.authContext?.reauthenticating && - this.mongoLogger?.willLog(SeverityLevel.DEBUG, MongoLoggableComponent.COMMAND))) ?? + this.mongoLogger?.willLog(MongoLoggableComponent.COMMAND, SeverityLevel.DEBUG))) ?? false ); } diff --git a/src/cmap/connection_pool.ts b/src/cmap/connection_pool.ts index 6071794f602..4fe5249738f 100644 --- a/src/cmap/connection_pool.ts +++ b/src/cmap/connection_pool.ts @@ -251,7 +251,7 @@ export class ConnectionPool extends TypedEventEmitter { this[kMetrics] = new ConnectionPoolMetrics(); this[kProcessingWaitQueue] = false; - this.mongoLogger = this[kServer].topology.client.mongoLogger; + this.mongoLogger = this[kServer].topology.client?.mongoLogger; this.component = 'connection'; process.nextTick(() => { diff --git a/src/mongo_client.ts b/src/mongo_client.ts index a8be513d176..476a912aece 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -25,7 +25,8 @@ import { type LogComponentSeveritiesClientOptions, type MongoDBLogWritable, MongoLogger, - type MongoLoggerOptions + type MongoLoggerOptions, + SeverityLevel } from './mongo_logger'; import { TypedEventEmitter } from './mongo_types'; import { executeOperation } from './operations/execute_operation'; @@ -345,7 +346,7 @@ export class MongoClient extends TypedEventEmitter { /** @internal */ topology?: Topology; /** @internal */ - override readonly mongoLogger: MongoLogger; + override readonly mongoLogger: MongoLogger | undefined; /** @internal */ private connectionLock?: Promise; @@ -359,7 +360,13 @@ export class MongoClient extends TypedEventEmitter { super(); this[kOptions] = parseOptions(url, this, options); - this.mongoLogger = new MongoLogger(this[kOptions].mongoLoggerOptions); + + const shouldSetLogger = Object.values( + this[kOptions].mongoLoggerOptions.componentSeverities + ).some(value => value !== SeverityLevel.OFF); + this.mongoLogger = shouldSetLogger + ? new MongoLogger(this[kOptions].mongoLoggerOptions) + : undefined; // eslint-disable-next-line @typescript-eslint/no-this-alias const client = this; @@ -405,9 +412,9 @@ export class MongoClient extends TypedEventEmitter { const srvHostIsCosmosDB = isHostMatch(COSMOS_DB_CHECK, this[kOptions].srvHost); if (documentDBHostnames.length !== 0 || srvHostIsDocumentDB) { - this.mongoLogger.info('client', DOCUMENT_DB_MSG); + this.mongoLogger?.info('client', DOCUMENT_DB_MSG); } else if (cosmosDBHostnames.length !== 0 || srvHostIsCosmosDB) { - this.mongoLogger.info('client', COSMOS_DB_MSG); + this.mongoLogger?.info('client', COSMOS_DB_MSG); } } diff --git a/src/mongo_logger.ts b/src/mongo_logger.ts index 57864c4c4ef..27fcbf8d308 100644 --- a/src/mongo_logger.ts +++ b/src/mongo_logger.ts @@ -728,6 +728,7 @@ export class MongoLogger { logDestination: MongoDBLogWritable; logDestinationIsStdErr: boolean; pendingLog: PromiseLike | unknown = null; + private severities: Record>; /** * This method should be used when logging errors that do not have a public driver API for @@ -760,15 +761,27 @@ export class MongoLogger { this.maxDocumentLength = options.maxDocumentLength; this.logDestination = options.logDestination; this.logDestinationIsStdErr = options.logDestinationIsStdErr; + this.severities = this.createLoggingSeverities(); } - willLog(severity: SeverityLevel, component: MongoLoggableComponent): boolean { - return compareSeverity(severity, this.componentSeverities[component]) <= 0; + createLoggingSeverities(): Record> { + const severities = Object(); + for (const component of Object.values(MongoLoggableComponent)) { + severities[component] = {}; + for (const severityLevel of Object.values(SeverityLevel)) { + severities[component][severityLevel] = + compareSeverity(severityLevel, this.componentSeverities[component]) <= 0; + } + } + return severities; } turnOffSeverities() { - for (const key of Object.values(MongoLoggableComponent)) { - this.componentSeverities[key as MongoLoggableComponent] = SeverityLevel.OFF; + for (const component of Object.values(MongoLoggableComponent)) { + this.componentSeverities[component] = SeverityLevel.OFF; + for (const severityLevel of Object.values(SeverityLevel)) { + this.severities[component][severityLevel] = false; + } } } @@ -797,12 +810,17 @@ export class MongoLogger { this.pendingLog = null; } + willLog(component: MongoLoggableComponent, severity: SeverityLevel): boolean { + if (severity === SeverityLevel.OFF) return false; + return this.severities[component][severity]; + } + private log( severity: SeverityLevel, component: MongoLoggableComponent, message: Loggable | string ): void { - if (!this.willLog(severity, component)) return; + if (!this.willLog(component, severity)) return; let logMessage: Log = { t: new Date(), c: component, s: severity }; if (typeof message === 'string') { diff --git a/src/sdam/monitor.ts b/src/sdam/monitor.ts index a85403c7461..6accf8cd3c1 100644 --- a/src/sdam/monitor.ts +++ b/src/sdam/monitor.ts @@ -120,7 +120,7 @@ export class Monitor extends TypedEventEmitter { serverMonitoringMode: options.serverMonitoringMode }); this.isRunningInFaasEnv = getFAASEnv() != null; - this.mongoLogger = this[kServer].topology.client.mongoLogger; + this.mongoLogger = this[kServer].topology.client?.mongoLogger; const cancellationToken = this[kCancellationToken]; // TODO: refactor this to pull it directly from the pool, requires new ConnectionPool integration diff --git a/src/sdam/topology.ts b/src/sdam/topology.ts index e5c318cb7b4..2526aad535b 100644 --- a/src/sdam/topology.ts +++ b/src/sdam/topology.ts @@ -102,7 +102,7 @@ export type ServerSelectionCallback = Callback; export interface ServerSelectionRequest { serverSelector: ServerSelector; topologyDescription: TopologyDescription; - mongoLogger: MongoLogger; + mongoLogger: MongoLogger | undefined; transaction?: Transaction; startTime: number; callback: ServerSelectionCallback; @@ -568,7 +568,7 @@ export class Topology extends TypedEventEmitter { } options = { serverSelectionTimeoutMS: this.s.serverSelectionTimeoutMS, ...options }; - this.client.mongoLogger.debug( + this.client.mongoLogger?.debug( MongoLoggableComponent.SERVER_SELECTION, new ServerSelectionStartedEvent(selector, this.description, options.operationName) ); @@ -578,7 +578,7 @@ export class Topology extends TypedEventEmitter { const transaction = session && session.transaction; if (isSharded && transaction && transaction.server) { - this.client.mongoLogger.debug( + this.client.mongoLogger?.debug( MongoLoggableComponent.SERVER_SELECTION, new ServerSelectionSucceededEvent( selector, @@ -611,7 +611,7 @@ export class Topology extends TypedEventEmitter { `Server selection timed out after ${options.serverSelectionTimeoutMS} ms`, this.description ); - this.client.mongoLogger.debug( + this.client.mongoLogger?.debug( MongoLoggableComponent.SERVER_SELECTION, new ServerSelectionFailedEvent( selector, @@ -896,7 +896,7 @@ function drainWaitQueue(queue: List, err?: MongoDriverEr if (!waitQueueMember[kCancelled]) { if (err) { - waitQueueMember.mongoLogger.debug( + waitQueueMember.mongoLogger?.debug( MongoLoggableComponent.SERVER_SELECTION, new ServerSelectionFailedEvent( waitQueueMember.serverSelector, @@ -943,7 +943,7 @@ function processWaitQueue(topology: Topology) { : serverDescriptions; } catch (e) { waitQueueMember.timeoutController.clear(); - topology.client.mongoLogger.debug( + topology.client.mongoLogger?.debug( MongoLoggableComponent.SERVER_SELECTION, new ServerSelectionFailedEvent( waitQueueMember.serverSelector, @@ -959,7 +959,7 @@ function processWaitQueue(topology: Topology) { let selectedServer: Server | undefined; if (selectedDescriptions.length === 0) { if (!waitQueueMember.waitingLogged) { - topology.client.mongoLogger.info( + topology.client.mongoLogger?.info( MongoLoggableComponent.SERVER_SELECTION, new WaitingForSuitableServerEvent( waitQueueMember.serverSelector, @@ -992,7 +992,7 @@ function processWaitQueue(topology: Topology) { 'server selection returned a server description but the server was not found in the topology', topology.description ); - topology.client.mongoLogger.debug( + topology.client.mongoLogger?.debug( MongoLoggableComponent.SERVER_SELECTION, new ServerSelectionFailedEvent( waitQueueMember.serverSelector, @@ -1011,7 +1011,7 @@ function processWaitQueue(topology: Topology) { waitQueueMember.timeoutController.clear(); - topology.client.mongoLogger.debug( + topology.client.mongoLogger?.debug( MongoLoggableComponent.SERVER_SELECTION, new ServerSelectionSucceededEvent( waitQueueMember.serverSelector, diff --git a/test/integration/node-specific/feature_flags.test.ts b/test/integration/node-specific/feature_flags.test.ts index cafdd5cc665..6bfeef0f8e3 100644 --- a/test/integration/node-specific/feature_flags.test.ts +++ b/test/integration/node-specific/feature_flags.test.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { MongoClient, type MongoLoggableComponent, SeverityLevel } from '../../mongodb'; +import { MongoClient, SeverityLevel } from '../../mongodb'; describe('Feature Flags', () => { describe('@@mdb.skipPingOnConnect', () => { @@ -45,16 +45,10 @@ describe('Feature Flags', () => { } }); + // TODO(NODE-5672): Release Standardized Logger describe('@@mdb.enableMongoLogger', () => { let cachedEnv; const loggerFeatureFlag = Symbol.for('@@mdb.enableMongoLogger'); - const components: Array = [ - 'default', - 'topology', - 'serverSelection', - 'connection', - 'command' - ]; before(() => { cachedEnv = process.env; @@ -74,7 +68,7 @@ describe('Feature Flags', () => { const client = new MongoClient('mongodb://localhost:27017', { [loggerFeatureFlag]: true }); - expect(client.mongoLogger.componentSeverities).to.have.property( + expect(client.mongoLogger?.componentSeverities).to.have.property( 'command', SeverityLevel.EMERGENCY ); @@ -86,16 +80,11 @@ describe('Feature Flags', () => { process.env = {}; }); - it('does not enable logging for any component', () => { + it('does not create logger', () => { const client = new MongoClient('mongodb://localhost:27017', { [loggerFeatureFlag]: true }); - for (const component of components) { - expect(client.mongoLogger.componentSeverities).to.have.property( - component, - SeverityLevel.OFF - ); - } + expect(client.mongoLogger).to.not.exist; }); }); }); @@ -107,16 +96,11 @@ describe('Feature Flags', () => { process.env['MONGODB_LOG_COMMAND'] = SeverityLevel.EMERGENCY; }); - it('does not enable logging', () => { + it('does not instantiate logger', () => { const client = new MongoClient('mongodb://localhost:27017', { [loggerFeatureFlag]: featureFlagValue }); - for (const component of components) { - expect(client.mongoLogger.componentSeverities).to.have.property( - component, - SeverityLevel.OFF - ); - } + expect(client.mongoLogger).to.not.exist; }); }); @@ -125,16 +109,11 @@ describe('Feature Flags', () => { process.env = {}; }); - it('does not enable logging', () => { + it('does not instantiate logger', () => { const client = new MongoClient('mongodb://localhost:27017', { [loggerFeatureFlag]: featureFlagValue }); - for (const component of components) { - expect(client.mongoLogger.componentSeverities).to.have.property( - component, - SeverityLevel.OFF - ); - } + expect(client.mongoLogger).to.not.exist; }); }); }); @@ -162,7 +141,7 @@ describe('Feature Flags', () => { [Symbol.for('@@mdb.internalLoggerConfig')]: undefined }); - expect(client.mongoLogger.componentSeverities).to.have.property( + expect(client.mongoLogger?.componentSeverities).to.have.property( 'command', SeverityLevel.EMERGENCY ); @@ -178,7 +157,7 @@ describe('Feature Flags', () => { } }); - expect(client.mongoLogger.componentSeverities).to.have.property( + expect(client.mongoLogger?.componentSeverities).to.have.property( 'command', SeverityLevel.ALERT ); diff --git a/test/unit/mongo_client.test.js b/test/unit/mongo_client.test.js index d325b073568..c9c3c9923fc 100644 --- a/test/unit/mongo_client.test.js +++ b/test/unit/mongo_client.test.js @@ -800,7 +800,10 @@ describe('MongoOptions', function () { context('loggingOptions', function () { const expectedLoggingObject = { maxDocumentLength: 20, - logDestination: new Writable() + logDestination: new Writable(), + componentSeverities: Object.fromEntries( + Object.values(MongoLoggableComponent).map(([value]) => [value, SeverityLevel.OFF]) + ) }; before(() => { @@ -1075,9 +1078,9 @@ describe('MongoOptions', function () { ).to.not.throw(MongoAPIError); const client = new MongoClient('mongodb://a/', { [loggerFeatureFlag]: true, - mongodbLogComponentSeverities: {} + mongodbLogComponentSeverities: { client: 'error' } // dummy so logger doesn't turn on }); - expect(client.mongoLogger.componentSeverities.default).to.equal('off'); + expect(client.mongoLogger?.componentSeverities.command).to.equal('off'); }); }); context('when valid client option is provided', function () { @@ -1091,9 +1094,9 @@ describe('MongoOptions', function () { ).to.not.throw(MongoAPIError); const client = new MongoClient('mongodb://a/', { [loggerFeatureFlag]: true, - mongodbLogComponentSeverities: { default: 'emergency' } + mongodbLogComponentSeverities: { command: 'emergency' } }); - expect(client.mongoLogger.componentSeverities.default).to.equal('emergency'); + expect(client.mongoLogger?.componentSeverities.command).to.equal('emergency'); }); }); }); diff --git a/test/unit/mongo_logger.test.ts b/test/unit/mongo_logger.test.ts index 0d60cf4d076..2e9cecf66d6 100644 --- a/test/unit/mongo_logger.test.ts +++ b/test/unit/mongo_logger.test.ts @@ -23,6 +23,7 @@ import { DEFAULT_MAX_DOCUMENT_LENGTH, type Log, type MongoDBLogWritable, + MongoLoggableComponent, MongoLogger, type MongoLoggerOptions, parseSeverityFromString, @@ -1548,4 +1549,70 @@ describe('class MongoLogger', async function () { }); }); }); + + describe('#willLog', function () { + const severityLevels = Object.values(SeverityLevel); + for (const severityLevel of severityLevels) { + context(`when the severity level is ${severityLevel}`, function () { + let logger: MongoLogger; + let componentSeverities; + const components = Object.values(MongoLoggableComponent); + + for (const component of components) { + context(`when ${component} severity level <= ${severityLevel}`, function () { + beforeEach(function () { + const index = severityLevels.indexOf(severityLevel); + componentSeverities = components.reduce((severities, value) => { + component === value + ? (severities[component] = severityLevel) + : (severities[value] = + severityLevels[index + 1] === 'off' + ? severityLevel + : severityLevels[index + 1]); + return severities; + }, {}); + logger = new MongoLogger({ + componentSeverities: componentSeverities, + logDestination: createStdioLogger(process.stderr), + logDestinationIsStdErr: true + } as any); + }); + + if (severityLevel === 'off') { + it('returns false always for off', function () { + expect(logger.willLog(component, severityLevel)).to.be.false; + }); + } else { + it('returns true', function () { + expect(logger.willLog(component, severityLevel)).to.be.true; + }); + } + }); + + context(`when ${component} severity level > ${severityLevel}`, function () { + if (severityLevel !== 'emergency') { + beforeEach(function () { + const index = severityLevels.indexOf(severityLevel); + componentSeverities = components.reduce((severities, value) => { + component === value + ? (severities[component] = severityLevels[index - 1]) + : (severities[value] = severityLevel); + return severities; + }, {}); + logger = new MongoLogger({ + componentSeverities: componentSeverities, + logDestination: createStdioLogger(process.stderr), + logDestinationIsStdErr: true + } as any); + }); + + it('returns false', function () { + expect(logger.willLog(component, severityLevel)).to.be.false; + }); + } + }); + } + }); + } + }); });