I'm trying to setup a sails hook with miniprofiler to help profile mongo usage. I'm struggling for how to wrap the db methods in a function that will execute the profile. I'm trying to do this via a user hook:
setupMiniprofilerMongo(req, res, next) {
const adapter = sails.hooks.orm.datastores.default.adapter;
const adapterPrototype = Object.getPrototypeOf(adapter);
const originalMethod = adapter.adapter.find;
methodPrototype.find = function profiledMongoCommand(connectionName, collectionName, options, cb) {
sails.log.info(`${collectionName}.find`);
return originalMethod.call(adapter, connectionName, collectionName, options, cb);
};
}
That causes the following error to be thrown:
TypeError: Cannot read property 'collections' of undefined
at Object.module.exports.adapter.find (/Users/jgeurts/dev/platform/node_modules/sails-mongo/lib/adapter.js:349:40)
at Object.profiledMongoCommand [as find] (/Users/jgeurts/dev/platform/config/http.js:234:37)
Any help would be appreciated. I tried to wrap the methods on mongodb package, but that doesn't seem to work either. :/
I got this working by wrapping waterline query methods. There is room for improvement, though.
setupMiniprofilerWaterline(req, res, next) {
const dbOperations = [
'count',
'create',
'createEach',
'define',
'describe',
'destroy',
'drop',
'find',
'join',
// 'native',
// 'registerConnection',
'update',
];
const waterlineMethodByModels = {};
const miniprofilerWaterline = () => {
return {
name: 'mongodb',
handler(req, res, next) {
if (!req.miniprofiler || !req.miniprofiler.enabled) {
return next();
}
const profiler = req.miniprofiler;
for (const modelName of _.keys(sails.models)) {
for (const dbOperation of dbOperations) {
const model = sails.models[modelName];
if (!model[dbOperation]) {
continue;
}
if (!waterlineMethodByModels[modelName]) {
waterlineMethodByModels[modelName] = {};
}
// Prevent wrapping a method more than once
if (waterlineMethodByModels[modelName][dbOperation]) {
continue;
}
waterlineMethodByModels[modelName][dbOperation] = true;
const originalMethod = model[dbOperation];
model[dbOperation] = function profiledMongoCommand(...args) {
const query = args && args.length ? args[0] : '';
const lastArg = args && args.length ? args[args.length - 1] : null;
const modelAndMethod = `${modelName}.${dbOperation}`;
if (lastArg && typeof lastArg === 'function') {
sails.log.debug(`mongo::${modelAndMethod} - ${JSON.stringify(query)}`);
const callback = args.pop();
const timing = profiler.startTimeQuery('mongodb', query ? JSON.stringify(query || '') : '');
// In general, the callstack is kind of useless to us for these profiles
// The model/db method is more useful in the miniprofiler UI
timing.callStack = `\n\nMethod: ${modelAndMethod}`;
return originalMethod.call(this, ...args, function profiledResult(...results) {
profiler.stopTimeQuery(timing);
callback(...results);
});
}
const methodResult = originalMethod.call(this, ...args);
const methodResultPrototype = Object.getPrototypeOf(methodResult);
const isDeferred = !!methodResultPrototype.exec;
// If methodResult is a Deferred object type, then the query method will be profiled above when the deferred is executed (with a callback)
// So we only care to log this if the methodResult is not a deferred object
if (!isDeferred) {
sails.log.warn(`Was not able to profile mongo::${modelAndMethod}. Maybe its a promise? query: ${JSON.stringify(query)}`);
}
return methodResult;
};
}
}
next();
},
};
};
miniprofiler.express.for(miniprofilerWaterline())(req, res, next);
},
The code is available as miniprofiler-waterline if you want to contribute/use it in your own projects