Skip to content

feat: improved LiveQuery error logging with additional information #7837

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Mar 23, 2022
Merged
7 changes: 3 additions & 4 deletions spec/ParseHooks.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ describe('Hooks', () => {

it('should run the function on the test server (error handling)', done => {
app.post('/SomeFunctionError', function (req, res) {
res.json({ error: { code: 1337, error: 'hacking that one!' } });
res.json({ error: { code: 1337, message: 'hacking that one!' } });
});
// The function is deleted as the DB is dropped between calls
Parse.Hooks.createFunction('SOME_TEST_FUNCTION', hookServerURL + '/SomeFunctionError')
Expand All @@ -464,9 +464,8 @@ describe('Hooks', () => {
expect(err).not.toBe(undefined);
expect(err).not.toBe(null);
if (err) {
expect(err.code).toBe(Parse.Error.SCRIPT_FAILED);
expect(err.message.code).toEqual(1337);
expect(err.message.error).toEqual('hacking that one!');
expect(err.code).toBe(1337);
expect(err.message).toEqual('hacking that one!');
}
done();
}
Expand Down
90 changes: 90 additions & 0 deletions spec/ParseLiveQuery.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,41 @@ describe('ParseLiveQuery', function () {
await object.save();
});

it('can log on afterLiveQueryEvent throw', async () => {
await reconfigureServer({
liveQuery: {
classNames: ['TestObject'],
},
startLiveQueryServer: true,
verbose: false,
silent: true,
});

const object = new TestObject();
await object.save();

const logger = require('../lib/logger').logger;
spyOn(logger, 'error').and.callFake(() => {});

let session = undefined;
Parse.Cloud.afterLiveQueryEvent('TestObject', ({ sessionToken }) => {
session = sessionToken;
/* eslint-disable no-undef */
foo.bar();
/* eslint-enable no-undef */
});

const query = new Parse.Query(TestObject);
query.equalTo('objectId', object.id);
const subscription = await query.subscribe();
object.set({ foo: 'bar' });
await object.save();
await new Promise(resolve => subscription.on('error', resolve));
expect(logger.error).toHaveBeenCalledWith(
`Failed running afterLiveQueryEvent on class TestObject for event update with session ${session} with:\n Error: {"message":"foo is not defined","code":141}`
);
});

it('can handle afterEvent sendEvent to false', async done => {
await reconfigureServer({
liveQuery: {
Expand Down Expand Up @@ -566,6 +601,33 @@ describe('ParseLiveQuery', function () {
await query.subscribe();
});

it('can log on beforeConnect throw', async () => {
await reconfigureServer({
liveQuery: {
classNames: ['TestObject'],
},
startLiveQueryServer: true,
verbose: false,
silent: true,
});

const logger = require('../lib/logger').logger;
spyOn(logger, 'error').and.callFake(() => {});
let token = undefined;
Parse.Cloud.beforeConnect(({ sessionToken }) => {
token = sessionToken;
/* eslint-disable no-undef */
foo.bar();
/* eslint-enable no-undef */
});
new Parse.Query(TestObject).subscribe();
await new Promise(resolve => Parse.LiveQuery.on('error', resolve));
Parse.LiveQuery.removeAllListeners('error');
expect(logger.error).toHaveBeenCalledWith(
`Failed running beforeConnect for session ${token} with:\n Error: {"message":"foo is not defined","code":141}`
);
});

it('can handle beforeSubscribe error', async done => {
await reconfigureServer({
liveQuery: {
Expand Down Expand Up @@ -594,6 +656,34 @@ describe('ParseLiveQuery', function () {
});
});

it('can log on beforeSubscribe error', async () => {
await reconfigureServer({
liveQuery: {
classNames: ['TestObject'],
},
startLiveQueryServer: true,
verbose: false,
silent: true,
});

const logger = require('../lib/logger').logger;
spyOn(logger, 'error').and.callFake(() => {});

Parse.Cloud.beforeSubscribe(TestObject, () => {
/* eslint-disable no-undef */
foo.bar();
/* eslint-enable no-undef */
});

const query = new Parse.Query(TestObject);
const subscription = await query.subscribe();
await new Promise(resolve => subscription.on('error', resolve));

expect(logger.error).toHaveBeenCalledWith(
`Failed running beforeSubscribe on TestObject for session undefined with:\n Error: {"message":"foo is not defined","code":141}`
);
});

it('can handle mutate beforeSubscribe query', async done => {
await reconfigureServer({
liveQuery: {
Expand Down
45 changes: 13 additions & 32 deletions src/LiveQuery/ParseLiveQueryServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ParsePubSub } from './ParsePubSub';
import SchemaController from '../Controllers/SchemaController';
import _ from 'lodash';
import { v4 as uuidv4 } from 'uuid';
import { runLiveQueryEventHandlers, getTrigger, runTrigger, toJSONwithObjects } from '../triggers';
import { runLiveQueryEventHandlers, getTrigger, runTrigger, resolveError, toJSONwithObjects } from '../triggers';
import { getAuthForSessionToken, Auth } from '../Auth';
import { getCacheController } from '../Controllers';
import LRU from 'lru-cache';
Expand Down Expand Up @@ -194,14 +194,9 @@ class ParseLiveQueryServer {
delete deletedParseObject.authData;
}
client.pushDelete(requestId, deletedParseObject);
} catch (error) {
Client.pushError(
client.parseWebSocket,
error.code || Parse.Error.SCRIPT_FAILED,
error.message || error,
false,
requestId
);
} catch (e) {
const error = resolveError(e);
Client.pushError(client.parseWebSocket, error.code, error.message, false, requestId);
logger.error(
`Failed running afterLiveQueryEvent on class ${className} for event ${res.event} with session ${res.sessionToken} with:\n Error: ` +
JSON.stringify(error)
Expand Down Expand Up @@ -358,14 +353,9 @@ class ParseLiveQueryServer {
if (client[functionName]) {
client[functionName](requestId, currentParseObject, originalParseObject);
}
} catch (error) {
Client.pushError(
client.parseWebSocket,
error.code || Parse.Error.SCRIPT_FAILED,
error.message || error,
false,
requestId
);
} catch (e) {
const error = resolveError(e);
Client.pushError(client.parseWebSocket, error.code, error.message, false, requestId);
logger.error(
`Failed running afterLiveQueryEvent on class ${className} for event ${res.event} with session ${res.sessionToken} with:\n Error: ` +
JSON.stringify(error)
Expand Down Expand Up @@ -681,13 +671,9 @@ class ParseLiveQueryServer {
logger.info(`Create new client: ${parseWebsocket.clientId}`);
client.pushConnect();
runLiveQueryEventHandlers(req);
} catch (error) {
Client.pushError(
parseWebsocket,
error.code || Parse.Error.SCRIPT_FAILED,
error.message || error,
false
);
} catch (e) {
const error = resolveError(e);
Client.pushError(parseWebsocket, error.code, error.message, false);
logger.error(
`Failed running beforeConnect for session ${request.sessionToken} with:\n Error: ` +
JSON.stringify(error)
Expand Down Expand Up @@ -827,16 +813,11 @@ class ParseLiveQueryServer {
installationId: client.installationId,
});
} catch (e) {
Client.pushError(
parseWebsocket,
e.code || Parse.Error.SCRIPT_FAILED,
e.message || e,
false,
request.requestId
);
const error = resolveError(e);
Client.pushError(parseWebsocket, error.code, error.message, false, request.requestId);
logger.error(
`Failed running beforeSubscribe on ${className} for session ${request.sessionToken} with:\n Error: ` +
JSON.stringify(e)
JSON.stringify(error)
);
}
}
Expand Down
31 changes: 14 additions & 17 deletions src/triggers.js
Original file line number Diff line number Diff line change
Expand Up @@ -599,28 +599,25 @@ export function maybeRunQueryTrigger(
);
}

export function resolveError(message, defaultOpts) {
if (!defaultOpts) {
defaultOpts = {};
}
if (!message) {
return new Parse.Error(
defaultOpts.code || Parse.Error.SCRIPT_FAILED,
defaultOpts.message || 'Script failed.'
);
export function resolveError(e, defaultOpts = {}) {
const { code = Parse.Error.SCRIPT_FAILED, message = 'Script failed.' } = defaultOpts;
if (!e) {
return new Parse.Error(code, message);
}
if (message instanceof Parse.Error) {
return message;
if (e instanceof Parse.Error) {
return e;
}

const code = defaultOpts.code || Parse.Error.SCRIPT_FAILED;
// If it's an error, mark it as a script failed
if (typeof message === 'string') {
return new Parse.Error(code, message);
if (typeof e === 'string') {
return new Parse.Error(code, e);
}
const error = new Parse.Error(code, e.message || message);
if (e instanceof Error) {
error.stack = e.stack;
}
const error = new Parse.Error(code, message.message || message);
if (message instanceof Error) {
error.stack = message.stack;
if (Number.isInteger(e.code)) {
error.code = e.code;
}
return error;
}
Expand Down