Skip to content

Commit

Permalink
feat: Add option to change the log level of the logs emitted by trigg…
Browse files Browse the repository at this point in the history
  • Loading branch information
alljinx committed Dec 7, 2022
1 parent 0a8670d commit 8f3b694
Show file tree
Hide file tree
Showing 10 changed files with 286 additions and 137 deletions.
238 changes: 129 additions & 109 deletions resources/buildConfigDefinitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,31 +24,33 @@ const nestedOptionTypes = [
'PasswordPolicyOptions',
'SecurityOptions',
'SchemaOptions',
'LogLevels',
];

/** The prefix of environment variables for nested options. */
const nestedOptionEnvPrefix = {
'AccountLockoutOptions': 'PARSE_SERVER_ACCOUNT_LOCKOUT_',
'CustomPagesOptions': 'PARSE_SERVER_CUSTOM_PAGES_',
'DatabaseOptions': 'PARSE_SERVER_DATABASE_',
'FileUploadOptions': 'PARSE_SERVER_FILE_UPLOAD_',
'IdempotencyOptions': 'PARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_',
'LiveQueryOptions': 'PARSE_SERVER_LIVEQUERY_',
'LiveQueryServerOptions': 'PARSE_LIVE_QUERY_SERVER_',
'PagesCustomUrlsOptions': 'PARSE_SERVER_PAGES_CUSTOM_URL_',
'PagesOptions': 'PARSE_SERVER_PAGES_',
'PagesRoute': 'PARSE_SERVER_PAGES_ROUTE_',
'ParseServerOptions': 'PARSE_SERVER_',
'PasswordPolicyOptions': 'PARSE_SERVER_PASSWORD_POLICY_',
'SecurityOptions': 'PARSE_SERVER_SECURITY_',
'SchemaOptions': 'PARSE_SERVER_SCHEMA_',
AccountLockoutOptions: 'PARSE_SERVER_ACCOUNT_LOCKOUT_',
CustomPagesOptions: 'PARSE_SERVER_CUSTOM_PAGES_',
DatabaseOptions: 'PARSE_SERVER_DATABASE_',
FileUploadOptions: 'PARSE_SERVER_FILE_UPLOAD_',
IdempotencyOptions: 'PARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_',
LiveQueryOptions: 'PARSE_SERVER_LIVEQUERY_',
LiveQueryServerOptions: 'PARSE_LIVE_QUERY_SERVER_',
PagesCustomUrlsOptions: 'PARSE_SERVER_PAGES_CUSTOM_URL_',
PagesOptions: 'PARSE_SERVER_PAGES_',
PagesRoute: 'PARSE_SERVER_PAGES_ROUTE_',
ParseServerOptions: 'PARSE_SERVER_',
PasswordPolicyOptions: 'PARSE_SERVER_PASSWORD_POLICY_',
SecurityOptions: 'PARSE_SERVER_SECURITY_',
SchemaOptions: 'PARSE_SERVER_SCHEMA_',
LogLevels: 'PARSE_SERVER_LOG_LEVELS_',
};

function last(array) {
return array[array.length - 1];
}

const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
function toENV(key) {
let str = '';
let previousIsUpper = false;
Expand All @@ -68,13 +70,15 @@ function toENV(key) {
}

function getCommentValue(comment) {
if (!comment) { return }
if (!comment) {
return;
}
return comment.value.trim();
}

function getENVPrefix(iface) {
if (nestedOptionEnvPrefix[iface.id.name]) {
return nestedOptionEnvPrefix[iface.id.name]
return nestedOptionEnvPrefix[iface.id.name];
}
}

Expand All @@ -86,11 +90,11 @@ function processProperty(property, iface) {
if (!firstComment) {
return;
}
const lines = firstComment.split('\n').map((line) => line.trim());
const lines = firstComment.split('\n').map(line => line.trim());
let help = '';
let envLine;
let defaultLine;
lines.forEach((line) => {
lines.forEach(line => {
if (line.indexOf(':ENV:') === 0) {
envLine = line;
} else if (line.indexOf(':DEFAULT:') === 0) {
Expand All @@ -103,7 +107,7 @@ function processProperty(property, iface) {
if (envLine) {
env = envLine.split(' ')[1];
} else {
env = (prefix + toENV(name));
env = prefix + toENV(name);
}
let defaultValue;
if (defaultLine) {
Expand All @@ -123,21 +127,20 @@ function processProperty(property, iface) {
defaultValue,
types: property.value.types,
typeAnnotation: property.value.typeAnnotation,
required: isRequired
required: isRequired,
};
}


function doInterface(iface) {
return iface.body.properties
.sort((a, b) => a.key.name.localeCompare(b.key.name))
.map((prop) => processProperty(prop, iface))
.filter((e) => e !== undefined);
.map(prop => processProperty(prop, iface))
.filter(e => e !== undefined);
}

function mapperFor(elt, t) {
const p = t.identifier('parsers');
const wrap = (identifier) => t.memberExpression(p, identifier);
const wrap = identifier => t.memberExpression(p, identifier);

if (t.isNumberTypeAnnotation(elt)) {
return t.callExpression(wrap(t.identifier('numberParser')), [t.stringLiteral(elt.name)]);
Expand Down Expand Up @@ -171,27 +174,29 @@ function parseDefaultValue(elt, value, t) {
literalValue = t.numericLiteral(parsers.numberOrBoolParser('')(value));
} else if (t.isArrayTypeAnnotation(elt)) {
const array = parsers.objectParser(value);
literalValue = t.arrayExpression(array.map((value) => {
if (typeof value == 'string') {
return t.stringLiteral(value);
} else if (typeof value == 'number') {
return t.numericLiteral(value);
} else if (typeof value == 'object') {
const object = parsers.objectParser(value);
const props = Object.entries(object).map(([k, v]) => {
if (typeof v == 'string') {
return t.objectProperty(t.identifier(k), t.stringLiteral(v));
} else if (typeof v == 'number') {
return t.objectProperty(t.identifier(k), t.numericLiteral(v));
} else if (typeof v == 'boolean') {
return t.objectProperty(t.identifier(k), t.booleanLiteral(v));
}
});
return t.objectExpression(props);
} else {
throw new Error('Unable to parse array');
}
}));
literalValue = t.arrayExpression(
array.map(value => {
if (typeof value == 'string') {
return t.stringLiteral(value);
} else if (typeof value == 'number') {
return t.numericLiteral(value);
} else if (typeof value == 'object') {
const object = parsers.objectParser(value);
const props = Object.entries(object).map(([k, v]) => {
if (typeof v == 'string') {
return t.objectProperty(t.identifier(k), t.stringLiteral(v));
} else if (typeof v == 'number') {
return t.objectProperty(t.identifier(k), t.numericLiteral(v));
} else if (typeof v == 'boolean') {
return t.objectProperty(t.identifier(k), t.booleanLiteral(v));
}
});
return t.objectExpression(props);
} else {
throw new Error('Unable to parse array');
}
})
);
} else if (t.isAnyTypeAnnotation(elt)) {
literalValue = t.arrayExpression([]);
} else if (t.isBooleanTypeAnnotation(elt)) {
Expand All @@ -204,15 +209,16 @@ function parseDefaultValue(elt, value, t) {

if (nestedOptionTypes.includes(type)) {
const object = parsers.objectParser(value);
const props = Object.keys(object).map((key) => {
const props = Object.keys(object).map(key => {
return t.objectProperty(key, object[value]);
});
literalValue = t.objectExpression(props);
}
if (type == 'ProtectedFields') {
const prop = t.objectProperty(
t.stringLiteral('_User'), t.objectPattern([
t.objectProperty(t.stringLiteral('*'), t.arrayExpression([t.stringLiteral('email')]))
t.stringLiteral('_User'),
t.objectPattern([
t.objectProperty(t.stringLiteral('*'), t.arrayExpression([t.stringLiteral('email')])),
])
);
literalValue = t.objectExpression([prop]);
Expand All @@ -223,62 +229,69 @@ function parseDefaultValue(elt, value, t) {

function inject(t, list) {
let comments = '';
const results = list.map((elt) => {
if (!elt.name) {
return;
}
const props = ['env', 'help'].map((key) => {
if (elt[key]) {
return t.objectProperty(t.stringLiteral(key), t.stringLiteral(elt[key]));
const results = list
.map(elt => {
if (!elt.name) {
return;
}
}).filter((e) => e !== undefined);
if (elt.required) {
props.push(t.objectProperty(t.stringLiteral('required'), t.booleanLiteral(true)))
}
const action = mapperFor(elt, t);
if (action) {
props.push(t.objectProperty(t.stringLiteral('action'), action))
}
if (elt.defaultValue) {
const parsedValue = parseDefaultValue(elt, elt.defaultValue, t);
if (parsedValue) {
props.push(t.objectProperty(t.stringLiteral('default'), parsedValue));
} else {
throw new Error(`Unable to parse value for ${elt.name} `);
const props = ['env', 'help']
.map(key => {
if (elt[key]) {
return t.objectProperty(t.stringLiteral(key), t.stringLiteral(elt[key]));
}
})
.filter(e => e !== undefined);
if (elt.required) {
props.push(t.objectProperty(t.stringLiteral('required'), t.booleanLiteral(true)));
}
}
let type = elt.type.replace('TypeAnnotation', '');
if (type === 'Generic') {
type = elt.typeAnnotation.id.name;
}
if (type === 'Array') {
type = elt.typeAnnotation.elementType.id
? `${elt.typeAnnotation.elementType.id.name}[]`
: `${elt.typeAnnotation.elementType.type.replace('TypeAnnotation', '')}[]`;
}
if (type === 'NumberOrBoolean') {
type = 'Number|Boolean';
}
if (type === 'NumberOrString') {
type = 'Number|String';
}
if (type === 'Adapter') {
const adapterType = elt.typeAnnotation.typeParameters.params[0].id.name;
type = `Adapter<${adapterType}>`;
}
comments += ` * @property {${type}} ${elt.name} ${elt.help}\n`;
const obj = t.objectExpression(props);
return t.objectProperty(t.stringLiteral(elt.name), obj);
}).filter((elt) => {
return elt != undefined;
});
const action = mapperFor(elt, t);
if (action) {
props.push(t.objectProperty(t.stringLiteral('action'), action));
}
if (elt.defaultValue) {
const parsedValue = parseDefaultValue(elt, elt.defaultValue, t);
if (parsedValue) {
props.push(t.objectProperty(t.stringLiteral('default'), parsedValue));
} else {
throw new Error(`Unable to parse value for ${elt.name} `);
}
}
let type = elt.type.replace('TypeAnnotation', '');
if (type === 'Generic') {
type = elt.typeAnnotation.id.name;
}
if (type === 'Array') {
type = elt.typeAnnotation.elementType.id
? `${elt.typeAnnotation.elementType.id.name}[]`
: `${elt.typeAnnotation.elementType.type.replace('TypeAnnotation', '')}[]`;
}
if (type === 'NumberOrBoolean') {
type = 'Number|Boolean';
}
if (type === 'NumberOrString') {
type = 'Number|String';
}
if (type === 'Adapter') {
const adapterType = elt.typeAnnotation.typeParameters.params[0].id.name;
type = `Adapter<${adapterType}>`;
}
comments += ` * @property {${type}} ${elt.name} ${elt.help}\n`;
const obj = t.objectExpression(props);
return t.objectProperty(t.stringLiteral(elt.name), obj);
})
.filter(elt => {
return elt != undefined;
});
return { results, comments };
}

const makeRequire = function (variableName, module, t) {
const decl = t.variableDeclarator(t.identifier(variableName), t.callExpression(t.identifier('require'), [t.stringLiteral(module)]));
return t.variableDeclaration('var', [decl])
}
const decl = t.variableDeclarator(
t.identifier(variableName),
t.callExpression(t.identifier('require'), [t.stringLiteral(module)])
);
return t.variableDeclaration('var', [decl]);
};
let docs = ``;
const plugin = function (babel) {
const t = babel.types;
Expand All @@ -294,27 +307,34 @@ const plugin = function (babel) {
},
ExportDeclaration: function (path) {
// Export declaration on an interface
if (path.node && path.node.declaration && path.node.declaration.type == 'InterfaceDeclaration') {
if (
path.node &&
path.node.declaration &&
path.node.declaration.type == 'InterfaceDeclaration'
) {
const { results, comments } = inject(t, doInterface(path.node.declaration));
const id = path.node.declaration.id.name;
const exports = t.memberExpression(moduleExports, t.identifier(id));
docs += `/**\n * @interface ${id}\n${comments} */\n\n`;
path.replaceWith(
t.assignmentExpression('=', exports, t.objectExpression(results))
)
path.replaceWith(t.assignmentExpression('=', exports, t.objectExpression(results)));
}
}
}
}
},
},
};
};

const auxiliaryCommentBefore = `
**** GENERATED CODE ****
This code has been generated by resources/buildConfigDefinitions.js
Do not edit manually, but update Options/index.js
`
`;

const babel = require("@babel/core");
const res = babel.transformFileSync('./src/Options/index.js', { plugins: [plugin, '@babel/transform-flow-strip-types'], babelrc: false, auxiliaryCommentBefore, sourceMaps: false });
const babel = require('@babel/core');
const res = babel.transformFileSync('./src/Options/index.js', {
plugins: [plugin, '@babel/transform-flow-strip-types'],
babelrc: false,
auxiliaryCommentBefore,
sourceMaps: false,
});
require('fs').writeFileSync('./src/Options/Definitions.js', res.code + '\n');
require('fs').writeFileSync('./src/Options/docs.js', docs);
35 changes: 35 additions & 0 deletions spec/CloudCodeLogger.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,41 @@ describe('Cloud Code Logger', () => {
});
});

it('should log cloud function triggers using the custom log level', async () => {
Parse.Cloud.beforeSave('TestClass', () => {});
Parse.Cloud.afterSave('TestClass', () => {});

const execTest = async (logLevel, triggerBeforeSuccess, triggerAfter) => {
await reconfigureServer({
silent: true,
logLevel,
logLevels: {
triggerAfter,
triggerBeforeSuccess,
},
});

spy = spyOn(Config.get('test').loggerController.adapter, 'log').and.callThrough();
const obj = new Parse.Object('TestClass');
await obj.save();

return {
beforeSave: spy.calls
.allArgs()
.find(log => log[1].startsWith('beforeSave triggered for TestClass for user '))?.[0],
afterSave: spy.calls
.allArgs()
.find(log => log[1].startsWith('afterSave triggered for TestClass for user '))?.[0],
};
};

let calls = await execTest('silly', 'silly', 'debug');
expect(calls).toEqual({ beforeSave: 'silly', afterSave: 'debug' });

calls = await execTest('info', 'warn', 'debug');
expect(calls).toEqual({ beforeSave: 'warn', afterSave: undefined });
});

it('should log cloud function failure', done => {
Parse.Cloud.define('aFunction', () => {
throw 'it failed!';
Expand Down

0 comments on commit 8f3b694

Please sign in to comment.