Skip to content

Commit f50da13

Browse files
committed
fix
1 parent 98c71c3 commit f50da13

File tree

4 files changed

+169
-16
lines changed

4 files changed

+169
-16
lines changed

spec/MongoStorageAdapter.spec.js

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -931,5 +931,136 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
931931

932932
await adapter.handleShutdown();
933933
});
934+
935+
it('should handle invalid log level gracefully', async () => {
936+
const logger = require('../lib/logger').logger;
937+
const infoSpy = spyOn(logger, 'info');
938+
939+
const clientLogEvents = [
940+
{
941+
name: 'connectionPoolReady',
942+
keys: ['address'],
943+
logLevel: 'invalidLogLevel', // Invalid log level
944+
},
945+
];
946+
947+
const adapter = new MongoStorageAdapter({
948+
uri: databaseURI,
949+
mongoOptions: { clientLogEvents },
950+
});
951+
952+
await adapter.connect();
953+
954+
const mockEvent = {
955+
address: 'localhost:27017',
956+
};
957+
958+
adapter.client.emit('connectionPoolReady', mockEvent);
959+
960+
// Should fallback to 'info' level
961+
expect(infoSpy).toHaveBeenCalledWith(
962+
jasmine.stringMatching(/MongoDB client event connectionPoolReady:.*"address":"localhost:27017"/)
963+
);
964+
965+
await adapter.handleShutdown();
966+
});
967+
968+
it('should handle Map and Set instances in events', async () => {
969+
const logger = require('../lib/logger').logger;
970+
const warnSpy = spyOn(logger, 'warn');
971+
972+
const clientLogEvents = [
973+
{
974+
name: 'customEvent',
975+
logLevel: 'warn',
976+
},
977+
];
978+
979+
const adapter = new MongoStorageAdapter({
980+
uri: databaseURI,
981+
mongoOptions: { clientLogEvents },
982+
});
983+
984+
await adapter.connect();
985+
986+
const mockEvent = {
987+
mapData: new Map([['key1', 'value1'], ['key2', 'value2']]),
988+
setData: new Set([1, 2, 3]),
989+
};
990+
991+
adapter.client.emit('customEvent', mockEvent);
992+
993+
// Should serialize Map and Set properly
994+
expect(warnSpy).toHaveBeenCalledWith(
995+
jasmine.stringMatching(/MongoDB client event customEvent:.*"mapData":\{"key1":"value1","key2":"value2"\}.*"setData":\[1,2,3\]/)
996+
);
997+
998+
await adapter.handleShutdown();
999+
});
1000+
1001+
it('should handle missing keys in event object', async () => {
1002+
const logger = require('../lib/logger').logger;
1003+
const infoSpy = spyOn(logger, 'info');
1004+
1005+
const clientLogEvents = [
1006+
{
1007+
name: 'testEvent',
1008+
keys: ['nonexistent.nested.key', 'another.missing'],
1009+
logLevel: 'info',
1010+
},
1011+
];
1012+
1013+
const adapter = new MongoStorageAdapter({
1014+
uri: databaseURI,
1015+
mongoOptions: { clientLogEvents },
1016+
});
1017+
1018+
await adapter.connect();
1019+
1020+
const mockEvent = {
1021+
actualField: 'value',
1022+
};
1023+
1024+
adapter.client.emit('testEvent', mockEvent);
1025+
1026+
// Should handle missing keys gracefully with undefined values
1027+
expect(infoSpy).toHaveBeenCalledWith(
1028+
jasmine.stringMatching(/MongoDB client event testEvent:/)
1029+
);
1030+
1031+
await adapter.handleShutdown();
1032+
});
1033+
1034+
it('should handle circular references with fallback warning', async () => {
1035+
const logger = require('../lib/logger').logger;
1036+
const warnSpy = spyOn(logger, 'warn');
1037+
1038+
const clientLogEvents = [
1039+
{
1040+
name: 'circularEvent',
1041+
logLevel: 'info',
1042+
},
1043+
];
1044+
1045+
const adapter = new MongoStorageAdapter({
1046+
uri: databaseURI,
1047+
mongoOptions: { clientLogEvents },
1048+
});
1049+
1050+
await adapter.connect();
1051+
1052+
// Create circular reference
1053+
const mockEvent = { name: 'test' };
1054+
mockEvent.self = mockEvent;
1055+
1056+
adapter.client.emit('circularEvent', mockEvent);
1057+
1058+
// Should fallback to warning when JSON.stringify fails
1059+
expect(warnSpy).toHaveBeenCalledWith(
1060+
jasmine.stringMatching(/MongoDB client event circularEvent logged with error:/)
1061+
);
1062+
1063+
await adapter.handleShutdown();
1064+
});
9341065
});
9351066
});

src/Adapters/Storage/Mongo/MongoStorageAdapter.js

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -211,22 +211,35 @@ export class MongoStorageAdapter implements StorageAdapter {
211211
if (this._clientLogEvents && Array.isArray(this._clientLogEvents)) {
212212
this._clientLogEvents.forEach(eventConfig => {
213213
client.on(eventConfig.name, event => {
214-
let logData = {};
215-
if (!eventConfig.keys || eventConfig.keys.length === 0) {
216-
logData = event;
217-
} else {
218-
eventConfig.keys.forEach(keyPath => {
219-
const keyParts = keyPath.split('.');
220-
let value = event;
221-
keyParts.forEach(part => {
222-
value = value ? value[part] : undefined;
214+
try {
215+
let logData = {};
216+
if (!eventConfig.keys || eventConfig.keys.length === 0) {
217+
logData = event;
218+
} else {
219+
eventConfig.keys.forEach(keyPath => {
220+
logData[keyPath] = _.get(event, keyPath);
223221
});
224-
logData[keyPath] = value;
225-
});
222+
}
223+
224+
// Validate log level exists, fallback to 'info'
225+
const logLevel = typeof logger[eventConfig.logLevel] === 'function' ? eventConfig.logLevel : 'info';
226+
227+
// Safe JSON serialization with Map/Set support
228+
const logMessage = `MongoDB client event ${eventConfig.name}: ${JSON.stringify(logData, (key, value) => {
229+
if (value instanceof Map) {
230+
return Object.fromEntries(value);
231+
}
232+
if (value instanceof Set) {
233+
return Array.from(value);
234+
}
235+
return value;
236+
})}`;
237+
238+
logger[logLevel](logMessage);
239+
} catch (error) {
240+
// Fallback if serialization completely fails
241+
logger.warn(`MongoDB client event ${eventConfig.name} logged with error: ${error.message}`);
226242
}
227-
228-
const logMessage = `MongoDB client event ${eventConfig.name}: ${JSON.stringify(logData)}`;
229-
logger[eventConfig.logLevel](logMessage);
230243
});
231244
});
232245
}

src/Options/docs.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Options/index.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,15 @@ export interface FileUploadOptions {
608608
enableForPublic: ?boolean;
609609
}
610610

611+
export type ClientLogEvent = {
612+
/* The MongoDB driver event name to listen for. */
613+
name: string,
614+
/* Optional array of dot-notation paths to extract specific data from the event object. If not provided or empty, the entire event object will be logged. */
615+
keys?: string[],
616+
/* The log level to use for this event. */
617+
logLevel: string,
618+
};
619+
611620
export interface DatabaseOptions {
612621
/* Enables database real-time hooks to update single schema cache. Set to `true` if using multiple Parse Servers instances connected to the same database. Failing to do so will cause a schema change to not propagate to all instances and re-syncing will only happen when the instances restart. To use this feature with MongoDB, a replica set cluster with [change stream](https://docs.mongodb.com/manual/changeStreams/#availability) support is required.
613622
:DEFAULT: false */
@@ -726,7 +735,7 @@ export interface DatabaseOptions {
726735
/* Set to `true` to disable validation of index fields. When disabled, indexes can be created even if the fields do not exist in the schema. This can be useful when creating indexes on fields that will be added later. */
727736
disableIndexFieldValidation: ?boolean;
728737
/* An array of MongoDB client event configurations to enable logging of specific events. Each configuration object should contain:<br><ul><li>`name` (the event name, e.g., 'topologyDescriptionChanged', 'serverDescriptionChanged', 'connectionPoolCleared', 'connectionPoolReady')</li><li>`keys` (optional array of dot-notation paths to extract specific data from the event object; if not provided or empty, the entire event object will be logged)</li><li>`logLevel` (the log level to use for this event: 'error', 'warn', 'info', 'debug', etc.).</li></ul> */
729-
clientLogEvents: ?(any[]);
738+
clientLogEvents: ?(ClientLogEvent[]);
730739
}
731740

732741
export interface AuthAdapter {

0 commit comments

Comments
 (0)