Commit With Frontend and Backend in MERN
This commit is contained in:
930
backend/node_modules/mongodb/src/mongo_logger.ts
generated
vendored
Normal file
930
backend/node_modules/mongodb/src/mongo_logger.ts
generated
vendored
Normal file
@@ -0,0 +1,930 @@
|
||||
import { inspect, promisify } from 'util';
|
||||
|
||||
import { type Document, EJSON, type EJSONOptions, type ObjectId } from './bson';
|
||||
import type { CommandStartedEvent } from './cmap/command_monitoring_events';
|
||||
import type {
|
||||
ConnectionCheckedInEvent,
|
||||
ConnectionCheckedOutEvent,
|
||||
ConnectionCheckOutFailedEvent,
|
||||
ConnectionCheckOutStartedEvent,
|
||||
ConnectionClosedEvent,
|
||||
ConnectionCreatedEvent,
|
||||
ConnectionPoolClearedEvent,
|
||||
ConnectionPoolClosedEvent,
|
||||
ConnectionPoolCreatedEvent,
|
||||
ConnectionPoolReadyEvent,
|
||||
ConnectionReadyEvent
|
||||
} from './cmap/connection_pool_events';
|
||||
import {
|
||||
COMMAND_FAILED,
|
||||
COMMAND_STARTED,
|
||||
COMMAND_SUCCEEDED,
|
||||
CONNECTION_CHECK_OUT_FAILED,
|
||||
CONNECTION_CHECK_OUT_STARTED,
|
||||
CONNECTION_CHECKED_IN,
|
||||
CONNECTION_CHECKED_OUT,
|
||||
CONNECTION_CLOSED,
|
||||
CONNECTION_CREATED,
|
||||
CONNECTION_POOL_CLEARED,
|
||||
CONNECTION_POOL_CLOSED,
|
||||
CONNECTION_POOL_CREATED,
|
||||
CONNECTION_POOL_READY,
|
||||
CONNECTION_READY,
|
||||
SERVER_CLOSED,
|
||||
SERVER_HEARTBEAT_FAILED,
|
||||
SERVER_HEARTBEAT_STARTED,
|
||||
SERVER_HEARTBEAT_SUCCEEDED,
|
||||
SERVER_OPENING,
|
||||
SERVER_SELECTION_FAILED,
|
||||
SERVER_SELECTION_STARTED,
|
||||
SERVER_SELECTION_SUCCEEDED,
|
||||
TOPOLOGY_CLOSED,
|
||||
TOPOLOGY_DESCRIPTION_CHANGED,
|
||||
TOPOLOGY_OPENING,
|
||||
WAITING_FOR_SUITABLE_SERVER
|
||||
} from './constants';
|
||||
import type {
|
||||
ServerClosedEvent,
|
||||
ServerOpeningEvent,
|
||||
TopologyClosedEvent,
|
||||
TopologyDescriptionChangedEvent,
|
||||
TopologyOpeningEvent
|
||||
} from './sdam/events';
|
||||
import type {
|
||||
ServerSelectionEvent,
|
||||
ServerSelectionFailedEvent,
|
||||
ServerSelectionStartedEvent,
|
||||
ServerSelectionSucceededEvent,
|
||||
WaitingForSuitableServerEvent
|
||||
} from './sdam/server_selection_events';
|
||||
import { HostAddress, isPromiseLike, parseUnsignedInteger } from './utils';
|
||||
|
||||
/** @internal */
|
||||
export const SeverityLevel = Object.freeze({
|
||||
EMERGENCY: 'emergency',
|
||||
ALERT: 'alert',
|
||||
CRITICAL: 'critical',
|
||||
ERROR: 'error',
|
||||
WARNING: 'warn',
|
||||
NOTICE: 'notice',
|
||||
INFORMATIONAL: 'info',
|
||||
DEBUG: 'debug',
|
||||
TRACE: 'trace',
|
||||
OFF: 'off'
|
||||
} as const);
|
||||
|
||||
/** @internal */
|
||||
export const DEFAULT_MAX_DOCUMENT_LENGTH = 1000;
|
||||
/** @internal */
|
||||
export type SeverityLevel = (typeof SeverityLevel)[keyof typeof SeverityLevel];
|
||||
|
||||
/** @internal */
|
||||
class SeverityLevelMap extends Map<SeverityLevel | number, SeverityLevel | number> {
|
||||
constructor(entries: [SeverityLevel | number, SeverityLevel | number][]) {
|
||||
const newEntries: [number | SeverityLevel, SeverityLevel | number][] = [];
|
||||
for (const [level, value] of entries) {
|
||||
newEntries.push([value, level]);
|
||||
}
|
||||
|
||||
newEntries.push(...entries);
|
||||
super(newEntries);
|
||||
}
|
||||
|
||||
getNumericSeverityLevel(severity: SeverityLevel): number {
|
||||
return this.get(severity) as number;
|
||||
}
|
||||
|
||||
getSeverityLevelName(level: number): SeverityLevel | undefined {
|
||||
return this.get(level) as SeverityLevel | undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export const SEVERITY_LEVEL_MAP = new SeverityLevelMap([
|
||||
[SeverityLevel.OFF, -Infinity],
|
||||
[SeverityLevel.EMERGENCY, 0],
|
||||
[SeverityLevel.ALERT, 1],
|
||||
[SeverityLevel.CRITICAL, 2],
|
||||
[SeverityLevel.ERROR, 3],
|
||||
[SeverityLevel.WARNING, 4],
|
||||
[SeverityLevel.NOTICE, 5],
|
||||
[SeverityLevel.INFORMATIONAL, 6],
|
||||
[SeverityLevel.DEBUG, 7],
|
||||
[SeverityLevel.TRACE, 8]
|
||||
]);
|
||||
|
||||
/** @internal */
|
||||
export const MongoLoggableComponent = Object.freeze({
|
||||
COMMAND: 'command',
|
||||
TOPOLOGY: 'topology',
|
||||
SERVER_SELECTION: 'serverSelection',
|
||||
CONNECTION: 'connection',
|
||||
CLIENT: 'client'
|
||||
} as const);
|
||||
|
||||
/** @internal */
|
||||
export type MongoLoggableComponent =
|
||||
(typeof MongoLoggableComponent)[keyof typeof MongoLoggableComponent];
|
||||
|
||||
/** @internal */
|
||||
export interface MongoLoggerEnvOptions {
|
||||
/** Severity level for command component */
|
||||
MONGODB_LOG_COMMAND?: string;
|
||||
/** Severity level for topology component */
|
||||
MONGODB_LOG_TOPOLOGY?: string;
|
||||
/** Severity level for server selection component */
|
||||
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. */
|
||||
MONGODB_LOG_MAX_DOCUMENT_LENGTH?: string;
|
||||
/** Destination for log messages. Must be 'stderr', 'stdout'. Defaults to 'stderr'. */
|
||||
MONGODB_LOG_PATH?: string;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface LogComponentSeveritiesClientOptions {
|
||||
/** Optional severity level for command component */
|
||||
command?: SeverityLevel;
|
||||
/** Optional severity level for topology component */
|
||||
topology?: SeverityLevel;
|
||||
/** Optionsl severity level for server selection component */
|
||||
serverSelection?: SeverityLevel;
|
||||
/** Optional severity level for connection component */
|
||||
connection?: SeverityLevel;
|
||||
/** Optional severity level for client component */
|
||||
client?: SeverityLevel;
|
||||
/** Optional default severity level to be used if any of the above are unset */
|
||||
default?: SeverityLevel;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface MongoLoggerMongoClientOptions {
|
||||
/** Destination for log messages */
|
||||
mongodbLogPath?: 'stdout' | 'stderr' | MongoDBLogWritable;
|
||||
/** Severity levels for logger components */
|
||||
mongodbLogComponentSeverities?: LogComponentSeveritiesClientOptions;
|
||||
/** Max length of embedded EJSON docs. Setting to 0 disables truncation. Defaults to 1000. */
|
||||
mongodbLogMaxDocumentLength?: number;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface MongoLoggerOptions {
|
||||
componentSeverities: {
|
||||
/** Severity level for command component */
|
||||
command: SeverityLevel;
|
||||
/** Severity level for topology component */
|
||||
topology: SeverityLevel;
|
||||
/** Severity level for server selection component */
|
||||
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;
|
||||
};
|
||||
/** Max length of embedded EJSON docs. Setting to 0 disables truncation. Defaults to 1000. */
|
||||
maxDocumentLength: number;
|
||||
/** Destination for log messages. */
|
||||
logDestination: MongoDBLogWritable;
|
||||
/** For internal check to see if error should stop logging. */
|
||||
logDestinationIsStdErr: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a string as one of SeverityLevel
|
||||
* @internal
|
||||
*
|
||||
* @param s - the value to be parsed
|
||||
* @returns one of SeverityLevel if value can be parsed as such, otherwise null
|
||||
*/
|
||||
export function parseSeverityFromString(s?: string): SeverityLevel | null {
|
||||
const validSeverities: string[] = Object.values(SeverityLevel);
|
||||
const lowerSeverity = s?.toLowerCase();
|
||||
|
||||
if (lowerSeverity != null && validSeverities.includes(lowerSeverity)) {
|
||||
return lowerSeverity as SeverityLevel;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function createStdioLogger(stream: {
|
||||
write: NodeJS.WriteStream['write'];
|
||||
}): MongoDBLogWritable {
|
||||
return {
|
||||
write: promisify((log: Log, cb: (error?: Error) => void): unknown => {
|
||||
const logLine = inspect(log, { compact: true, breakLength: Infinity });
|
||||
stream.write(`${logLine}\n`, 'utf-8', cb);
|
||||
return;
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* resolves the MONGODB_LOG_PATH and mongodbLogPath options from the environment and the
|
||||
* mongo client options respectively. The mongodbLogPath can be either 'stdout', 'stderr', a NodeJS
|
||||
* Writable or an object which has a `write` method with the signature:
|
||||
* ```ts
|
||||
* write(log: Log): void
|
||||
* ```
|
||||
*
|
||||
* @returns the MongoDBLogWritable object to write logs to
|
||||
*/
|
||||
function resolveLogPath(
|
||||
{ MONGODB_LOG_PATH }: MongoLoggerEnvOptions,
|
||||
{ mongodbLogPath }: MongoLoggerMongoClientOptions
|
||||
): { mongodbLogPath: MongoDBLogWritable; mongodbLogPathIsStdErr: boolean } {
|
||||
if (typeof mongodbLogPath === 'string' && /^stderr$/i.test(mongodbLogPath)) {
|
||||
return { mongodbLogPath: createStdioLogger(process.stderr), mongodbLogPathIsStdErr: true };
|
||||
}
|
||||
if (typeof mongodbLogPath === 'string' && /^stdout$/i.test(mongodbLogPath)) {
|
||||
return { mongodbLogPath: createStdioLogger(process.stdout), mongodbLogPathIsStdErr: false };
|
||||
}
|
||||
|
||||
if (typeof mongodbLogPath === 'object' && typeof mongodbLogPath?.write === 'function') {
|
||||
return { mongodbLogPath: mongodbLogPath, mongodbLogPathIsStdErr: false };
|
||||
}
|
||||
|
||||
if (MONGODB_LOG_PATH && /^stderr$/i.test(MONGODB_LOG_PATH)) {
|
||||
return { mongodbLogPath: createStdioLogger(process.stderr), mongodbLogPathIsStdErr: true };
|
||||
}
|
||||
if (MONGODB_LOG_PATH && /^stdout$/i.test(MONGODB_LOG_PATH)) {
|
||||
return { mongodbLogPath: createStdioLogger(process.stdout), mongodbLogPathIsStdErr: false };
|
||||
}
|
||||
|
||||
return { mongodbLogPath: createStdioLogger(process.stderr), mongodbLogPathIsStdErr: true };
|
||||
}
|
||||
|
||||
function resolveSeverityConfiguration(
|
||||
clientOption: string | undefined,
|
||||
environmentOption: string | undefined,
|
||||
defaultSeverity: SeverityLevel
|
||||
): SeverityLevel {
|
||||
return (
|
||||
parseSeverityFromString(clientOption) ??
|
||||
parseSeverityFromString(environmentOption) ??
|
||||
defaultSeverity
|
||||
);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface Log extends Record<string, any> {
|
||||
t: Date;
|
||||
c: MongoLoggableComponent;
|
||||
s: SeverityLevel;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* TODO: NODE-5671 - remove internal flag and add API comments
|
||||
*/
|
||||
export interface MongoDBLogWritable {
|
||||
write(log: Log): PromiseLike<unknown> | unknown;
|
||||
}
|
||||
|
||||
function compareSeverity(s0: SeverityLevel, s1: SeverityLevel): 1 | 0 | -1 {
|
||||
const s0Num = SEVERITY_LEVEL_MAP.getNumericSeverityLevel(s0);
|
||||
const s1Num = SEVERITY_LEVEL_MAP.getNumericSeverityLevel(s1);
|
||||
|
||||
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
|
||||
*/
|
||||
export type LoggableServerHeartbeatStartedEvent = {
|
||||
topologyId: number;
|
||||
awaited: boolean;
|
||||
connectionId: string;
|
||||
name: typeof SERVER_HEARTBEAT_STARTED;
|
||||
};
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Must be separate from Events API due to differences in spec requirements for logging server heartbeat success
|
||||
*/
|
||||
export type LoggableServerHeartbeatSucceededEvent = {
|
||||
topologyId: number;
|
||||
awaited: boolean;
|
||||
connectionId: string;
|
||||
reply: Document;
|
||||
serverConnectionId: number | '<monitor>';
|
||||
duration: number;
|
||||
name: typeof SERVER_HEARTBEAT_SUCCEEDED;
|
||||
};
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Must be separate from Events API due to differences in spec requirements for logging server heartbeat failure
|
||||
*/
|
||||
export type LoggableServerHeartbeatFailedEvent = {
|
||||
topologyId: number;
|
||||
awaited: boolean;
|
||||
connectionId: string;
|
||||
failure: Error;
|
||||
duration: number;
|
||||
name: typeof SERVER_HEARTBEAT_FAILED;
|
||||
};
|
||||
|
||||
type SDAMLoggableEvent =
|
||||
| ServerClosedEvent
|
||||
| LoggableServerHeartbeatFailedEvent
|
||||
| LoggableServerHeartbeatStartedEvent
|
||||
| LoggableServerHeartbeatSucceededEvent
|
||||
| ServerOpeningEvent
|
||||
| TopologyClosedEvent
|
||||
| TopologyDescriptionChangedEvent
|
||||
| TopologyOpeningEvent;
|
||||
|
||||
/** @internal */
|
||||
export type LoggableEvent =
|
||||
| ServerSelectionStartedEvent
|
||||
| ServerSelectionFailedEvent
|
||||
| ServerSelectionSucceededEvent
|
||||
| WaitingForSuitableServerEvent
|
||||
| CommandStartedEvent
|
||||
| LoggableCommandSucceededEvent
|
||||
| LoggableCommandFailedEvent
|
||||
| ConnectionPoolCreatedEvent
|
||||
| ConnectionPoolReadyEvent
|
||||
| ConnectionPoolClosedEvent
|
||||
| ConnectionPoolClearedEvent
|
||||
| ConnectionCreatedEvent
|
||||
| ConnectionReadyEvent
|
||||
| ConnectionClosedEvent
|
||||
| ConnectionCheckedInEvent
|
||||
| ConnectionCheckedOutEvent
|
||||
| ConnectionCheckOutStartedEvent
|
||||
| ConnectionCheckOutFailedEvent
|
||||
| ServerClosedEvent
|
||||
| LoggableServerHeartbeatFailedEvent
|
||||
| LoggableServerHeartbeatStartedEvent
|
||||
| LoggableServerHeartbeatSucceededEvent
|
||||
| ServerOpeningEvent
|
||||
| TopologyClosedEvent
|
||||
| TopologyDescriptionChangedEvent
|
||||
| TopologyOpeningEvent;
|
||||
|
||||
/** @internal */
|
||||
export interface LogConvertible extends Record<string, any> {
|
||||
toLog(): Record<string, any>;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function stringifyWithMaxLen(
|
||||
value: any,
|
||||
maxDocumentLength: number,
|
||||
options: EJSONOptions = {}
|
||||
): string {
|
||||
let strToTruncate = '';
|
||||
|
||||
if (typeof value === 'string') {
|
||||
strToTruncate = value;
|
||||
} else if (typeof value === 'function') {
|
||||
strToTruncate = value.name;
|
||||
} else {
|
||||
try {
|
||||
strToTruncate = EJSON.stringify(value, options);
|
||||
} catch (e) {
|
||||
strToTruncate = `Extended JSON serialization failed with: ${e.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
// handle truncation that occurs in the middle of multi-byte codepoints
|
||||
if (
|
||||
maxDocumentLength !== 0 &&
|
||||
strToTruncate.length > maxDocumentLength &&
|
||||
strToTruncate.charCodeAt(maxDocumentLength - 1) !==
|
||||
strToTruncate.codePointAt(maxDocumentLength - 1)
|
||||
) {
|
||||
maxDocumentLength--;
|
||||
if (maxDocumentLength === 0) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
return maxDocumentLength !== 0 && strToTruncate.length > maxDocumentLength
|
||||
? `${strToTruncate.slice(0, maxDocumentLength)}...`
|
||||
: strToTruncate;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export type Loggable = LoggableEvent | LogConvertible;
|
||||
|
||||
function isLogConvertible(obj: Loggable): obj is LogConvertible {
|
||||
const objAsLogConvertible = obj as LogConvertible;
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
return objAsLogConvertible.toLog !== undefined && typeof objAsLogConvertible.toLog === 'function';
|
||||
}
|
||||
|
||||
function attachServerSelectionFields(
|
||||
log: Record<string, any>,
|
||||
serverSelectionEvent: ServerSelectionEvent,
|
||||
maxDocumentLength: number = DEFAULT_MAX_DOCUMENT_LENGTH
|
||||
) {
|
||||
const { selector, operation, topologyDescription, message } = serverSelectionEvent;
|
||||
log.selector = stringifyWithMaxLen(selector, maxDocumentLength);
|
||||
log.operation = operation;
|
||||
log.topologyDescription = stringifyWithMaxLen(topologyDescription, maxDocumentLength);
|
||||
log.message = message;
|
||||
|
||||
return log;
|
||||
}
|
||||
|
||||
function attachCommandFields(
|
||||
log: Record<string, any>,
|
||||
commandEvent: CommandStartedEvent | LoggableCommandSucceededEvent | LoggableCommandFailedEvent
|
||||
) {
|
||||
log.commandName = commandEvent.commandName;
|
||||
log.requestId = commandEvent.requestId;
|
||||
log.driverConnectionId = commandEvent.connectionId;
|
||||
const { host, port } = HostAddress.fromString(commandEvent.address).toHostPort();
|
||||
log.serverHost = host;
|
||||
log.serverPort = port;
|
||||
if (commandEvent?.serviceId) {
|
||||
log.serviceId = commandEvent.serviceId.toHexString();
|
||||
}
|
||||
log.databaseName = commandEvent.databaseName;
|
||||
log.serverConnectionId = commandEvent.serverConnectionId;
|
||||
|
||||
return log;
|
||||
}
|
||||
|
||||
function attachConnectionFields(log: Record<string, any>, event: any) {
|
||||
const { host, port } = HostAddress.fromString(event.address).toHostPort();
|
||||
log.serverHost = host;
|
||||
log.serverPort = port;
|
||||
|
||||
return log;
|
||||
}
|
||||
|
||||
function attachSDAMFields(log: Record<string, any>, sdamEvent: SDAMLoggableEvent) {
|
||||
log.topologyId = sdamEvent.topologyId;
|
||||
return log;
|
||||
}
|
||||
|
||||
function attachServerHeartbeatFields(
|
||||
log: Record<string, any>,
|
||||
serverHeartbeatEvent:
|
||||
| LoggableServerHeartbeatFailedEvent
|
||||
| LoggableServerHeartbeatStartedEvent
|
||||
| LoggableServerHeartbeatSucceededEvent
|
||||
) {
|
||||
const { awaited, connectionId } = serverHeartbeatEvent;
|
||||
log.awaited = awaited;
|
||||
log.driverConnectionId = serverHeartbeatEvent.connectionId;
|
||||
const { host, port } = HostAddress.fromString(connectionId).toHostPort();
|
||||
log.serverHost = host;
|
||||
log.serverPort = port;
|
||||
return log;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function defaultLogTransform(
|
||||
logObject: LoggableEvent | Record<string, any>,
|
||||
maxDocumentLength: number = DEFAULT_MAX_DOCUMENT_LENGTH
|
||||
): Omit<Log, 's' | 't' | 'c'> {
|
||||
let log: Omit<Log, 's' | 't' | 'c'> = Object.create(null);
|
||||
|
||||
switch (logObject.name) {
|
||||
case SERVER_SELECTION_STARTED:
|
||||
log = attachServerSelectionFields(log, logObject, maxDocumentLength);
|
||||
return log;
|
||||
case SERVER_SELECTION_FAILED:
|
||||
log = attachServerSelectionFields(log, logObject, maxDocumentLength);
|
||||
log.failure = logObject.failure?.message;
|
||||
return log;
|
||||
case SERVER_SELECTION_SUCCEEDED:
|
||||
log = attachServerSelectionFields(log, logObject, maxDocumentLength);
|
||||
log.serverHost = logObject.serverHost;
|
||||
log.serverPort = logObject.serverPort;
|
||||
return log;
|
||||
case WAITING_FOR_SUITABLE_SERVER:
|
||||
log = attachServerSelectionFields(log, logObject, maxDocumentLength);
|
||||
log.remainingTimeMS = logObject.remainingTimeMS;
|
||||
return log;
|
||||
case COMMAND_STARTED:
|
||||
log = attachCommandFields(log, logObject);
|
||||
log.message = 'Command started';
|
||||
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, { relaxed: true });
|
||||
return log;
|
||||
case COMMAND_FAILED:
|
||||
log = attachCommandFields(log, logObject);
|
||||
log.message = 'Command failed';
|
||||
log.durationMS = logObject.duration;
|
||||
log.failure = logObject.failure?.message ?? '(redacted)';
|
||||
return log;
|
||||
case CONNECTION_POOL_CREATED:
|
||||
log = attachConnectionFields(log, logObject);
|
||||
log.message = 'Connection pool created';
|
||||
if (logObject.options) {
|
||||
const { maxIdleTimeMS, minPoolSize, maxPoolSize, maxConnecting, waitQueueTimeoutMS } =
|
||||
logObject.options;
|
||||
log = {
|
||||
...log,
|
||||
maxIdleTimeMS,
|
||||
minPoolSize,
|
||||
maxPoolSize,
|
||||
maxConnecting,
|
||||
waitQueueTimeoutMS
|
||||
};
|
||||
}
|
||||
return log;
|
||||
case CONNECTION_POOL_READY:
|
||||
log = attachConnectionFields(log, logObject);
|
||||
log.message = 'Connection pool ready';
|
||||
return log;
|
||||
case CONNECTION_POOL_CLEARED:
|
||||
log = attachConnectionFields(log, logObject);
|
||||
log.message = 'Connection pool cleared';
|
||||
if (logObject.serviceId?._bsontype === 'ObjectId') {
|
||||
log.serviceId = logObject.serviceId?.toHexString();
|
||||
}
|
||||
return log;
|
||||
case CONNECTION_POOL_CLOSED:
|
||||
log = attachConnectionFields(log, logObject);
|
||||
log.message = 'Connection pool closed';
|
||||
return log;
|
||||
case CONNECTION_CREATED:
|
||||
log = attachConnectionFields(log, logObject);
|
||||
log.message = 'Connection created';
|
||||
log.driverConnectionId = logObject.connectionId;
|
||||
return log;
|
||||
case CONNECTION_READY:
|
||||
log = attachConnectionFields(log, logObject);
|
||||
log.message = 'Connection ready';
|
||||
log.driverConnectionId = logObject.connectionId;
|
||||
log.durationMS = logObject.durationMS;
|
||||
return log;
|
||||
case CONNECTION_CLOSED:
|
||||
log = attachConnectionFields(log, logObject);
|
||||
log.message = 'Connection closed';
|
||||
log.driverConnectionId = logObject.connectionId;
|
||||
switch (logObject.reason) {
|
||||
case 'stale':
|
||||
log.reason = 'Connection became stale because the pool was cleared';
|
||||
break;
|
||||
case 'idle':
|
||||
log.reason =
|
||||
'Connection has been available but unused for longer than the configured max idle time';
|
||||
break;
|
||||
case 'error':
|
||||
log.reason = 'An error occurred while using the connection';
|
||||
if (logObject.error) {
|
||||
log.error = logObject.error;
|
||||
}
|
||||
break;
|
||||
case 'poolClosed':
|
||||
log.reason = 'Connection pool was closed';
|
||||
break;
|
||||
default:
|
||||
log.reason = `Unknown close reason: ${logObject.reason}`;
|
||||
}
|
||||
return log;
|
||||
case CONNECTION_CHECK_OUT_STARTED:
|
||||
log = attachConnectionFields(log, logObject);
|
||||
log.message = 'Connection checkout started';
|
||||
return log;
|
||||
case CONNECTION_CHECK_OUT_FAILED:
|
||||
log = attachConnectionFields(log, logObject);
|
||||
log.message = 'Connection checkout failed';
|
||||
switch (logObject.reason) {
|
||||
case 'poolClosed':
|
||||
log.reason = 'Connection pool was closed';
|
||||
break;
|
||||
case 'timeout':
|
||||
log.reason = 'Wait queue timeout elapsed without a connection becoming available';
|
||||
break;
|
||||
case 'connectionError':
|
||||
log.reason = 'An error occurred while trying to establish a new connection';
|
||||
if (logObject.error) {
|
||||
log.error = logObject.error;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
log.reason = `Unknown close reason: ${logObject.reason}`;
|
||||
}
|
||||
log.durationMS = logObject.durationMS;
|
||||
return log;
|
||||
case CONNECTION_CHECKED_OUT:
|
||||
log = attachConnectionFields(log, logObject);
|
||||
log.message = 'Connection checked out';
|
||||
log.driverConnectionId = logObject.connectionId;
|
||||
log.durationMS = logObject.durationMS;
|
||||
return log;
|
||||
case CONNECTION_CHECKED_IN:
|
||||
log = attachConnectionFields(log, logObject);
|
||||
log.message = 'Connection checked in';
|
||||
log.driverConnectionId = logObject.connectionId;
|
||||
return log;
|
||||
case SERVER_OPENING:
|
||||
log = attachSDAMFields(log, logObject);
|
||||
log = attachConnectionFields(log, logObject);
|
||||
log.message = 'Starting server monitoring';
|
||||
return log;
|
||||
case SERVER_CLOSED:
|
||||
log = attachSDAMFields(log, logObject);
|
||||
log = attachConnectionFields(log, logObject);
|
||||
log.message = 'Stopped server monitoring';
|
||||
return log;
|
||||
case SERVER_HEARTBEAT_STARTED:
|
||||
log = attachSDAMFields(log, logObject);
|
||||
log = attachServerHeartbeatFields(log, logObject);
|
||||
log.message = 'Server heartbeat started';
|
||||
return log;
|
||||
case SERVER_HEARTBEAT_SUCCEEDED:
|
||||
log = attachSDAMFields(log, logObject);
|
||||
log = attachServerHeartbeatFields(log, logObject);
|
||||
log.message = 'Server heartbeat succeeded';
|
||||
log.durationMS = logObject.duration;
|
||||
log.serverConnectionId = logObject.serverConnectionId;
|
||||
log.reply = stringifyWithMaxLen(logObject.reply, maxDocumentLength, { relaxed: true });
|
||||
return log;
|
||||
case SERVER_HEARTBEAT_FAILED:
|
||||
log = attachSDAMFields(log, logObject);
|
||||
log = attachServerHeartbeatFields(log, logObject);
|
||||
log.message = 'Server heartbeat failed';
|
||||
log.durationMS = logObject.duration;
|
||||
log.failure = logObject.failure?.message;
|
||||
return log;
|
||||
case TOPOLOGY_OPENING:
|
||||
log = attachSDAMFields(log, logObject);
|
||||
log.message = 'Starting topology monitoring';
|
||||
return log;
|
||||
case TOPOLOGY_CLOSED:
|
||||
log = attachSDAMFields(log, logObject);
|
||||
log.message = 'Stopped topology monitoring';
|
||||
return log;
|
||||
case TOPOLOGY_DESCRIPTION_CHANGED:
|
||||
log = attachSDAMFields(log, logObject);
|
||||
log.message = 'Topology description changed';
|
||||
log.previousDescription = log.reply = stringifyWithMaxLen(
|
||||
logObject.previousDescription,
|
||||
maxDocumentLength
|
||||
);
|
||||
log.newDescription = log.reply = stringifyWithMaxLen(
|
||||
logObject.newDescription,
|
||||
maxDocumentLength
|
||||
);
|
||||
return log;
|
||||
default:
|
||||
for (const [key, value] of Object.entries(logObject)) {
|
||||
if (value != null) log[key] = value;
|
||||
}
|
||||
}
|
||||
return log;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export class MongoLogger {
|
||||
componentSeverities: Record<MongoLoggableComponent, SeverityLevel>;
|
||||
maxDocumentLength: number;
|
||||
logDestination: MongoDBLogWritable;
|
||||
logDestinationIsStdErr: boolean;
|
||||
pendingLog: PromiseLike<unknown> | unknown = null;
|
||||
private severities: Record<MongoLoggableComponent, Record<SeverityLevel, boolean>>;
|
||||
|
||||
/**
|
||||
* This method should be used when logging errors that do not have a public driver API for
|
||||
* reporting errors.
|
||||
*/
|
||||
error = this.log.bind(this, 'error');
|
||||
/**
|
||||
* This method should be used to log situations where undesirable application behaviour might
|
||||
* occur. For example, failing to end sessions on `MongoClient.close`.
|
||||
*/
|
||||
warn = this.log.bind(this, 'warn');
|
||||
/**
|
||||
* This method should be used to report high-level information about normal driver behaviour.
|
||||
* For example, the creation of a `MongoClient`.
|
||||
*/
|
||||
info = this.log.bind(this, 'info');
|
||||
/**
|
||||
* This method should be used to report information that would be helpful when debugging an
|
||||
* application. For example, a command starting, succeeding or failing.
|
||||
*/
|
||||
debug = this.log.bind(this, 'debug');
|
||||
/**
|
||||
* This method should be used to report fine-grained details related to logic flow. For example,
|
||||
* entering and exiting a function body.
|
||||
*/
|
||||
trace = this.log.bind(this, 'trace');
|
||||
|
||||
constructor(options: MongoLoggerOptions) {
|
||||
this.componentSeverities = options.componentSeverities;
|
||||
this.maxDocumentLength = options.maxDocumentLength;
|
||||
this.logDestination = options.logDestination;
|
||||
this.logDestinationIsStdErr = options.logDestinationIsStdErr;
|
||||
this.severities = this.createLoggingSeverities();
|
||||
}
|
||||
|
||||
createLoggingSeverities(): Record<MongoLoggableComponent, Record<SeverityLevel, boolean>> {
|
||||
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 component of Object.values(MongoLoggableComponent)) {
|
||||
this.componentSeverities[component] = SeverityLevel.OFF;
|
||||
for (const severityLevel of Object.values(SeverityLevel)) {
|
||||
this.severities[component][severityLevel] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private logWriteFailureHandler(error: Error) {
|
||||
if (this.logDestinationIsStdErr) {
|
||||
this.turnOffSeverities();
|
||||
this.clearPendingLog();
|
||||
return;
|
||||
}
|
||||
this.logDestination = createStdioLogger(process.stderr);
|
||||
this.logDestinationIsStdErr = true;
|
||||
this.clearPendingLog();
|
||||
this.error(MongoLoggableComponent.CLIENT, {
|
||||
toLog: function () {
|
||||
return {
|
||||
message: 'User input for mongodbLogPath is now invalid. Logging is halted.',
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
});
|
||||
this.turnOffSeverities();
|
||||
this.clearPendingLog();
|
||||
}
|
||||
|
||||
private clearPendingLog() {
|
||||
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(component, severity)) return;
|
||||
|
||||
let logMessage: Log = { t: new Date(), c: component, s: severity };
|
||||
if (typeof message === 'string') {
|
||||
logMessage.message = message;
|
||||
} else if (typeof message === 'object') {
|
||||
if (isLogConvertible(message)) {
|
||||
logMessage = { ...logMessage, ...message.toLog() };
|
||||
} else {
|
||||
logMessage = { ...logMessage, ...defaultLogTransform(message, this.maxDocumentLength) };
|
||||
}
|
||||
}
|
||||
|
||||
if (isPromiseLike(this.pendingLog)) {
|
||||
this.pendingLog = this.pendingLog
|
||||
|
||||
.then(() => this.logDestination.write(logMessage))
|
||||
|
||||
.then(this.clearPendingLog.bind(this), this.logWriteFailureHandler.bind(this));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const logResult = this.logDestination.write(logMessage);
|
||||
if (isPromiseLike(logResult)) {
|
||||
this.pendingLog = logResult.then(
|
||||
this.clearPendingLog.bind(this),
|
||||
this.logWriteFailureHandler.bind(this)
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logWriteFailureHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges options set through environment variables and the MongoClient, preferring environment
|
||||
* variables when both are set, and substituting defaults for values not set. Options set in
|
||||
* constructor take precedence over both environment variables and MongoClient options.
|
||||
*
|
||||
* @remarks
|
||||
* When parsing component severity levels, invalid values are treated as unset and replaced with
|
||||
* the default severity.
|
||||
*
|
||||
* @param envOptions - options set for the logger from the environment
|
||||
* @param clientOptions - options set for the logger in the MongoClient options
|
||||
* @returns a MongoLoggerOptions object to be used when instantiating a new MongoLogger
|
||||
*/
|
||||
static resolveOptions(
|
||||
envOptions: MongoLoggerEnvOptions,
|
||||
clientOptions: MongoLoggerMongoClientOptions
|
||||
): MongoLoggerOptions {
|
||||
// client options take precedence over env options
|
||||
const resolvedLogPath = resolveLogPath(envOptions, clientOptions);
|
||||
const combinedOptions = {
|
||||
...envOptions,
|
||||
...clientOptions,
|
||||
mongodbLogPath: resolvedLogPath.mongodbLogPath,
|
||||
mongodbLogPathIsStdErr: resolvedLogPath.mongodbLogPathIsStdErr
|
||||
};
|
||||
const defaultSeverity = resolveSeverityConfiguration(
|
||||
combinedOptions.mongodbLogComponentSeverities?.default,
|
||||
combinedOptions.MONGODB_LOG_ALL,
|
||||
SeverityLevel.OFF
|
||||
);
|
||||
|
||||
return {
|
||||
componentSeverities: {
|
||||
command: resolveSeverityConfiguration(
|
||||
combinedOptions.mongodbLogComponentSeverities?.command,
|
||||
combinedOptions.MONGODB_LOG_COMMAND,
|
||||
defaultSeverity
|
||||
),
|
||||
topology: resolveSeverityConfiguration(
|
||||
combinedOptions.mongodbLogComponentSeverities?.topology,
|
||||
combinedOptions.MONGODB_LOG_TOPOLOGY,
|
||||
defaultSeverity
|
||||
),
|
||||
serverSelection: resolveSeverityConfiguration(
|
||||
combinedOptions.mongodbLogComponentSeverities?.serverSelection,
|
||||
combinedOptions.MONGODB_LOG_SERVER_SELECTION,
|
||||
defaultSeverity
|
||||
),
|
||||
connection: resolveSeverityConfiguration(
|
||||
combinedOptions.mongodbLogComponentSeverities?.connection,
|
||||
combinedOptions.MONGODB_LOG_CONNECTION,
|
||||
defaultSeverity
|
||||
),
|
||||
client: resolveSeverityConfiguration(
|
||||
combinedOptions.mongodbLogComponentSeverities?.client,
|
||||
combinedOptions.MONGODB_LOG_CLIENT,
|
||||
defaultSeverity
|
||||
),
|
||||
default: defaultSeverity
|
||||
},
|
||||
maxDocumentLength:
|
||||
combinedOptions.mongodbLogMaxDocumentLength ??
|
||||
parseUnsignedInteger(combinedOptions.MONGODB_LOG_MAX_DOCUMENT_LENGTH) ??
|
||||
1000,
|
||||
logDestination: combinedOptions.mongodbLogPath,
|
||||
logDestinationIsStdErr: combinedOptions.mongodbLogPathIsStdErr
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user