diff --git a/src/bson.ts b/src/bson.ts index 80c54360d7b..2c0b43df12a 100644 --- a/src/bson.ts +++ b/src/bson.ts @@ -13,6 +13,8 @@ export { deserialize, Document, Double, + EJSON, + EJSONOptions, Int32, Long, MaxKey, diff --git a/src/cmap/connect.ts b/src/cmap/connect.ts index b22eb34059c..4c7c9179987 100644 --- a/src/cmap/connect.ts +++ b/src/cmap/connect.ts @@ -186,6 +186,10 @@ async function performInitialHandshake( throw error; } } + + // Connection establishment is socket creation (tcp handshake, tls handshake, MongoDB handshake (saslStart, saslContinue)) + // Once connection is established, command logging can log events (if enabled) + conn.established = true; } export interface HandshakeDocument extends Document { diff --git a/src/cmap/connection.ts b/src/cmap/connection.ts index 904709d7604..e036457a586 100644 --- a/src/cmap/connection.ts +++ b/src/cmap/connection.ts @@ -24,6 +24,7 @@ import { MongoWriteConcernError } from '../error'; import type { ServerApi, SupportedNodeConnectionOptions } from '../mongo_client'; +import { MongoLoggableComponent, type MongoLogger, SeverityLevel } from '../mongo_logger'; import { type CancellationToken, TypedEventEmitter } from '../mongo_types'; import type { ReadPreferenceLike } from '../read_preference'; import { applySession, type ClientSession, updateSessionFromResponse } from '../sessions'; @@ -114,6 +115,8 @@ export interface ConnectionOptions socketTimeoutMS?: number; cancellationToken?: CancellationToken; metadata: ClientMetadata; + /** @internal */ + mongoLogger?: MongoLogger | undefined; } /** @internal */ @@ -165,6 +168,16 @@ export class Connection extends TypedEventEmitter { public delayedTimeoutId: NodeJS.Timeout | null = null; public generation: number; public readonly description: Readonly; + /** + * @public + * Represents if the connection has been established: + * - TCP handshake + * - TLS negotiated + * - mongodb handshake (saslStart, saslContinue), includes authentication + * + * Once connection is established, command logging can log events (if enabled) + */ + public established: boolean; private lastUseTime: number; private socketTimeoutMS: number; @@ -174,6 +187,8 @@ export class Connection extends TypedEventEmitter { private messageStream: Readable; private socketWrite: (buffer: Uint8Array) => Promise; private clusterTime: Document | null = null; + /** @internal */ + override mongoLogger: MongoLogger | undefined; /** @event */ static readonly COMMAND_STARTED = COMMAND_STARTED; @@ -198,6 +213,8 @@ export class Connection extends TypedEventEmitter { this.socketTimeoutMS = options.socketTimeoutMS ?? 0; this.monitorCommands = options.monitorCommands; this.serverApi = options.serverApi; + this.mongoLogger = options.mongoLogger; + this.established = false; this.description = new StreamDescription(this.address, options); this.generation = options.generation; @@ -258,6 +275,16 @@ export class Connection extends TypedEventEmitter { ); } + private get shouldEmitAndLogCommand(): boolean { + return ( + (this.monitorCommands || + (this.established && + !this.authContext?.reauthenticating && + this.mongoLogger?.willLog(SeverityLevel.DEBUG, MongoLoggableComponent.COMMAND))) ?? + false + ); + } + public markAvailable(): void { this.lastUseTime = now(); } @@ -441,10 +468,13 @@ export class Connection extends TypedEventEmitter { const message = this.prepareCommand(ns.db, command, options); let started = 0; - if (this.monitorCommands) { + if (this.shouldEmitAndLogCommand) { started = now(); - this.emit( + this.emitAndLogCommand( + this.monitorCommands, Connection.COMMAND_STARTED, + message.databaseName, + this.established, new CommandStartedEvent(this, message, this.description.serverConnectionId) ); } @@ -464,9 +494,12 @@ export class Connection extends TypedEventEmitter { throw new MongoServerError(document); } - if (this.monitorCommands) { - this.emit( + if (this.shouldEmitAndLogCommand) { + this.emitAndLogCommand( + this.monitorCommands, Connection.COMMAND_SUCCEEDED, + message.databaseName, + this.established, new CommandSucceededEvent( this, message, @@ -481,10 +514,13 @@ export class Connection extends TypedEventEmitter { this.controller.signal.throwIfAborted(); } } catch (error) { - if (this.monitorCommands) { + if (this.shouldEmitAndLogCommand) { if (error.name === 'MongoWriteConcernError') { - this.emit( + this.emitAndLogCommand( + this.monitorCommands, Connection.COMMAND_SUCCEEDED, + message.databaseName, + this.established, new CommandSucceededEvent( this, message, @@ -494,8 +530,11 @@ export class Connection extends TypedEventEmitter { ) ); } else { - this.emit( + this.emitAndLogCommand( + this.monitorCommands, Connection.COMMAND_FAILED, + message.databaseName, + this.established, new CommandFailedEvent( this, message, diff --git a/src/cmap/connection_pool.ts b/src/cmap/connection_pool.ts index 0df93f688af..5c7de8dd54c 100644 --- a/src/cmap/connection_pool.ts +++ b/src/cmap/connection_pool.ts @@ -699,7 +699,8 @@ export class ConnectionPool extends TypedEventEmitter { ...this.options, id: this[kConnectionCounter].next().value, generation: this[kGeneration], - cancellationToken: this[kCancellationToken] + cancellationToken: this[kCancellationToken], + mongoLogger: this.mongoLogger }; this[kPending]++; diff --git a/src/constants.ts b/src/constants.ts index 8f8465443bc..293749cad89 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -60,8 +60,11 @@ export const CONNECTION_CHECKED_OUT = 'connectionCheckedOut' as const; /** @internal */ export const CONNECTION_CHECKED_IN = 'connectionCheckedIn' as const; export const CLUSTER_TIME_RECEIVED = 'clusterTimeReceived' as const; +/** @internal */ export const COMMAND_STARTED = 'commandStarted' as const; +/** @internal */ export const COMMAND_SUCCEEDED = 'commandSucceeded' as const; +/** @internal */ export const COMMAND_FAILED = 'commandFailed' as const; /** @internal */ export const SERVER_HEARTBEAT_STARTED = 'serverHeartbeatStarted' as const; diff --git a/src/index.ts b/src/index.ts index 4771bfa0de1..6366e746655 100644 --- a/src/index.ts +++ b/src/index.ts @@ -292,6 +292,9 @@ export type { StreamDescription, StreamDescriptionOptions } from './cmap/stream_ export type { CompressorName } from './cmap/wire_protocol/compression'; export type { CollectionOptions, CollectionPrivate, ModifyResult } from './collection'; export type { + COMMAND_FAILED, + COMMAND_STARTED, + COMMAND_SUCCEEDED, CONNECTION_CHECK_OUT_FAILED, CONNECTION_CHECK_OUT_STARTED, CONNECTION_CHECKED_IN, @@ -367,6 +370,8 @@ export type { LogComponentSeveritiesClientOptions, LogConvertible, Loggable, + LoggableCommandFailedEvent, + LoggableCommandSucceededEvent, LoggableEvent, LoggableServerHeartbeatFailedEvent, LoggableServerHeartbeatStartedEvent, diff --git a/src/mongo_logger.ts b/src/mongo_logger.ts index 04b83defaee..23f270bd4a3 100644 --- a/src/mongo_logger.ts +++ b/src/mongo_logger.ts @@ -1,12 +1,8 @@ -import { type Document, EJSON, type EJSONOptions } from 'bson'; import type { Writable } from 'stream'; import { inspect } from 'util'; -import type { - CommandFailedEvent, - CommandStartedEvent, - CommandSucceededEvent -} from './cmap/command_monitoring_events'; +import { type Document, EJSON, type EJSONOptions, type ObjectId } from './bson'; +import type { CommandStartedEvent } from './cmap/command_monitoring_events'; import type { ConnectionCheckedInEvent, ConnectionCheckedOutEvent, @@ -295,6 +291,40 @@ function compareSeverity(s0: SeverityLevel, s1: SeverityLevel): 1 | 0 | -1 { return s0Num < s1Num ? -1 : s0Num > s1Num ? 1 : 0; } +/** + * @internal + * Must be separate from Events API due to differences in spec requirements for logging a command success + */ +export type LoggableCommandSucceededEvent = { + address: string; + connectionId?: string | number; + requestId: number; + duration: number; + commandName: string; + reply: Document | undefined; + serviceId?: ObjectId; + name: typeof COMMAND_SUCCEEDED; + serverConnectionId: bigint | null; + databaseName: string; +}; + +/** + * @internal + * Must be separate from Events API due to differences in spec requirements for logging a command failure + */ +export type LoggableCommandFailedEvent = { + address: string; + connectionId?: string | number; + requestId: number; + duration: number; + commandName: string; + failure: Error; + serviceId?: ObjectId; + name: typeof COMMAND_FAILED; + serverConnectionId: bigint | null; + databaseName: string; +}; + /** * @internal * Must be separate from Events API due to differences in spec requirements for logging server heartbeat beginning @@ -350,8 +380,8 @@ export type LoggableEvent = | ServerSelectionSucceededEvent | WaitingForSuitableServerEvent | CommandStartedEvent - | CommandSucceededEvent - | CommandFailedEvent + | LoggableCommandSucceededEvent + | LoggableCommandFailedEvent | ConnectionPoolCreatedEvent | ConnectionPoolReadyEvent | ConnectionPoolClosedEvent @@ -383,7 +413,8 @@ export function stringifyWithMaxLen( maxDocumentLength: number, options: EJSONOptions = {} ): string { - let strToTruncate: string; + let strToTruncate = ''; + if (typeof value === 'function') { strToTruncate = value.toString(); } else { @@ -420,7 +451,7 @@ function attachServerSelectionFields( function attachCommandFields( log: Record, - commandEvent: CommandStartedEvent | CommandSucceededEvent | CommandFailedEvent + commandEvent: CommandStartedEvent | LoggableCommandSucceededEvent | LoggableCommandFailedEvent ) { log.commandName = commandEvent.commandName; log.requestId = commandEvent.requestId; @@ -431,6 +462,8 @@ function attachCommandFields( if (commandEvent?.serviceId) { log.serviceId = commandEvent.serviceId.toHexString(); } + log.databaseName = commandEvent.databaseName; + log.serverConnectionId = commandEvent?.serverConnectionId; return log; } @@ -490,20 +523,20 @@ function defaultLogTransform( case COMMAND_STARTED: log = attachCommandFields(log, logObject); log.message = 'Command started'; - log.command = stringifyWithMaxLen(logObject.command, maxDocumentLength); + log.command = stringifyWithMaxLen(logObject.command, maxDocumentLength, { relaxed: true }); log.databaseName = logObject.databaseName; return log; case COMMAND_SUCCEEDED: log = attachCommandFields(log, logObject); log.message = 'Command succeeded'; log.durationMS = logObject.duration; - log.reply = stringifyWithMaxLen(logObject.reply, maxDocumentLength); + log.reply = stringifyWithMaxLen(logObject.reply, maxDocumentLength, { relaxed: true }); return log; case COMMAND_FAILED: log = attachCommandFields(log, logObject); log.message = 'Command failed'; log.durationMS = logObject.duration; - log.failure = logObject.failure; + log.failure = logObject.failure.message ?? '(redacted)'; return log; case CONNECTION_POOL_CREATED: log = attachConnectionFields(log, logObject); @@ -701,12 +734,16 @@ export class MongoLogger { this.logDestination = options.logDestination; } + willLog(severity: SeverityLevel, component: MongoLoggableComponent): boolean { + return compareSeverity(severity, this.componentSeverities[component]) <= 0; + } + private log( severity: SeverityLevel, component: MongoLoggableComponent, message: Loggable | string ): void { - if (compareSeverity(severity, this.componentSeverities[component]) > 0) return; + if (!this.willLog(severity, component)) return; let logMessage: Log = { t: new Date(), c: component, s: severity }; if (typeof message === 'string') { diff --git a/src/mongo_types.ts b/src/mongo_types.ts index f201a50eb61..1924b2afe29 100644 --- a/src/mongo_types.ts +++ b/src/mongo_types.ts @@ -12,12 +12,15 @@ import type { ObjectId, Timestamp } from './bson'; -import type { - LoggableServerHeartbeatFailedEvent, - LoggableServerHeartbeatStartedEvent, - LoggableServerHeartbeatSucceededEvent, +import { type CommandStartedEvent } from './cmap/command_monitoring_events'; +import { + type LoggableCommandFailedEvent, + type LoggableCommandSucceededEvent, + type LoggableServerHeartbeatFailedEvent, + type LoggableServerHeartbeatStartedEvent, + type LoggableServerHeartbeatSucceededEvent, MongoLoggableComponent, - MongoLogger + type MongoLogger } from './mongo_logger'; import type { Sort } from './sort'; @@ -442,6 +445,28 @@ export class TypedEventEmitter extends EventEm this.mongoLogger?.debug(this.component, loggableHeartbeatEvent); } } + /** @internal */ + emitAndLogCommand( + monitorCommands: boolean, + event: EventKey | symbol, + databaseName: string, + connectionEstablished: boolean, + ...args: Parameters + ): void { + if (monitorCommands) { + this.emit(event, ...args); + } + if (connectionEstablished) { + const loggableCommandEvent: + | CommandStartedEvent + | LoggableCommandFailedEvent + | LoggableCommandSucceededEvent = { + databaseName: databaseName, + ...args[0] + }; + this.mongoLogger?.debug(MongoLoggableComponent.COMMAND, loggableCommandEvent); + } + } } /** @public */ diff --git a/test/integration/command-logging-and-monitoring/command_logging_and_monitoring.prose.test.ts b/test/integration/command-logging-and-monitoring/command_logging_and_monitoring.prose.test.ts new file mode 100644 index 00000000000..b6a841b26e3 --- /dev/null +++ b/test/integration/command-logging-and-monitoring/command_logging_and_monitoring.prose.test.ts @@ -0,0 +1,234 @@ +import { expect } from 'chai'; + +import { DEFAULT_MAX_DOCUMENT_LENGTH, type Document } from '../../mongodb'; + +describe('Command Logging and Monitoring Prose Tests', function () { + const loggerFeatureFlag = Symbol.for('@@mdb.enableMongoLogger'); + const ELLIPSES_LENGTH = 3; + let client; + let writable; + + context('When no custom truncation limit is provided', function () { + /* + 1. Configure logging with a minimum severity level of "debug" for the "command" component. + Do not explicitly configure the max document length. + + 2. Construct an array docs containing the document {"x" : "y"} repeated 100 times. + + 3. Insert docs to a collection via insertMany. + + 4. Inspect the resulting "command started" log message and assert that + the "command" value is a string of length 1000 + (length of trailing ellipsis). + + 5. Inspect the resulting "command succeeded" log message and assert that + the "reply" value is a string of length <= 1000 + (length of trailing ellipsis). + + 6. Run find() on the collection where the document was inserted. + + 7. Inspect the resulting "command succeeded" log message and assert that + the reply is a string of length 1000 + (length of trailing ellipsis). + */ + + beforeEach(async function () { + writable = { + buffer: [], + write(log) { + this.buffer.push(log); + } + }; + // 1. + client = this.configuration.newClient( + {}, + { + [loggerFeatureFlag]: true, + mongodbLogPath: writable, + mongodbLogComponentSeverities: { + command: 'debug' + } + } + ); + }); + + afterEach(async function () { + await client.close(); + }); + + it('should follow default truncation limit of 1000', async function () { + // 2. + const docs: Array = []; + for (let i = 0; i < 100; i++) { + docs.push({ x: 'y' }); + } + + // 3. + await client.db('admin').collection('test').insertMany(docs); + + // 4. + const insertManyCommandStarted = writable.buffer[0]; + expect(insertManyCommandStarted?.message).to.equal('Command started'); + expect(insertManyCommandStarted?.command).to.be.a('string'); + expect(insertManyCommandStarted?.command?.length).to.equal( + DEFAULT_MAX_DOCUMENT_LENGTH + ELLIPSES_LENGTH + ); + + // 5. + const insertManyCommandSucceeded = writable.buffer[1]; + expect(insertManyCommandSucceeded?.message).to.equal('Command succeeded'); + expect(insertManyCommandSucceeded?.reply).to.be.a('string'); + expect(insertManyCommandSucceeded?.reply?.length).to.be.at.most( + DEFAULT_MAX_DOCUMENT_LENGTH + ELLIPSES_LENGTH + ); + + // 6. + await client.db('admin').collection('test').find().toArray(); + + // 7. + const findCommandSucceeded = writable.buffer[3]; + expect(findCommandSucceeded?.message).to.equal('Command succeeded'); + expect(findCommandSucceeded?.reply).to.be.a('string'); + expect(findCommandSucceeded?.reply?.length).to.equal( + DEFAULT_MAX_DOCUMENT_LENGTH + ELLIPSES_LENGTH + ); + }); + }); + + context('When custom truncation limit is provided', function () { + /* + 1. Configure logging with a minimum severity level of "debug" for the "command" component. + Set the max document length to 5. + + 2. Run the command {"hello": true}. + + 3. Inspect the resulting "command started" log message and assert that + the "command" value is a string of length 5 + (length of trailing ellipsis). + + 4. Inspect the resulting "command succeeded" log message and assert that + the "reply" value is a string of length 5 + (length of trailing ellipsis). + + 5. (Optional) + If the driver attaches raw server responses to failures + and can access these via log messages to assert on, + run the command {"notARealCommand": true}. + + Inspect the resulting "command failed" log message + and confirm that the server error is a string of length 5 + (length of trailing ellipsis). + */ + beforeEach(async function () { + writable = { + buffer: [], + write(log) { + this.buffer.push(log); + } + }; + // 1. + client = this.configuration.newClient( + {}, + { + [loggerFeatureFlag]: true, + mongodbLogPath: writable, + mongodbLogComponentSeverities: { + command: 'debug' + }, + mongodbLogMaxDocumentLength: 5 + } + ); + }); + + afterEach(async function () { + await client.close(); + }); + + it('should follow custom truncation limit', async function () { + // 2. + await client.db('admin').command({ hello: 'true' }); + + // 3. + const insertManyCommandStarted = writable.buffer[0]; + expect(insertManyCommandStarted?.message).to.equal('Command started'); + expect(insertManyCommandStarted?.command).to.be.a('string'); + expect(insertManyCommandStarted?.command?.length).to.equal(5 + ELLIPSES_LENGTH); + + // 4. + const insertManyCommandSucceeded = writable.buffer[1]; + expect(insertManyCommandSucceeded?.message).to.equal('Command succeeded'); + expect(insertManyCommandSucceeded?.reply).to.be.a('string'); + expect(insertManyCommandSucceeded?.reply?.length).to.be.at.most(5 + ELLIPSES_LENGTH); + }); + }); + + context.skip('Truncation with multi-byte codepoints', function () { + /* + A specific test case is not provided here due to the allowed variations in truncation logic + as well as varying extended JSON whitespace usage. + Drivers MUST write language-specific tests that confirm truncation of commands, replies, + and (if applicable) server responses included in error messages + work as expected when the data being truncated includes multi-byte Unicode codepoints. + + If the driver uses anything other than Unicode codepoints as the unit for max document length, + there also MUST be tests confirming that cases + where the max length falls in the middle of a multi-byte codepoint are handled gracefully. + */ + + const maxDocLength = 39; + beforeEach(async function () { + writable = { + buffer: [], + write(log) { + this.buffer.push(log); + } + }; + // 1. + client = this.configuration.newClient( + {}, + { + [loggerFeatureFlag]: true, + mongodbLogPath: writable, + mongodbLogComponentSeverities: { + command: 'debug' + }, + mongodbLogMaxDocumentLength: maxDocLength + } + ); + }); + + afterEach(async function () { + await client.close(); + }); + + it('should handle unicode codepoints in middle and end of truncation gracefully', async function () { + const multibyteUnicode = '\uD801\uDC37'; + const firstByteChar = multibyteUnicode.charCodeAt(0); + const secondByteChar = multibyteUnicode.charCodeAt(1); + const docs: Array = [ + { + x: `${multibyteUnicode}${multibyteUnicode}${multibyteUnicode}${multibyteUnicode}` + } + ]; + + await client.db('admin').collection('test').insertMany(docs); + + const insertManyCommandStarted = writable.buffer[0]; + expect(insertManyCommandStarted?.message).to.equal('Command started'); + expect(insertManyCommandStarted?.command).to.be.a('string'); + // maxDocLength - 1 because stringified response should be rounded down due to ending mid-codepoint + expect(insertManyCommandStarted?.command?.length).to.equal( + maxDocLength - 1 + ELLIPSES_LENGTH + ); + + // multi-byte codepoint in middle of truncated string + expect(insertManyCommandStarted?.command.charCodeAt(maxDocLength - 1)).to.equal( + firstByteChar + ); + expect(insertManyCommandStarted?.command.charCodeAt(maxDocLength - 1)).to.equal( + secondByteChar + ); + + const insertManyCommandSucceeded = writable.buffer[1]; + expect(insertManyCommandSucceeded?.message).to.equal('Command succeeded'); + expect(insertManyCommandSucceeded?.reply).to.be.a('string'); + expect(insertManyCommandSucceeded?.reply?.length).to.be.at.most( + maxDocLength + ELLIPSES_LENGTH + ); + }); + }).skipReason = 'todo(NODE-5839)'; +}); diff --git a/test/integration/command-logging-and-monitoring/command_logging_and_monitoring.spec.test.ts b/test/integration/command-logging-and-monitoring/command_logging_and_monitoring.spec.test.ts index 8d2446ab0cf..62075e06b77 100644 --- a/test/integration/command-logging-and-monitoring/command_logging_and_monitoring.spec.test.ts +++ b/test/integration/command-logging-and-monitoring/command_logging_and_monitoring.spec.test.ts @@ -22,7 +22,18 @@ describe('Command Logging and Monitoring Spec', function () { ); }); - describe.skip('Command Logging Spec', () => { - runUnifiedSuite(loadSpecTests(path.join('command-logging-and-monitoring', 'logging'))); - }).skipReason = 'TODO(NODE-4686): Unskip these tests'; + describe('Command Logging Spec', () => { + const tests = loadSpecTests(path.join('command-logging-and-monitoring', 'logging')); + runUnifiedSuite(tests, test => { + if ( + [ + 'Successful bulk write command log messages include operationIds', + 'Failed bulk write command log message includes operationId' + ].includes(test.description) + ) { + return 'not applicable: operationId not supported'; + } + return false; + }); + }); }); diff --git a/test/tools/unified-spec-runner/entities.ts b/test/tools/unified-spec-runner/entities.ts index de137966ce1..0e5a8f3d5d7 100644 --- a/test/tools/unified-spec-runner/entities.ts +++ b/test/tools/unified-spec-runner/entities.ts @@ -232,18 +232,17 @@ export class UnifiedMongoClient extends MongoClient { mongodbLogMaxDocumentLength: 1250 } as any); this.logCollector = logCollector; + this.observeSensitiveCommands = description.observeSensitiveCommands ?? false; this.ignoredEvents = [ ...(description.ignoreCommandMonitoringEvents ?? []), 'configureFailPoint' ]; - if (!description.observeSensitiveCommands) { + if (!this.observeSensitiveCommands) { this.ignoredEvents.push(...Array.from(SENSITIVE_COMMANDS)); } - this.observeSensitiveCommands = description.observeSensitiveCommands ?? false; - this.observedCommandEvents = (description.observeEvents ?? []) .map(e => UnifiedMongoClient.COMMAND_EVENT_NAME_LOOKUP[e]) .filter(e => !!e); diff --git a/test/tools/unified-spec-runner/match.ts b/test/tools/unified-spec-runner/match.ts index 0a30d158917..1098efc1fb1 100644 --- a/test/tools/unified-spec-runner/match.ts +++ b/test/tools/unified-spec-runner/match.ts @@ -648,7 +648,7 @@ export function matchesEvents( } } -export function filterLogs( +export function filterExtraLogs( logsToIgnore: ExpectedLogMessage[], actual: ExpectedLogMessage[], entities: EntitiesMap @@ -694,7 +694,9 @@ export function compareLogs( expect(actualLog.data.failure, 'Expected failure to exist').to.exist; if (expectedLog.failureIsRedacted) { // Assert that failure has been redacted - expect(actualLog.data.failure, 'Expected failure to have been redacted').to.deep.equal({}); + expect(actualLog.data.failure, 'Expected failure to have been redacted').to.equal( + '(redacted)' + ); } else { // Assert that failure has not been redacted expect( diff --git a/test/tools/unified-spec-runner/runner.ts b/test/tools/unified-spec-runner/runner.ts index 050d7a2e879..9f1fb3925f5 100644 --- a/test/tools/unified-spec-runner/runner.ts +++ b/test/tools/unified-spec-runner/runner.ts @@ -7,7 +7,7 @@ import { MONGODB_ERROR_CODES, ns, ReadPreference, TopologyType } from '../../mon import { ejson } from '../utils'; import { AstrolabeResultsWriter } from './astrolabe_results_writer'; import { EntitiesMap, type UnifiedMongoClient } from './entities'; -import { compareLogs, filterLogs, matchesEvents } from './match'; +import { compareLogs, filterExtraLogs, matchesEvents } from './match'; import { executeOperationAndCheck } from './operations'; import * as uni from './schema'; import { isAnyRequirementSatisfied, patchVersion, zip } from './unified-utils'; @@ -233,7 +233,11 @@ async function runUnifiedTest( expect(testClient, `No client entity found with id ${clientId}`).to.exist; const filteredTestClientLogs = expectedLogsForClient.ignoreMessages - ? filterLogs(expectedLogsForClient.ignoreMessages, testClient!.collectedLogs, entities) + ? filterExtraLogs( + expectedLogsForClient.ignoreMessages, + testClient!.collectedLogs, + entities + ) : testClient!.collectedLogs; compareLogs(expectedLogsForClient.messages, filteredTestClientLogs, entities); } diff --git a/test/unit/cmap/connection_pool.test.js b/test/unit/cmap/connection_pool.test.js index 9acf50e50af..cdbf00bf67f 100644 --- a/test/unit/cmap/connection_pool.test.js +++ b/test/unit/cmap/connection_pool.test.js @@ -18,7 +18,8 @@ describe('Connection Pool', function () { topology: { client: { mongoLogger: { - debug: () => null + debug: () => null, + willLog: () => null } } } diff --git a/test/unit/mongo_logger.test.ts b/test/unit/mongo_logger.test.ts index 0d419164536..90a36437494 100644 --- a/test/unit/mongo_logger.test.ts +++ b/test/unit/mongo_logger.test.ts @@ -809,6 +809,7 @@ describe('class MongoLogger', function () { address: '127.0.0.1:27017', serviceId: new ObjectId(), databaseName: 'db', + failure: 'err', name: COMMAND_FAILED }; diff --git a/test/unit/tools/unified_spec_runner.test.ts b/test/unit/tools/unified_spec_runner.test.ts index bf78b75c9a3..647005411fe 100644 --- a/test/unit/tools/unified_spec_runner.test.ts +++ b/test/unit/tools/unified_spec_runner.test.ts @@ -167,7 +167,7 @@ describe('Unified Spec Runner', function () { level: 'debug', component: 'command', data: { - failure: {} + failure: '(redacted)' } }; compareLogsSpy([expected], [actual], entitiesMap);