Skip to content

Commit d6bd1d1

Browse files
authored
feat(NODE-4815): stringify and truncate BSON documents in log messages (#3635)
1 parent 9e02c42 commit d6bd1d1

File tree

2 files changed

+67
-5
lines changed

2 files changed

+67
-5
lines changed

src/mongo_logger.ts

+16-4
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ export const SeverityLevel = Object.freeze({
5353
OFF: 'off'
5454
} as const);
5555

56+
/** @internal */
57+
export const DEFAULT_MAX_DOCUMENT_LENGTH = 1000;
5658
/** @internal */
5759
export type SeverityLevel = (typeof SeverityLevel)[keyof typeof SeverityLevel];
5860

@@ -254,6 +256,15 @@ export interface LogConvertible extends Record<string, any> {
254256
toLog(): Record<string, any>;
255257
}
256258

259+
/** @internal */
260+
export function stringifyWithMaxLen(value: any, maxDocumentLength: number): string {
261+
const ejson = EJSON.stringify(value);
262+
263+
return maxDocumentLength !== 0 && ejson.length > maxDocumentLength
264+
? `${ejson.slice(0, maxDocumentLength)}...`
265+
: ejson;
266+
}
267+
257268
/** @internal */
258269
export type Loggable = LoggableEvent | LogConvertible;
259270

@@ -292,22 +303,23 @@ function attachConnectionFields(
292303
}
293304

294305
function defaultLogTransform(
295-
logObject: LoggableEvent | Record<string, any>
306+
logObject: LoggableEvent | Record<string, any>,
307+
maxDocumentLength: number = DEFAULT_MAX_DOCUMENT_LENGTH
296308
): Omit<Log, 's' | 't' | 'c'> {
297309
let log: Omit<Log, 's' | 't' | 'c'> = Object.create(null);
298310

299311
switch (logObject.name) {
300312
case COMMAND_STARTED:
301313
log = attachCommandFields(log, logObject);
302314
log.message = 'Command started';
303-
log.command = EJSON.stringify(logObject.command);
315+
log.command = stringifyWithMaxLen(logObject.command, maxDocumentLength);
304316
log.databaseName = logObject.databaseName;
305317
return log;
306318
case COMMAND_SUCCEEDED:
307319
log = attachCommandFields(log, logObject);
308320
log.message = 'Command succeeded';
309321
log.durationMS = logObject.duration;
310-
log.reply = EJSON.stringify(logObject.reply);
322+
log.reply = stringifyWithMaxLen(logObject.reply, maxDocumentLength);
311323
return log;
312324
case COMMAND_FAILED:
313325
log = attachCommandFields(log, logObject);
@@ -452,7 +464,7 @@ export class MongoLogger {
452464
if (isLogConvertible(message)) {
453465
logMessage = { ...logMessage, ...message.toLog() };
454466
} else {
455-
logMessage = { ...logMessage, ...defaultLogTransform(message) };
467+
logMessage = { ...logMessage, ...defaultLogTransform(message, this.maxDocumentLength) };
456468
}
457469
}
458470
this.logDestination.write(logMessage);

test/unit/mongo_logger.test.ts

+51-1
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ import {
1919
CONNECTION_POOL_CREATED,
2020
CONNECTION_POOL_READY,
2121
CONNECTION_READY,
22+
DEFAULT_MAX_DOCUMENT_LENGTH,
2223
Log,
2324
MongoDBLogWritable,
2425
MongoLogger,
2526
MongoLoggerOptions,
2627
SEVERITY_LEVEL_MAP,
27-
SeverityLevel
28+
SeverityLevel,
29+
stringifyWithMaxLen
2830
} from '../mongodb';
2931

3032
class BufferingStream extends Writable {
@@ -1239,4 +1241,52 @@ describe('class MongoLogger', function () {
12391241
});
12401242
}
12411243
});
1244+
1245+
describe('stringifyWithMaxLen', function () {
1246+
const largeDoc = {};
1247+
const smallDoc = { test: 'Hello' };
1248+
before(function () {
1249+
for (let i = 0; i < DEFAULT_MAX_DOCUMENT_LENGTH; i++) {
1250+
largeDoc[`test${i}`] = `Hello_${i}`;
1251+
}
1252+
});
1253+
1254+
context('when maxDocumentLength = 0', function () {
1255+
it('does not truncate document', function () {
1256+
expect(stringifyWithMaxLen(largeDoc, 0)).to.equal(EJSON.stringify(largeDoc));
1257+
});
1258+
});
1259+
1260+
context('when maxDocumentLength is non-zero', function () {
1261+
context('when document has length greater than maxDocumentLength', function () {
1262+
it('truncates ejson string to length of maxDocumentLength + 3', function () {
1263+
expect(stringifyWithMaxLen(largeDoc, DEFAULT_MAX_DOCUMENT_LENGTH)).to.have.lengthOf(
1264+
DEFAULT_MAX_DOCUMENT_LENGTH + 3
1265+
);
1266+
});
1267+
it('ends with "..."', function () {
1268+
expect(stringifyWithMaxLen(largeDoc, DEFAULT_MAX_DOCUMENT_LENGTH)).to.match(/^.*\.\.\.$/);
1269+
});
1270+
});
1271+
1272+
context('when document has length less than or equal to maxDocumentLength', function () {
1273+
it('does not truncate document', function () {
1274+
expect(stringifyWithMaxLen(smallDoc, DEFAULT_MAX_DOCUMENT_LENGTH)).to.equal(
1275+
EJSON.stringify(smallDoc)
1276+
);
1277+
});
1278+
it('does not end with "..."', function () {
1279+
expect(stringifyWithMaxLen(smallDoc, DEFAULT_MAX_DOCUMENT_LENGTH)).to.not.match(
1280+
/^.*\.\.\./
1281+
);
1282+
});
1283+
1284+
it('produces valid relaxed EJSON', function () {
1285+
expect(() => {
1286+
EJSON.parse(stringifyWithMaxLen(smallDoc, DEFAULT_MAX_DOCUMENT_LENGTH));
1287+
}).to.not.throw();
1288+
});
1289+
});
1290+
});
1291+
});
12421292
});

0 commit comments

Comments
 (0)