CoolFace
Apppublic

opusdev/vector-similarity-api

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes
mongo_logger.ts1077 linesDownload Raw Back to src
1import { inspect, promisify } from 'util';2 3import {4  type Binary,5  type BSONRegExp,6  type BSONSymbol,7  type Code,8  type DBRef,9  type Decimal128,10  type Document,11  type Double,12  EJSON,13  type EJSONOptions,14  type Int32,15  type Long,16  type MaxKey,17  type MinKey,18  type ObjectId,19  type Timestamp20} from './bson';21import type { CommandStartedEvent } from './cmap/command_monitoring_events';22import type {23  ConnectionCheckedInEvent,24  ConnectionCheckedOutEvent,25  ConnectionCheckOutFailedEvent,26  ConnectionCheckOutStartedEvent,27  ConnectionClosedEvent,28  ConnectionCreatedEvent,29  ConnectionPoolClearedEvent,30  ConnectionPoolClosedEvent,31  ConnectionPoolCreatedEvent,32  ConnectionPoolReadyEvent,33  ConnectionReadyEvent34} from './cmap/connection_pool_events';35import {36  COMMAND_FAILED,37  COMMAND_STARTED,38  COMMAND_SUCCEEDED,39  CONNECTION_CHECK_OUT_FAILED,40  CONNECTION_CHECK_OUT_STARTED,41  CONNECTION_CHECKED_IN,42  CONNECTION_CHECKED_OUT,43  CONNECTION_CLOSED,44  CONNECTION_CREATED,45  CONNECTION_POOL_CLEARED,46  CONNECTION_POOL_CLOSED,47  CONNECTION_POOL_CREATED,48  CONNECTION_POOL_READY,49  CONNECTION_READY,50  SERVER_CLOSED,51  SERVER_HEARTBEAT_FAILED,52  SERVER_HEARTBEAT_STARTED,53  SERVER_HEARTBEAT_SUCCEEDED,54  SERVER_OPENING,55  SERVER_SELECTION_FAILED,56  SERVER_SELECTION_STARTED,57  SERVER_SELECTION_SUCCEEDED,58  TOPOLOGY_CLOSED,59  TOPOLOGY_DESCRIPTION_CHANGED,60  TOPOLOGY_OPENING,61  WAITING_FOR_SUITABLE_SERVER62} from './constants';63import type {64  ServerClosedEvent,65  ServerOpeningEvent,66  TopologyClosedEvent,67  TopologyDescriptionChangedEvent,68  TopologyOpeningEvent69} from './sdam/events';70import type {71  ServerSelectionEvent,72  ServerSelectionFailedEvent,73  ServerSelectionStartedEvent,74  ServerSelectionSucceededEvent,75  WaitingForSuitableServerEvent76} from './sdam/server_selection_events';77import { HostAddress, isPromiseLike, isUint8Array, parseUnsignedInteger } from './utils';78 79/**80 * @public81 * Severity levels align with unix syslog.82 * Most typical driver functions will log to debug.83 */84export const SeverityLevel = Object.freeze({85  EMERGENCY: 'emergency',86  ALERT: 'alert',87  CRITICAL: 'critical',88  ERROR: 'error',89  WARNING: 'warn',90  NOTICE: 'notice',91  INFORMATIONAL: 'info',92  DEBUG: 'debug',93  TRACE: 'trace',94  OFF: 'off'95} as const);96 97/** @internal */98export const DEFAULT_MAX_DOCUMENT_LENGTH = 1000;99/** @public */100export type SeverityLevel = (typeof SeverityLevel)[keyof typeof SeverityLevel];101 102/** @internal */103class SeverityLevelMap extends Map<SeverityLevel | number, SeverityLevel | number> {104  constructor(entries: [SeverityLevel | number, SeverityLevel | number][]) {105    const newEntries: [number | SeverityLevel, SeverityLevel | number][] = [];106    for (const [level, value] of entries) {107      newEntries.push([value, level]);108    }109 110    newEntries.push(...entries);111    super(newEntries);112  }113 114  getNumericSeverityLevel(severity: SeverityLevel): number {115    return this.get(severity) as number;116  }117 118  getSeverityLevelName(level: number): SeverityLevel | undefined {119    return this.get(level) as SeverityLevel | undefined;120  }121}122 123/** @internal */124export const SEVERITY_LEVEL_MAP = new SeverityLevelMap([125  [SeverityLevel.OFF, -Infinity],126  [SeverityLevel.EMERGENCY, 0],127  [SeverityLevel.ALERT, 1],128  [SeverityLevel.CRITICAL, 2],129  [SeverityLevel.ERROR, 3],130  [SeverityLevel.WARNING, 4],131  [SeverityLevel.NOTICE, 5],132  [SeverityLevel.INFORMATIONAL, 6],133  [SeverityLevel.DEBUG, 7],134  [SeverityLevel.TRACE, 8]135]);136 137/** @public */138export const MongoLoggableComponent = Object.freeze({139  COMMAND: 'command',140  TOPOLOGY: 'topology',141  SERVER_SELECTION: 'serverSelection',142  CONNECTION: 'connection',143  CLIENT: 'client'144} as const);145 146/** @public */147export type MongoLoggableComponent =148  (typeof MongoLoggableComponent)[keyof typeof MongoLoggableComponent];149 150/** @internal */151export interface MongoLoggerEnvOptions {152  /** Severity level for command component */153  MONGODB_LOG_COMMAND?: string;154  /** Severity level for topology component */155  MONGODB_LOG_TOPOLOGY?: string;156  /** Severity level for server selection component */157  MONGODB_LOG_SERVER_SELECTION?: string;158  /** Severity level for CMAP */159  MONGODB_LOG_CONNECTION?: string;160  /** Severity level for client */161  MONGODB_LOG_CLIENT?: string;162  /** Default severity level to be if any of the above are unset */163  MONGODB_LOG_ALL?: string;164  /** Max length of embedded EJSON docs. Setting to 0 disables truncation. Defaults to 1000. */165  MONGODB_LOG_MAX_DOCUMENT_LENGTH?: string;166  /** Destination for log messages. Must be 'stderr', 'stdout'. Defaults to 'stderr'. */167  MONGODB_LOG_PATH?: string;168}169 170/** @public */171export interface LogComponentSeveritiesClientOptions {172  /** Optional severity level for command component */173  command?: SeverityLevel;174  /** Optional severity level for topology component */175  topology?: SeverityLevel;176  /** Optional severity level for server selection component */177  serverSelection?: SeverityLevel;178  /** Optional severity level for connection component */179  connection?: SeverityLevel;180  /** Optional severity level for client component */181  client?: SeverityLevel;182  /** Optional default severity level to be used if any of the above are unset */183  default?: SeverityLevel;184}185 186/** @internal */187export interface MongoLoggerMongoClientOptions {188  /** Destination for log messages */189  mongodbLogPath?: 'stdout' | 'stderr' | MongoDBLogWritable;190  /** Severity levels for logger components */191  mongodbLogComponentSeverities?: LogComponentSeveritiesClientOptions;192  /** Max length of embedded EJSON docs. Setting to 0 disables truncation. Defaults to 1000. */193  mongodbLogMaxDocumentLength?: number;194}195 196/** @internal */197export interface MongoLoggerOptions {198  componentSeverities: {199    /** Severity level for command component */200    command: SeverityLevel;201    /** Severity level for topology component */202    topology: SeverityLevel;203    /** Severity level for server selection component */204    serverSelection: SeverityLevel;205    /** Severity level for connection component */206    connection: SeverityLevel;207    /** Severity level for client component */208    client: SeverityLevel;209    /** Default severity level to be used if any of the above are unset */210    default: SeverityLevel;211  };212  /** Max length of embedded EJSON docs. Setting to 0 disables truncation. Defaults to 1000. */213  maxDocumentLength: number;214  /** Destination for log messages. */215  logDestination: MongoDBLogWritable;216  /** For internal check to see if error should stop logging. */217  logDestinationIsStdErr: boolean;218}219 220/**221 * Parses a string as one of SeverityLevel222 * @internal223 *224 * @param s - the value to be parsed225 * @returns one of SeverityLevel if value can be parsed as such, otherwise null226 */227export function parseSeverityFromString(s?: string): SeverityLevel | null {228  const validSeverities: string[] = Object.values(SeverityLevel);229  const lowerSeverity = s?.toLowerCase();230 231  if (lowerSeverity != null && validSeverities.includes(lowerSeverity)) {232    return lowerSeverity as SeverityLevel;233  }234 235  return null;236}237 238/** @internal */239export function createStdioLogger(stream: {240  write: NodeJS.WriteStream['write'];241}): MongoDBLogWritable {242  return {243    write: promisify((log: Log, cb: (error?: Error) => void): unknown => {244      const logLine = inspect(log, { compact: true, breakLength: Infinity });245      stream.write(`${logLine}\n`, 'utf-8', cb);246      return;247    })248  };249}250 251/**252 * resolves the MONGODB_LOG_PATH and mongodbLogPath options from the environment and the253 * mongo client options respectively. The mongodbLogPath can be either 'stdout', 'stderr', a NodeJS254 * Writable or an object which has a `write` method with the signature:255 * ```ts256 * write(log: Log): void257 * ```258 *259 * @returns the MongoDBLogWritable object to write logs to260 */261function resolveLogPath(262  { MONGODB_LOG_PATH }: MongoLoggerEnvOptions,263  { mongodbLogPath }: MongoLoggerMongoClientOptions264): { mongodbLogPath: MongoDBLogWritable; mongodbLogPathIsStdErr: boolean } {265  if (typeof mongodbLogPath === 'string' && /^stderr$/i.test(mongodbLogPath)) {266    return { mongodbLogPath: createStdioLogger(process.stderr), mongodbLogPathIsStdErr: true };267  }268  if (typeof mongodbLogPath === 'string' && /^stdout$/i.test(mongodbLogPath)) {269    return { mongodbLogPath: createStdioLogger(process.stdout), mongodbLogPathIsStdErr: false };270  }271 272  if (typeof mongodbLogPath === 'object' && typeof mongodbLogPath?.write === 'function') {273    return { mongodbLogPath: mongodbLogPath, mongodbLogPathIsStdErr: false };274  }275 276  if (MONGODB_LOG_PATH && /^stderr$/i.test(MONGODB_LOG_PATH)) {277    return { mongodbLogPath: createStdioLogger(process.stderr), mongodbLogPathIsStdErr: true };278  }279  if (MONGODB_LOG_PATH && /^stdout$/i.test(MONGODB_LOG_PATH)) {280    return { mongodbLogPath: createStdioLogger(process.stdout), mongodbLogPathIsStdErr: false };281  }282 283  return { mongodbLogPath: createStdioLogger(process.stderr), mongodbLogPathIsStdErr: true };284}285 286function resolveSeverityConfiguration(287  clientOption: string | undefined,288  environmentOption: string | undefined,289  defaultSeverity: SeverityLevel290): SeverityLevel {291  return (292    parseSeverityFromString(clientOption) ??293    parseSeverityFromString(environmentOption) ??294    defaultSeverity295  );296}297 298/** @public */299export interface Log extends Record<string, any> {300  t: Date;301  c: MongoLoggableComponent;302  s: SeverityLevel;303  message?: string;304}305 306/**307 * @public308 *309 * A custom destination for structured logging messages.310 */311export interface MongoDBLogWritable {312  /**313   * This function will be called for every enabled log message.314   *315   * It can be sync or async:316   * - If it is synchronous it will block the driver from proceeding until this method returns.317   * - If it is asynchronous the driver will not await the returned promise. It will attach fulfillment handling (`.then`).318   *   If the promise rejects the logger will write an error message to stderr and stop functioning.319   *   If the promise resolves the driver proceeds to the next log message (or waits for new ones to occur).320   *321   * Tips:322   * - We recommend writing an async `write` function that _never_ rejects.323   *   Instead handle logging errors as necessary to your use case and make the write function a noop, until it can be recovered.324   * - The Log messages are structured but **subject to change** since the intended purpose is informational.325   *   Program against this defensively and err on the side of stringifying whatever is passed in to write in some form or another.326   *327   */328  write(log: Log): PromiseLike<unknown> | unknown;329}330 331function compareSeverity(s0: SeverityLevel, s1: SeverityLevel): 1 | 0 | -1 {332  const s0Num = SEVERITY_LEVEL_MAP.getNumericSeverityLevel(s0);333  const s1Num = SEVERITY_LEVEL_MAP.getNumericSeverityLevel(s1);334 335  return s0Num < s1Num ? -1 : s0Num > s1Num ? 1 : 0;336}337 338/**339 * @internal340 * Must be separate from Events API due to differences in spec requirements for logging a command success341 */342export type LoggableCommandSucceededEvent = {343  address: string;344  connectionId?: string | number;345  requestId: number;346  duration: number;347  commandName: string;348  reply: Document | undefined;349  serviceId?: ObjectId;350  name: typeof COMMAND_SUCCEEDED;351  serverConnectionId: bigint | null;352  databaseName: string;353};354 355/**356 * @internal357 * Must be separate from Events API due to differences in spec requirements for logging a command failure358 */359export type LoggableCommandFailedEvent = {360  address: string;361  connectionId?: string | number;362  requestId: number;363  duration: number;364  commandName: string;365  failure: Error;366  serviceId?: ObjectId;367  name: typeof COMMAND_FAILED;368  serverConnectionId: bigint | null;369  databaseName: string;370};371 372/**373 * @internal374 * Must be separate from Events API due to differences in spec requirements for logging server heartbeat beginning375 */376export type LoggableServerHeartbeatStartedEvent = {377  topologyId: number;378  awaited: boolean;379  connectionId: string;380  name: typeof SERVER_HEARTBEAT_STARTED;381};382 383/**384 * @internal385 * Must be separate from Events API due to differences in spec requirements for logging server heartbeat success386 */387export type LoggableServerHeartbeatSucceededEvent = {388  topologyId: number;389  awaited: boolean;390  connectionId: string;391  reply: Document;392  serverConnectionId: number | '<monitor>';393  duration: number;394  name: typeof SERVER_HEARTBEAT_SUCCEEDED;395};396 397/**398 * @internal399 * Must be separate from Events API due to differences in spec requirements for logging server heartbeat failure400 */401export type LoggableServerHeartbeatFailedEvent = {402  topologyId: number;403  awaited: boolean;404  connectionId: string;405  failure: Error;406  duration: number;407  name: typeof SERVER_HEARTBEAT_FAILED;408};409 410type SDAMLoggableEvent =411  | ServerClosedEvent412  | LoggableServerHeartbeatFailedEvent413  | LoggableServerHeartbeatStartedEvent414  | LoggableServerHeartbeatSucceededEvent415  | ServerOpeningEvent416  | TopologyClosedEvent417  | TopologyDescriptionChangedEvent418  | TopologyOpeningEvent;419 420/** @internal */421export type LoggableEvent =422  | ServerSelectionStartedEvent423  | ServerSelectionFailedEvent424  | ServerSelectionSucceededEvent425  | WaitingForSuitableServerEvent426  | CommandStartedEvent427  | LoggableCommandSucceededEvent428  | LoggableCommandFailedEvent429  | ConnectionPoolCreatedEvent430  | ConnectionPoolReadyEvent431  | ConnectionPoolClosedEvent432  | ConnectionPoolClearedEvent433  | ConnectionCreatedEvent434  | ConnectionReadyEvent435  | ConnectionClosedEvent436  | ConnectionCheckedInEvent437  | ConnectionCheckedOutEvent438  | ConnectionCheckOutStartedEvent439  | ConnectionCheckOutFailedEvent440  | ServerClosedEvent441  | LoggableServerHeartbeatFailedEvent442  | LoggableServerHeartbeatStartedEvent443  | LoggableServerHeartbeatSucceededEvent444  | ServerOpeningEvent445  | TopologyClosedEvent446  | TopologyDescriptionChangedEvent447  | TopologyOpeningEvent;448 449/** @internal */450export interface LogConvertible extends Record<string, any> {451  toLog(): Record<string, any>;452}453 454type BSONObject =455  | BSONRegExp456  | BSONSymbol457  | Code458  | DBRef459  | Decimal128460  | Double461  | Int32462  | Long463  | MaxKey464  | MinKey465  | ObjectId466  | Timestamp467  | Binary;468/** @internal */469export function stringifyWithMaxLen(470  value: any,471  maxDocumentLength: number,472  options: EJSONOptions = {}473): string {474  let strToTruncate = '';475 476  let currentLength = 0;477  const maxDocumentLengthEnsurer = function maxDocumentLengthEnsurer(key: string, value: any) {478    if (currentLength >= maxDocumentLength) {479      return undefined;480    }481    // Account for root document482    if (key === '') {483      // Account for starting brace484      currentLength += 1;485      return value;486    }487 488    // +4 accounts for 2 quotation marks, colon and comma after value489    // Note that this potentially undercounts since it does not account for escape sequences which490    // will have an additional backslash added to them once passed through JSON.stringify.491    currentLength += key.length + 4;492 493    if (value == null) return value;494 495    switch (typeof value) {496      case 'string':497        // +2 accounts for quotes498        // Note that this potentially undercounts similarly to the key length calculation499        currentLength += value.length + 2;500        break;501      case 'number':502      case 'bigint':503        currentLength += String(value).length;504        break;505      case 'boolean':506        currentLength += value ? 4 : 5;507        break;508      case 'object':509        if (isUint8Array(value)) {510          // '{"$binary":{"base64":"<base64 string>","subType":"XX"}}'511          // This is an estimate based on the fact that the base64 is approximately 1.33x the length of512          // the actual binary sequence https://en.wikipedia.org/wiki/Base64513          currentLength += (22 + value.byteLength + value.byteLength * 0.33 + 18) | 0;514        } else if ('_bsontype' in value) {515          const v = value as BSONObject;516          switch (v._bsontype) {517            case 'Int32':518              currentLength += String(v.value).length;519              break;520            case 'Double':521              // Account for representing integers as <value>.0522              currentLength +=523                (v.value | 0) === v.value ? String(v.value).length + 2 : String(v.value).length;524              break;525            case 'Long':526              currentLength += v.toString().length;527              break;528            case 'ObjectId':529              // '{"$oid":"XXXXXXXXXXXXXXXXXXXXXXXX"}'530              currentLength += 35;531              break;532            case 'MaxKey':533            case 'MinKey':534              // '{"$maxKey":1}' or '{"$minKey":1}'535              currentLength += 13;536              break;537            case 'Binary':538              // '{"$binary":{"base64":"<base64 string>","subType":"XX"}}'539              // This is an estimate based on the fact that the base64 is approximately 1.33x the length of540              // the actual binary sequence https://en.wikipedia.org/wiki/Base64541              currentLength += (22 + value.position + value.position * 0.33 + 18) | 0;542              break;543            case 'Timestamp':544              // '{"$timestamp":{"t":<t>,"i":<i>}}'545              currentLength += 19 + String(v.t).length + 5 + String(v.i).length + 2;546              break;547            case 'Code':548              // '{"$code":"<code>"}' or '{"$code":"<code>","$scope":<scope>}'549              if (v.scope == null) {550                currentLength += v.code.length + 10 + 2;551              } else {552                // Ignoring actual scope object, so this undercounts by a significant amount553                currentLength += v.code.length + 10 + 11;554              }555              break;556            case 'BSONRegExp':557              // '{"$regularExpression":{"pattern":"<pattern>","options":"<options>"}}'558              currentLength += 34 + v.pattern.length + 13 + v.options.length + 3;559              break;560          }561        }562    }563    return value;564  };565 566  if (typeof value === 'string') {567    strToTruncate = value;568  } else if (typeof value === 'function') {569    strToTruncate = value.name;570  } else {571    try {572      if (maxDocumentLength !== 0) {573        strToTruncate = EJSON.stringify(value, maxDocumentLengthEnsurer, 0, options);574      } else {575        strToTruncate = EJSON.stringify(value, options);576      }577    } catch (e) {578      strToTruncate = `Extended JSON serialization failed with: ${e.message}`;579    }580  }581 582  // handle truncation that occurs in the middle of multi-byte codepoints583  if (584    maxDocumentLength !== 0 &&585    strToTruncate.length > maxDocumentLength &&586    strToTruncate.charCodeAt(maxDocumentLength - 1) !==587      strToTruncate.codePointAt(maxDocumentLength - 1)588  ) {589    maxDocumentLength--;590    if (maxDocumentLength === 0) {591      return '';592    }593  }594 595  return maxDocumentLength !== 0 && strToTruncate.length > maxDocumentLength596    ? `${strToTruncate.slice(0, maxDocumentLength)}...`597    : strToTruncate;598}599 600/** @internal */601export type Loggable = LoggableEvent | LogConvertible;602 603function isLogConvertible(obj: Loggable): obj is LogConvertible {604  const objAsLogConvertible = obj as LogConvertible;605  // eslint-disable-next-line no-restricted-syntax606  return objAsLogConvertible.toLog !== undefined && typeof objAsLogConvertible.toLog === 'function';607}608 609function attachServerSelectionFields(610  log: Record<string, any>,611  serverSelectionEvent: ServerSelectionEvent,612  maxDocumentLength: number = DEFAULT_MAX_DOCUMENT_LENGTH613) {614  const { selector, operation, topologyDescription, message } = serverSelectionEvent;615  log.selector = stringifyWithMaxLen(selector, maxDocumentLength);616  log.operation = operation;617  log.topologyDescription = stringifyWithMaxLen(topologyDescription, maxDocumentLength);618  log.message = message;619 620  return log;621}622 623function attachCommandFields(624  log: Record<string, any>,625  commandEvent: CommandStartedEvent | LoggableCommandSucceededEvent | LoggableCommandFailedEvent626) {627  log.commandName = commandEvent.commandName;628  log.requestId = commandEvent.requestId;629  log.driverConnectionId = commandEvent.connectionId;630  const { host, port } = HostAddress.fromString(commandEvent.address).toHostPort();631  log.serverHost = host;632  log.serverPort = port;633  if (commandEvent?.serviceId) {634    log.serviceId = commandEvent.serviceId.toHexString();635  }636  log.databaseName = commandEvent.databaseName;637  log.serverConnectionId = commandEvent.serverConnectionId;638 639  return log;640}641 642function attachConnectionFields(log: Record<string, any>, event: any) {643  const { host, port } = HostAddress.fromString(event.address).toHostPort();644  log.serverHost = host;645  log.serverPort = port;646 647  return log;648}649 650function attachSDAMFields(log: Record<string, any>, sdamEvent: SDAMLoggableEvent) {651  log.topologyId = sdamEvent.topologyId;652  return log;653}654 655function attachServerHeartbeatFields(656  log: Record<string, any>,657  serverHeartbeatEvent:658    | LoggableServerHeartbeatFailedEvent659    | LoggableServerHeartbeatStartedEvent660    | LoggableServerHeartbeatSucceededEvent661) {662  const { awaited, connectionId } = serverHeartbeatEvent;663  log.awaited = awaited;664  log.driverConnectionId = serverHeartbeatEvent.connectionId;665  const { host, port } = HostAddress.fromString(connectionId).toHostPort();666  log.serverHost = host;667  log.serverPort = port;668  return log;669}670 671/** @internal */672export function defaultLogTransform(673  logObject: LoggableEvent | Record<string, any>,674  maxDocumentLength: number = DEFAULT_MAX_DOCUMENT_LENGTH675): Omit<Log, 's' | 't' | 'c'> {676  let log: Omit<Log, 's' | 't' | 'c'> = Object.create(null);677 678  switch (logObject.name) {679    case SERVER_SELECTION_STARTED:680      log = attachServerSelectionFields(log, logObject, maxDocumentLength);681      return log;682    case SERVER_SELECTION_FAILED:683      log = attachServerSelectionFields(log, logObject, maxDocumentLength);684      log.failure = logObject.failure?.message;685      return log;686    case SERVER_SELECTION_SUCCEEDED:687      log = attachServerSelectionFields(log, logObject, maxDocumentLength);688      log.serverHost = logObject.serverHost;689      log.serverPort = logObject.serverPort;690      return log;691    case WAITING_FOR_SUITABLE_SERVER:692      log = attachServerSelectionFields(log, logObject, maxDocumentLength);693      log.remainingTimeMS = logObject.remainingTimeMS;694      return log;695    case COMMAND_STARTED:696      log = attachCommandFields(log, logObject);697      log.message = 'Command started';698      log.command = stringifyWithMaxLen(logObject.command, maxDocumentLength, { relaxed: true });699      log.databaseName = logObject.databaseName;700      return log;701    case COMMAND_SUCCEEDED:702      log = attachCommandFields(log, logObject);703      log.message = 'Command succeeded';704      log.durationMS = logObject.duration;705      log.reply = stringifyWithMaxLen(logObject.reply, maxDocumentLength, { relaxed: true });706      return log;707    case COMMAND_FAILED:708      log = attachCommandFields(log, logObject);709      log.message = 'Command failed';710      log.durationMS = logObject.duration;711      log.failure = logObject.failure?.message ?? '(redacted)';712      return log;713    case CONNECTION_POOL_CREATED:714      log = attachConnectionFields(log, logObject);715      log.message = 'Connection pool created';716      if (logObject.options) {717        const { maxIdleTimeMS, minPoolSize, maxPoolSize, maxConnecting, waitQueueTimeoutMS } =718          logObject.options;719        log = {720          ...log,721          maxIdleTimeMS,722          minPoolSize,723          maxPoolSize,724          maxConnecting,725          waitQueueTimeoutMS726        };727      }728      return log;729    case CONNECTION_POOL_READY:730      log = attachConnectionFields(log, logObject);731      log.message = 'Connection pool ready';732      return log;733    case CONNECTION_POOL_CLEARED:734      log = attachConnectionFields(log, logObject);735      log.message = 'Connection pool cleared';736      if (logObject.serviceId?._bsontype === 'ObjectId') {737        log.serviceId = logObject.serviceId?.toHexString();738      }739      return log;740    case CONNECTION_POOL_CLOSED:741      log = attachConnectionFields(log, logObject);742      log.message = 'Connection pool closed';743      return log;744    case CONNECTION_CREATED:745      log = attachConnectionFields(log, logObject);746      log.message = 'Connection created';747      log.driverConnectionId = logObject.connectionId;748      return log;749    case CONNECTION_READY:750      log = attachConnectionFields(log, logObject);751      log.message = 'Connection ready';752      log.driverConnectionId = logObject.connectionId;753      log.durationMS = logObject.durationMS;754      return log;755    case CONNECTION_CLOSED:756      log = attachConnectionFields(log, logObject);757      log.message = 'Connection closed';758      log.driverConnectionId = logObject.connectionId;759      switch (logObject.reason) {760        case 'stale':761          log.reason = 'Connection became stale because the pool was cleared';762          break;763        case 'idle':764          log.reason =765            'Connection has been available but unused for longer than the configured max idle time';766          break;767        case 'error':768          log.reason = 'An error occurred while using the connection';769          if (logObject.error) {770            log.error = logObject.error;771          }772          break;773        case 'poolClosed':774          log.reason = 'Connection pool was closed';775          break;776        default:777          log.reason = `Unknown close reason: ${logObject.reason}`;778      }779      return log;780    case CONNECTION_CHECK_OUT_STARTED:781      log = attachConnectionFields(log, logObject);782      log.message = 'Connection checkout started';783      return log;784    case CONNECTION_CHECK_OUT_FAILED:785      log = attachConnectionFields(log, logObject);786      log.message = 'Connection checkout failed';787      switch (logObject.reason) {788        case 'poolClosed':789          log.reason = 'Connection pool was closed';790          break;791        case 'timeout':792          log.reason = 'Wait queue timeout elapsed without a connection becoming available';793          break;794        case 'connectionError':795          log.reason = 'An error occurred while trying to establish a new connection';796          if (logObject.error) {797            log.error = logObject.error;798          }799          break;800        default:801          log.reason = `Unknown close reason: ${logObject.reason}`;802      }803      log.durationMS = logObject.durationMS;804      return log;805    case CONNECTION_CHECKED_OUT:806      log = attachConnectionFields(log, logObject);807      log.message = 'Connection checked out';808      log.driverConnectionId = logObject.connectionId;809      log.durationMS = logObject.durationMS;810      return log;811    case CONNECTION_CHECKED_IN:812      log = attachConnectionFields(log, logObject);813      log.message = 'Connection checked in';814      log.driverConnectionId = logObject.connectionId;815      return log;816    case SERVER_OPENING:817      log = attachSDAMFields(log, logObject);818      log = attachConnectionFields(log, logObject);819      log.message = 'Starting server monitoring';820      return log;821    case SERVER_CLOSED:822      log = attachSDAMFields(log, logObject);823      log = attachConnectionFields(log, logObject);824      log.message = 'Stopped server monitoring';825      return log;826    case SERVER_HEARTBEAT_STARTED:827      log = attachSDAMFields(log, logObject);828      log = attachServerHeartbeatFields(log, logObject);829      log.message = 'Server heartbeat started';830      return log;831    case SERVER_HEARTBEAT_SUCCEEDED:832      log = attachSDAMFields(log, logObject);833      log = attachServerHeartbeatFields(log, logObject);834      log.message = 'Server heartbeat succeeded';835      log.durationMS = logObject.duration;836      log.serverConnectionId = logObject.serverConnectionId;837      log.reply = stringifyWithMaxLen(logObject.reply, maxDocumentLength, { relaxed: true });838      return log;839    case SERVER_HEARTBEAT_FAILED:840      log = attachSDAMFields(log, logObject);841      log = attachServerHeartbeatFields(log, logObject);842      log.message = 'Server heartbeat failed';843      log.durationMS = logObject.duration;844      log.failure = logObject.failure?.message;845      return log;846    case TOPOLOGY_OPENING:847      log = attachSDAMFields(log, logObject);848      log.message = 'Starting topology monitoring';849      return log;850    case TOPOLOGY_CLOSED:851      log = attachSDAMFields(log, logObject);852      log.message = 'Stopped topology monitoring';853      return log;854    case TOPOLOGY_DESCRIPTION_CHANGED:855      log = attachSDAMFields(log, logObject);856      log.message = 'Topology description changed';857      log.previousDescription = log.reply = stringifyWithMaxLen(858        logObject.previousDescription,859        maxDocumentLength860      );861      log.newDescription = log.reply = stringifyWithMaxLen(862        logObject.newDescription,863        maxDocumentLength864      );865      return log;866    default:867      for (const [key, value] of Object.entries(logObject)) {868        if (value != null) log[key] = value;869      }870  }871  return log;872}873 874/** @internal */875export class MongoLogger {876  componentSeverities: Record<MongoLoggableComponent, SeverityLevel>;877  maxDocumentLength: number;878  logDestination: MongoDBLogWritable;879  logDestinationIsStdErr: boolean;880  pendingLog: PromiseLike<unknown> | unknown = null;881  private severities: Record<MongoLoggableComponent, Record<SeverityLevel, boolean>>;882 883  /**884   * This method should be used when logging errors that do not have a public driver API for885   * reporting errors.886   */887  error = this.log.bind(this, 'error');888  /**889   * This method should be used to log situations where undesirable application behaviour might890   * occur. For example, failing to end sessions on `MongoClient.close`.891   */892  warn = this.log.bind(this, 'warn');893  /**894   * This method should be used to report high-level information about normal driver behaviour.895   * For example, the creation of a `MongoClient`.896   */897  info = this.log.bind(this, 'info');898  /**899   * This method should be used to report information that would be helpful when debugging an900   * application. For example, a command starting, succeeding or failing.901   */902  debug = this.log.bind(this, 'debug');903  /**904   * This method should be used to report fine-grained details related to logic flow. For example,905   * entering and exiting a function body.906   */907  trace = this.log.bind(this, 'trace');908 909  constructor(options: MongoLoggerOptions) {910    this.componentSeverities = options.componentSeverities;911    this.maxDocumentLength = options.maxDocumentLength;912    this.logDestination = options.logDestination;913    this.logDestinationIsStdErr = options.logDestinationIsStdErr;914    this.severities = this.createLoggingSeverities();915  }916 917  createLoggingSeverities(): Record<MongoLoggableComponent, Record<SeverityLevel, boolean>> {918    const severities = Object();919    for (const component of Object.values(MongoLoggableComponent)) {920      severities[component] = {};921      for (const severityLevel of Object.values(SeverityLevel)) {922        severities[component][severityLevel] =923          compareSeverity(severityLevel, this.componentSeverities[component]) <= 0;924      }925    }926    return severities;927  }928 929  turnOffSeverities() {930    for (const component of Object.values(MongoLoggableComponent)) {931      this.componentSeverities[component] = SeverityLevel.OFF;932      for (const severityLevel of Object.values(SeverityLevel)) {933        this.severities[component][severityLevel] = false;934      }935    }936  }937 938  private logWriteFailureHandler(error: Error) {939    if (this.logDestinationIsStdErr) {940      this.turnOffSeverities();941      this.clearPendingLog();942      return;943    }944    this.logDestination = createStdioLogger(process.stderr);945    this.logDestinationIsStdErr = true;946    this.clearPendingLog();947    this.error(MongoLoggableComponent.CLIENT, {948      toLog: function () {949        return {950          message: 'User input for mongodbLogPath is now invalid. Logging is halted.',951          error: error.message952        };953      }954    });955    this.turnOffSeverities();956    this.clearPendingLog();957  }958 959  private clearPendingLog() {960    this.pendingLog = null;961  }962 963  willLog(component: MongoLoggableComponent, severity: SeverityLevel): boolean {964    if (severity === SeverityLevel.OFF) return false;965    return this.severities[component][severity];966  }967 968  private log(969    severity: SeverityLevel,970    component: MongoLoggableComponent,971    message: Loggable | string972  ): void {973    if (!this.willLog(component, severity)) return;974 975    let logMessage: Log = { t: new Date(), c: component, s: severity };976    if (typeof message === 'string') {977      logMessage.message = message;978    } else if (typeof message === 'object') {979      if (isLogConvertible(message)) {980        logMessage = { ...logMessage, ...message.toLog() };981      } else {982        logMessage = { ...logMessage, ...defaultLogTransform(message, this.maxDocumentLength) };983      }984    }985 986    if (isPromiseLike(this.pendingLog)) {987      this.pendingLog = this.pendingLog988 989        .then(() => this.logDestination.write(logMessage))990 991        .then(this.clearPendingLog.bind(this), this.logWriteFailureHandler.bind(this));992      return;993    }994 995    try {996      const logResult = this.logDestination.write(logMessage);997      if (isPromiseLike(logResult)) {998        this.pendingLog = logResult.then(999          this.clearPendingLog.bind(this),1000          this.logWriteFailureHandler.bind(this)1001        );1002      }1003    } catch (error) {1004      this.logWriteFailureHandler(error);1005    }1006  }1007 1008  /**1009   * Merges options set through environment variables and the MongoClient, preferring environment1010   * variables when both are set, and substituting defaults for values not set. Options set in1011   * constructor take precedence over both environment variables and MongoClient options.1012   *1013   * @remarks1014   * When parsing component severity levels, invalid values are treated as unset and replaced with1015   * the default severity.1016   *1017   * @param envOptions - options set for the logger from the environment1018   * @param clientOptions - options set for the logger in the MongoClient options1019   * @returns a MongoLoggerOptions object to be used when instantiating a new MongoLogger1020   */1021  static resolveOptions(1022    envOptions: MongoLoggerEnvOptions,1023    clientOptions: MongoLoggerMongoClientOptions1024  ): MongoLoggerOptions {1025    // client options take precedence over env options1026    const resolvedLogPath = resolveLogPath(envOptions, clientOptions);1027    const combinedOptions = {1028      ...envOptions,1029      ...clientOptions,1030      mongodbLogPath: resolvedLogPath.mongodbLogPath,1031      mongodbLogPathIsStdErr: resolvedLogPath.mongodbLogPathIsStdErr1032    };1033    const defaultSeverity = resolveSeverityConfiguration(1034      combinedOptions.mongodbLogComponentSeverities?.default,1035      combinedOptions.MONGODB_LOG_ALL,1036      SeverityLevel.OFF1037    );1038 1039    return {1040      componentSeverities: {1041        command: resolveSeverityConfiguration(1042          combinedOptions.mongodbLogComponentSeverities?.command,1043          combinedOptions.MONGODB_LOG_COMMAND,1044          defaultSeverity1045        ),1046        topology: resolveSeverityConfiguration(1047          combinedOptions.mongodbLogComponentSeverities?.topology,1048          combinedOptions.MONGODB_LOG_TOPOLOGY,1049          defaultSeverity1050        ),1051        serverSelection: resolveSeverityConfiguration(1052          combinedOptions.mongodbLogComponentSeverities?.serverSelection,1053          combinedOptions.MONGODB_LOG_SERVER_SELECTION,1054          defaultSeverity1055        ),1056        connection: resolveSeverityConfiguration(1057          combinedOptions.mongodbLogComponentSeverities?.connection,1058          combinedOptions.MONGODB_LOG_CONNECTION,1059          defaultSeverity1060        ),1061        client: resolveSeverityConfiguration(1062          combinedOptions.mongodbLogComponentSeverities?.client,1063          combinedOptions.MONGODB_LOG_CLIENT,1064          defaultSeverity1065        ),1066        default: defaultSeverity1067      },1068      maxDocumentLength:1069        combinedOptions.mongodbLogMaxDocumentLength ??1070        parseUnsignedInteger(combinedOptions.MONGODB_LOG_MAX_DOCUMENT_LENGTH) ??1071        1000,1072      logDestination: combinedOptions.mongodbLogPath,1073      logDestinationIsStdErr: combinedOptions.mongodbLogPathIsStdErr1074    };1075  }1076}1077