diff --git a/spec/CloudCodeLogger.spec.js b/spec/CloudCodeLogger.spec.js new file mode 100644 index 0000000000..23fc967e0c --- /dev/null +++ b/spec/CloudCodeLogger.spec.js @@ -0,0 +1,61 @@ +'use strict'; +var LoggerController = require('../src/Controllers/LoggerController').LoggerController; +var FileLoggerAdapter = require('../src/Adapters/Logger/FileLoggerAdapter').FileLoggerAdapter; + +describe("Cloud Code Logger", () => { + + it("should expose log to functions", (done) => { + var logController = new LoggerController(new FileLoggerAdapter()); + + Parse.Cloud.define("loggerTest", (req, res) => { + req.log.info('logTest', 'info log', {info: 'some log' }); + req.log.error('logTest','error log', {error: 'there was an error'}); + res.success({}); + }); + + Parse.Cloud.run('loggerTest').then(() => { + Parse.Cloud._removeHook('Functions', 'logTest'); + return logController.getLogs({from: Date.now() - 500, size: 1000}); + }).then((res) => { + expect(res.length).not.toBe(0); + let lastLogs = res.slice(0, 2); + let errorMessage = lastLogs[0]; + let infoMessage = lastLogs[1]; + expect(errorMessage.level).toBe('error'); + expect(errorMessage.error).toBe('there was an error'); + expect(errorMessage.message).toBe('logTest error log'); + expect(infoMessage.level).toBe('info'); + expect(infoMessage.info).toBe('some log'); + expect(infoMessage.message).toBe('logTest info log'); + done(); + }); + }); + + it("should expose log to trigger", (done) => { + var logController = new LoggerController(new FileLoggerAdapter()); + + Parse.Cloud.beforeSave("MyObject", (req, res) => { + req.log.info('beforeSave MyObject', 'info log', {info: 'some log' }); + req.log.error('beforeSave MyObject','error log', {error: 'there was an error'}); + res.success({}); + }); + + let obj = new Parse.Object('MyObject'); + obj.save().then(() => { + Parse.Cloud._removeHook('Triggers', 'beforeSave', 'MyObject'); + return logController.getLogs({from: Date.now() - 500, size: 1000}) + }).then((res) => { + expect(res.length).not.toBe(0); + let lastLogs = res.slice(0, 2); + let errorMessage = lastLogs[0]; + let infoMessage = lastLogs[1]; + expect(errorMessage.level).toBe('error'); + expect(errorMessage.error).toBe('there was an error'); + expect(errorMessage.message).toBe('beforeSave MyObject error log'); + expect(infoMessage.level).toBe('info'); + expect(infoMessage.info).toBe('some log'); + expect(infoMessage.message).toBe('beforeSave MyObject info log'); + done(); + }); + }); +}); diff --git a/spec/FileLoggerAdapter.spec.js b/spec/FileLoggerAdapter.spec.js index 54e661bde9..fb4d6d5572 100644 --- a/spec/FileLoggerAdapter.spec.js +++ b/spec/FileLoggerAdapter.spec.js @@ -7,7 +7,8 @@ describe('info logs', () => { var fileLoggerAdapter = new FileLoggerAdapter(); fileLoggerAdapter.info('testing info logs', () => { fileLoggerAdapter.query({ - size: 1, + from: new Date(Date.now() - 500), + size: 100, level: 'info' }, (results) => { if(results.length == 0) { @@ -28,7 +29,8 @@ describe('error logs', () => { var fileLoggerAdapter = new FileLoggerAdapter(); fileLoggerAdapter.error('testing error logs', () => { fileLoggerAdapter.query({ - size: 1, + from: new Date(Date.now() - 500), + size: 100, level: 'error' }, (results) => { if(results.length == 0) { diff --git a/src/RestWrite.js b/src/RestWrite.js index 0b88b71a8f..5250fc2fdf 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -160,7 +160,7 @@ RestWrite.prototype.runBeforeTrigger = function() { updatedObject.set(this.sanitizedData()); return Promise.resolve().then(() => { - return triggers.maybeRunTrigger(triggers.Types.beforeSave, this.auth, updatedObject, originalObject, this.config.applicationId); + return triggers.maybeRunTrigger(triggers.Types.beforeSave, this.auth, updatedObject, originalObject, this.config); }).then((response) => { if (response && response.object) { this.data = response.object; @@ -821,7 +821,7 @@ RestWrite.prototype.runAfterTrigger = function() { this.config.liveQueryController.onAfterSave(updatedObject.className, updatedObject, originalObject); // Run afterSave trigger - triggers.maybeRunTrigger(triggers.Types.afterSave, this.auth, updatedObject, originalObject, this.config.applicationId); + triggers.maybeRunTrigger(triggers.Types.afterSave, this.auth, updatedObject, originalObject, this.config); }; // A helper to figure out what location this operation happens at. diff --git a/src/Routers/FunctionsRouter.js b/src/Routers/FunctionsRouter.js index 0902b871da..b405b90eb5 100644 --- a/src/Routers/FunctionsRouter.js +++ b/src/Routers/FunctionsRouter.js @@ -7,11 +7,11 @@ var express = require('express'), import PromiseRouter from '../PromiseRouter'; export class FunctionsRouter extends PromiseRouter { - + mountRoutes() { this.route('POST', '/functions/:functionName', FunctionsRouter.handleCloudFunction); } - + static createResponseObject(resolve, reject) { return { success: function(result) { @@ -26,19 +26,19 @@ export class FunctionsRouter extends PromiseRouter { } } } - + static handleCloudFunction(req) { var applicationId = req.config.applicationId; var theFunction = triggers.getFunction(req.params.functionName, applicationId); var theValidator = triggers.getValidator(req.params.functionName, applicationId); if (theFunction) { - const params = Object.assign({}, req.body, req.query); var request = { params: params, master: req.auth && req.auth.isMaster, user: req.auth && req.auth.user, - installationId: req.info.installationId + installationId: req.info.installationId, + log: req.config.loggerController && req.config.loggerController.adapter }; if (theValidator && typeof theValidator === "function") { diff --git a/src/rest.js b/src/rest.js index f776fe09de..f372cdfb6b 100644 --- a/src/rest.js +++ b/src/rest.js @@ -52,7 +52,7 @@ function del(config, auth, className, objectId) { inflatedObject = Parse.Object.fromJSON(response.results[0]); // Notify LiveQuery server if possible config.liveQueryController.onAfterDelete(inflatedObject.className, inflatedObject); - return triggers.maybeRunTrigger(triggers.Types.beforeDelete, auth, inflatedObject, null, config.applicationId); + return triggers.maybeRunTrigger(triggers.Types.beforeDelete, auth, inflatedObject, null, config); } throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found for delete.'); @@ -79,7 +79,7 @@ function del(config, auth, className, objectId) { objectId: objectId }, options); }).then(() => { - triggers.maybeRunTrigger(triggers.Types.afterDelete, auth, inflatedObject, null, config.applicationId); + triggers.maybeRunTrigger(triggers.Types.afterDelete, auth, inflatedObject, null, config); return Promise.resolve(); }); } diff --git a/src/triggers.js b/src/triggers.js index 8622df87a5..7ab1ea1902 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -16,7 +16,7 @@ const baseStore = function() { base[key] = {}; return base; }, {}); - + return Object.freeze({ Functions, Validators, @@ -63,7 +63,7 @@ export function getTrigger(className, triggerType, applicationId) { throw "Missing ApplicationID"; } var manager = _triggerStore[applicationId] - if (manager + if (manager && manager.Triggers && manager.Triggers[triggerType] && manager.Triggers[triggerType][className]) { @@ -92,15 +92,18 @@ export function getValidator(functionName, applicationId) { return undefined; } -export function getRequestObject(triggerType, auth, parseObject, originalParseObject) { +export function getRequestObject(triggerType, auth, parseObject, originalParseObject, config) { var request = { triggerName: triggerType, object: parseObject, - master: false + master: false, + log: config.loggerController && config.loggerController.adapter }; + if (originalParseObject) { request.original = originalParseObject; } + if (!auth) { return request; } @@ -145,19 +148,19 @@ export function getResponseObject(request, resolve, reject) { // Resolves to an object, empty or containing an object key. A beforeSave // trigger will set the object key to the rest format object to save. // originalParseObject is optional, we only need that for befote/afterSave functions -export function maybeRunTrigger(triggerType, auth, parseObject, originalParseObject, applicationId) { +export function maybeRunTrigger(triggerType, auth, parseObject, originalParseObject, config) { if (!parseObject) { return Promise.resolve({}); } return new Promise(function (resolve, reject) { - var trigger = getTrigger(parseObject.className, triggerType, applicationId); + var trigger = getTrigger(parseObject.className, triggerType, config.applicationId); if (!trigger) return resolve(); - var request = getRequestObject(triggerType, auth, parseObject, originalParseObject); + var request = getRequestObject(triggerType, auth, parseObject, originalParseObject, config); var response = getResponseObject(request, resolve, reject); // Force the current Parse app before the trigger - Parse.applicationId = applicationId; - Parse.javascriptKey = cache.apps.get(applicationId).javascriptKey || ''; - Parse.masterKey = cache.apps.get(applicationId).masterKey; + Parse.applicationId = config.applicationId; + Parse.javascriptKey = config.javascriptKey || ''; + Parse.masterKey = config.masterKey; trigger(request, response); }); };