Skip to content

feat(NODE-5672): support standardized logging #4387

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .evergreen/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3517,7 +3517,7 @@ tasks:
- {key: VERSION, value: v6.0-perf}
- {key: TOPOLOGY, value: server}
- {key: AUTH, value: noauth}
- {key: MONGODB_CLIENT_OPTIONS, value: '{"__enableMongoLogger":true,"__internalLoggerConfig":{"MONGODB_LOG_ALL":"trace","MONGODB_LOG_PATH":"stderr"}}'}
- {key: MONGODB_CLIENT_OPTIONS, value: '{"mongodbLogPath":"stderr","mongodbLogComponentSeverities":{"default":"trace"}}'}
- func: install dependencies
- func: bootstrap mongo-orchestration
- func: run spec driver benchmarks
Expand Down
9 changes: 3 additions & 6 deletions .evergreen/generate_evergreen_tasks.js
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,7 @@ SINGLETON_TASKS.push(
updateExpansions({
VERSION: 'latest',
TOPOLOGY: 'replica_set',
NODE_LTS_VERSION: LATEST_LTS
NODE_LTS_VERSION: LATEST_LTS
}),
{ func: 'install dependencies' },
{ func: 'bootstrap mongo-orchestration' },
Expand Down Expand Up @@ -750,11 +750,8 @@ function addPerformanceTasks() {
monitorCommands: true
}),
makePerfTask('run-spec-benchmark-tests-node-server-logging', {
__enableMongoLogger: true,
__internalLoggerConfig: {
MONGODB_LOG_ALL: 'trace',
MONGODB_LOG_PATH: 'stderr'
}
mongodbLogPath: 'stderr',
mongodbLogComponentSeverities: { default: 'trace' }
})
];

Expand Down
4 changes: 2 additions & 2 deletions .evergreen/run-benchmarks.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export MONGODB_CLIENT_OPTIONS=$MONGODB_CLIENT_OPTIONS
npm run build:ts


# If MONGODB_CLIENT_OPTIONS contains __enableMongoLogger redirect stderr to a file
if [[ $MONGODB_CLIENT_OPTIONS == *"__enableMongoLogger"* ]]; then
# If MONGODB_CLIENT_OPTIONS contains mongodbLogComponentSeverities redirect stderr to a file
if [[ $MONGODB_CLIENT_OPTIONS == *"mongodbLogComponentSeverities"* ]]; then
npm run check:bench 2> bench.log
else
npm run check:bench
Expand Down
128 changes: 49 additions & 79 deletions src/connection_string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,7 @@ import {
type ServerApi,
ServerApiVersion
} from './mongo_client';
import {
MongoLoggableComponent,
MongoLogger,
type MongoLoggerEnvOptions,
type MongoLoggerMongoClientOptions,
SeverityLevel
} from './mongo_logger';
import { MongoLoggableComponent, MongoLogger, SeverityLevel } from './mongo_logger';
import { ReadConcern, type ReadConcernLevel } from './read_concern';
import { ReadPreference, type ReadPreferenceMode } from './read_preference';
import { ServerMonitoringMode } from './sdam/monitor';
Expand Down Expand Up @@ -528,31 +522,22 @@ export function parseOptions(
);
}

mongoOptions.__enableMongoLogger = mongoOptions.__enableMongoLogger ?? false;

let loggerEnvOptions: MongoLoggerEnvOptions = {};
let loggerClientOptions: MongoLoggerMongoClientOptions = {};
if (mongoOptions.__enableMongoLogger) {
loggerEnvOptions = {
mongoOptions.mongoLoggerOptions = MongoLogger.resolveOptions(
{
MONGODB_LOG_COMMAND: process.env.MONGODB_LOG_COMMAND,
MONGODB_LOG_TOPOLOGY: process.env.MONGODB_LOG_TOPOLOGY,
MONGODB_LOG_SERVER_SELECTION: process.env.MONGODB_LOG_SERVER_SELECTION,
MONGODB_LOG_CONNECTION: process.env.MONGODB_LOG_CONNECTION,
MONGODB_LOG_CLIENT: process.env.MONGODB_LOG_CLIENT,
MONGODB_LOG_ALL: process.env.MONGODB_LOG_ALL,
MONGODB_LOG_MAX_DOCUMENT_LENGTH: process.env.MONGODB_LOG_MAX_DOCUMENT_LENGTH,
MONGODB_LOG_PATH: process.env.MONGODB_LOG_PATH,
...mongoOptions.__internalLoggerConfig
};
loggerClientOptions = {
MONGODB_LOG_PATH: process.env.MONGODB_LOG_PATH
},
{
mongodbLogPath: mongoOptions.mongodbLogPath,
mongodbLogComponentSeverities: mongoOptions.mongodbLogComponentSeverities,
mongodbLogMaxDocumentLength: mongoOptions.mongodbLogMaxDocumentLength
};
}
mongoOptions.mongoLoggerOptions = MongoLogger.resolveOptions(
loggerEnvOptions,
loggerClientOptions
}
);

mongoOptions.metadata = makeClientMetadata(mongoOptions);
Expand Down Expand Up @@ -1232,52 +1217,6 @@ export const OPTIONS = {
default: 0,
type: 'int'
},
// Custom types for modifying core behavior
connectionType: { type: 'any' },
srvPoller: { type: 'any' },
// Accepted Node.js Options
allowPartialTrustChain: { type: 'any' },
minDHSize: { type: 'any' },
pskCallback: { type: 'any' },
secureContext: { type: 'any' },
enableTrace: { type: 'any' },
requestCert: { type: 'any' },
rejectUnauthorized: { type: 'any' },
checkServerIdentity: { type: 'any' },
ALPNProtocols: { type: 'any' },
SNICallback: { type: 'any' },
session: { type: 'any' },
requestOCSP: { type: 'any' },
localAddress: { type: 'any' },
localPort: { type: 'any' },
hints: { type: 'any' },
lookup: { type: 'any' },
ca: { type: 'any' },
cert: { type: 'any' },
ciphers: { type: 'any' },
crl: { type: 'any' },
ecdhCurve: { type: 'any' },
key: { type: 'any' },
passphrase: { type: 'any' },
pfx: { type: 'any' },
secureProtocol: { type: 'any' },
index: { type: 'any' },
// Legacy options from v3 era
useNewUrlParser: {
type: 'boolean',
deprecated:
'useNewUrlParser has no effect since Node.js Driver version 4.0.0 and will be removed in the next major version'
} as OptionDescriptor,
useUnifiedTopology: {
type: 'boolean',
deprecated:
'useUnifiedTopology has no effect since Node.js Driver version 4.0.0 and will be removed in the next major version'
} as OptionDescriptor,
// MongoLogger
/**
* @internal
* TODO: NODE-5671 - remove internal flag
*/
mongodbLogPath: {
transform({ values: [value] }) {
if (
Expand All @@ -1296,10 +1235,6 @@ export const OPTIONS = {
return value;
}
},
/**
* @internal
* TODO: NODE-5671 - remove internal flag
*/
mongodbLogComponentSeverities: {
transform({ values: [value] }) {
if (typeof value !== 'object' || !value) {
Expand All @@ -1325,14 +1260,49 @@ export const OPTIONS = {
return value;
}
},
/**
* @internal
* TODO: NODE-5671 - remove internal flag
*/
mongodbLogMaxDocumentLength: { type: 'uint' },
__enableMongoLogger: { type: 'boolean' },
__skipPingOnConnect: { type: 'boolean' },
__internalLoggerConfig: { type: 'record' }
// Custom types for modifying core behavior
connectionType: { type: 'any' },
srvPoller: { type: 'any' },
// Accepted Node.js Options
allowPartialTrustChain: { type: 'any' },
minDHSize: { type: 'any' },
pskCallback: { type: 'any' },
secureContext: { type: 'any' },
enableTrace: { type: 'any' },
requestCert: { type: 'any' },
rejectUnauthorized: { type: 'any' },
checkServerIdentity: { type: 'any' },
ALPNProtocols: { type: 'any' },
SNICallback: { type: 'any' },
session: { type: 'any' },
requestOCSP: { type: 'any' },
localAddress: { type: 'any' },
localPort: { type: 'any' },
hints: { type: 'any' },
lookup: { type: 'any' },
ca: { type: 'any' },
cert: { type: 'any' },
ciphers: { type: 'any' },
crl: { type: 'any' },
ecdhCurve: { type: 'any' },
key: { type: 'any' },
passphrase: { type: 'any' },
pfx: { type: 'any' },
secureProtocol: { type: 'any' },
index: { type: 'any' },
// Legacy options from v3 era
useNewUrlParser: {
type: 'boolean',
deprecated:
'useNewUrlParser has no effect since Node.js Driver version 4.0.0 and will be removed in the next major version'
} as OptionDescriptor,
useUnifiedTopology: {
type: 'boolean',
deprecated:
'useUnifiedTopology has no effect since Node.js Driver version 4.0.0 and will be removed in the next major version'
} as OptionDescriptor,
__skipPingOnConnect: { type: 'boolean' }
} as Record<keyof MongoClientOptions, OptionDescriptor>;

export const DEFAULT_OPTIONS = new CaseInsensitiveMap(
Expand Down
5 changes: 2 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export { CURSOR_FLAGS, CursorTimeoutMode } from './cursor/abstract_cursor';
export { MongoErrorLabel } from './error';
export { ExplainVerbosity } from './explain';
export { ServerApiVersion } from './mongo_client';
export { MongoLoggableComponent, SeverityLevel } from './mongo_logger';
export { ReturnDocument } from './operations/find_and_modify';
export { ProfilingLevel } from './operations/set_profiling_level';
export { ReadConcernLevel } from './read_concern';
Expand Down Expand Up @@ -422,12 +423,10 @@ export type {
LoggableServerHeartbeatStartedEvent,
LoggableServerHeartbeatSucceededEvent,
MongoDBLogWritable,
MongoLoggableComponent,
MongoLogger,
MongoLoggerEnvOptions,
MongoLoggerMongoClientOptions,
MongoLoggerOptions,
SeverityLevel
MongoLoggerOptions
} from './mongo_logger';
export type {
Abortable,
Expand Down
29 changes: 10 additions & 19 deletions src/mongo_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import {
type LogComponentSeveritiesClientOptions,
type MongoDBLogWritable,
MongoLogger,
type MongoLoggerEnvOptions,
type MongoLoggerOptions,
SeverityLevel
} from './mongo_logger';
Expand Down Expand Up @@ -279,33 +278,29 @@ export interface MongoClientOptions extends BSONSerializeOptions, SupportedNodeC
proxyPassword?: string;
/** Instructs the driver monitors to use a specific monitoring mode */
serverMonitoringMode?: ServerMonitoringMode;

/** @internal */
srvPoller?: SrvPoller;
/** @internal */
connectionType?: typeof Connection;
/**
* @internal
* TODO: NODE-5671 - remove internal flag
* @public
* Specifies the destination of the driver's logging. The default is stderr.
*/
mongodbLogPath?: 'stderr' | 'stdout' | MongoDBLogWritable;
/**
* @internal
* TODO: NODE-5671 - remove internal flag
* @public
* Enable logging level per component or use `default` to control any unset components.
*/
mongodbLogComponentSeverities?: LogComponentSeveritiesClientOptions;
/**
* @internal
* TODO: NODE-5671 - remove internal flag
* @public
* All BSON documents are stringified to EJSON. This controls the maximum length of those strings.
* It is defaulted to 1000.
*/
mongodbLogMaxDocumentLength?: number;

/** @internal */
__skipPingOnConnect?: boolean;
srvPoller?: SrvPoller;
/** @internal */
__internalLoggerConfig?: MongoLoggerEnvOptions;
connectionType?: typeof Connection;
/** @internal */
__enableMongoLogger?: boolean;
__skipPingOnConnect?: boolean;
}

/** @public */
Expand Down Expand Up @@ -1062,8 +1057,4 @@ export interface MongoOptions
timeoutMS?: number;
/** @internal */
__skipPingOnConnect?: boolean;
/** @internal */
__internalLoggerConfig?: Document;
/** @internal */
__enableMongoLogger?: boolean;
}
39 changes: 30 additions & 9 deletions src/mongo_logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,11 @@ import type {
} from './sdam/server_selection_events';
import { HostAddress, isPromiseLike, parseUnsignedInteger } from './utils';

/** @internal */
/**
* @public
* Severity levels align with unix syslog.
* Most typical driver functions will log to debug.
*/
export const SeverityLevel = Object.freeze({
EMERGENCY: 'emergency',
ALERT: 'alert',
Expand All @@ -93,7 +97,7 @@ export const SeverityLevel = Object.freeze({

/** @internal */
export const DEFAULT_MAX_DOCUMENT_LENGTH = 1000;
/** @internal */
/** @public */
export type SeverityLevel = (typeof SeverityLevel)[keyof typeof SeverityLevel];

/** @internal */
Expand Down Expand Up @@ -131,7 +135,7 @@ export const SEVERITY_LEVEL_MAP = new SeverityLevelMap([
[SeverityLevel.TRACE, 8]
]);

/** @internal */
/** @public */
export const MongoLoggableComponent = Object.freeze({
COMMAND: 'command',
TOPOLOGY: 'topology',
Expand All @@ -140,7 +144,7 @@ export const MongoLoggableComponent = Object.freeze({
CLIENT: 'client'
} as const);

/** @internal */
/** @public */
export type MongoLoggableComponent =
(typeof MongoLoggableComponent)[keyof typeof MongoLoggableComponent];

Expand All @@ -164,13 +168,13 @@ export interface MongoLoggerEnvOptions {
MONGODB_LOG_PATH?: string;
}

/** @internal */
/** @public */
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 */
/** Optional severity level for server selection component */
serverSelection?: SeverityLevel;
/** Optional severity level for connection component */
connection?: SeverityLevel;
Expand Down Expand Up @@ -292,7 +296,7 @@ function resolveSeverityConfiguration(
);
}

/** @internal */
/** @public */
export interface Log extends Record<string, any> {
t: Date;
c: MongoLoggableComponent;
Expand All @@ -301,10 +305,27 @@ export interface Log extends Record<string, any> {
}

/**
* @internal
* TODO: NODE-5671 - remove internal flag and add API comments
* @public
*
* A custom destination for structured logging messages.
*/
export interface MongoDBLogWritable {
/**
* This function will be called for every enabled log message.
*
* It can be sync or async:
* - If it is synchronous it will block the driver from proceeding until this method returns.
* - If it is asynchronous the driver will not await the returned promise. It will attach fulfillment handling (`.then`).
* If the promise rejects the logger will write an error message to stderr and stop functioning.
* If the promise resolves the driver proceeds to the next log message (or waits for new ones to occur).
*
* Tips:
* - We recommend writing an async `write` function that _never_ rejects.
* Instead handle logging errors as necessary to your use case and make the write function a noop, until it can be recovered.
* - The Log messages are structured but **subject to change** since the intended purpose is informational.
* Program against this defensively and err on the side of stringifying whatever is passed in to write in some form or another.
*
*/
write(log: Log): PromiseLike<unknown> | unknown;
}

Expand Down
Loading