From aed1cf0d2b1083e24997e49bfe7f5416e944466e Mon Sep 17 00:00:00 2001 From: aditi-khare-mongoDB <106987683+aditi-khare-mongoDB@users.noreply.github.com> Date: Fri, 26 Jan 2024 11:43:30 -0500 Subject: [PATCH] fix(NODE-5839): support for multibyte code-points in stringifyWithMaxLen (#3979) --- src/mongo_logger.ts | 27 +++++++++++++--- ...mmand_logging_and_monitoring.prose.test.ts | 12 +++---- test/unit/mongo_logger.test.ts | 31 ++++++++++++++++--- 3 files changed, 55 insertions(+), 15 deletions(-) diff --git a/src/mongo_logger.ts b/src/mongo_logger.ts index 6ffcee304a..57864c4c4e 100644 --- a/src/mongo_logger.ts +++ b/src/mongo_logger.ts @@ -420,10 +420,29 @@ export function stringifyWithMaxLen( ): string { let strToTruncate = ''; - try { - strToTruncate = typeof value !== 'function' ? EJSON.stringify(value, options) : value.name; - } catch (e) { - strToTruncate = `Extended JSON serialization failed with: ${e.message}`; + 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 diff --git a/test/integration/command-logging-and-monitoring/command_logging_and_monitoring.prose.test.ts b/test/integration/command-logging-and-monitoring/command_logging_and_monitoring.prose.test.ts index b6a841b26e..22857a6cc3 100644 --- a/test/integration/command-logging-and-monitoring/command_logging_and_monitoring.prose.test.ts +++ b/test/integration/command-logging-and-monitoring/command_logging_and_monitoring.prose.test.ts @@ -156,7 +156,7 @@ describe('Command Logging and Monitoring Prose Tests', function () { }); }); - context.skip('Truncation with multi-byte codepoints', function () { + context('Truncation with multi-byte codepoints', function () { /* A specific test case is not provided here due to the allowed variations in truncation logic as well as varying extended JSON whitespace usage. @@ -216,12 +216,12 @@ describe('Command Logging and Monitoring Prose Tests', function () { ); // multi-byte codepoint in middle of truncated string - expect(insertManyCommandStarted?.command.charCodeAt(maxDocLength - 1)).to.equal( - firstByteChar - ); - expect(insertManyCommandStarted?.command.charCodeAt(maxDocLength - 1)).to.equal( + expect(insertManyCommandStarted?.command.charCodeAt(maxDocLength - 2)).to.equal( secondByteChar ); + expect(insertManyCommandStarted?.command.charCodeAt(maxDocLength - 3)).to.equal( + firstByteChar + ); const insertManyCommandSucceeded = writable.buffer[1]; expect(insertManyCommandSucceeded?.message).to.equal('Command succeeded'); @@ -230,5 +230,5 @@ describe('Command Logging and Monitoring Prose Tests', function () { maxDocLength + ELLIPSES_LENGTH ); }); - }).skipReason = 'todo(NODE-5839)'; + }); }); diff --git a/test/unit/mongo_logger.test.ts b/test/unit/mongo_logger.test.ts index 7a6e1b2da9..0d60cf4d07 100644 --- a/test/unit/mongo_logger.test.ts +++ b/test/unit/mongo_logger.test.ts @@ -1268,14 +1268,36 @@ describe('class MongoLogger', async function () { context('when maxDocumentLength is non-zero', function () { context('when document has length greater than maxDocumentLength', function () { - it('truncates ejson string to length of maxDocumentLength + 3', function () { - expect(stringifyWithMaxLen(largeDoc, DEFAULT_MAX_DOCUMENT_LENGTH)).to.have.lengthOf( - DEFAULT_MAX_DOCUMENT_LENGTH + 3 - ); + context('when truncation does not occur mid-multibyte codepoint', function () { + it('truncates ejson string to length of maxDocumentLength + 3', function () { + expect(stringifyWithMaxLen(largeDoc, DEFAULT_MAX_DOCUMENT_LENGTH)).to.have.lengthOf( + DEFAULT_MAX_DOCUMENT_LENGTH + 3 + ); + }); }); + it('ends with "..."', function () { expect(stringifyWithMaxLen(largeDoc, DEFAULT_MAX_DOCUMENT_LENGTH)).to.match(/^.*\.\.\.$/); }); + + context('when truncation occurs mid-multibyte codepoint', function () { + const multiByteCodePoint = '\ud83d\ude0d'; // heart eyes emoji + context('when maxDocumentLength = 1 but greater than 0', function () { + it('should return an empty string', function () { + expect(stringifyWithMaxLen(multiByteCodePoint, 1, { relaxed: true })).to.equal(''); + }); + }); + + context('when maxDocumentLength > 1', function () { + it('should round down maxDocLength to previous codepoint', function () { + const randomStringMinusACodePoint = `random ${multiByteCodePoint}random random${multiByteCodePoint}`; + const randomString = `${randomStringMinusACodePoint}${multiByteCodePoint}`; + expect( + stringifyWithMaxLen(randomString, randomString.length - 1, { relaxed: true }) + ).to.equal(`${randomStringMinusACodePoint}...`); + }); + }); + }); }); context('when document has length less than or equal to maxDocumentLength', function () { @@ -1289,7 +1311,6 @@ describe('class MongoLogger', async function () { /^.*\.\.\./ ); }); - it('produces valid relaxed EJSON', function () { expect(() => { EJSON.parse(stringifyWithMaxLen(smallDoc, DEFAULT_MAX_DOCUMENT_LENGTH));