Skip to content
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

feat: Add option to change the log level of the logs emitted by triggers #8328

Merged
merged 27 commits into from
Dec 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a391ebd
feat: add option to change the log level of the logs emitted by triggers
alljinx Nov 21, 2022
b0102fd
feat: add option to change the log level of the logs emitted by triggers
alljinx Nov 22, 2022
c792e51
Update src/Options/index.js
alljinx Nov 23, 2022
6a4b83b
Multiples fixes suggested by mtrezza, including renaming logLevels op…
alljinx Nov 23, 2022
2c167d3
Refactoring test.
alljinx Nov 23, 2022
30e21eb
Update src/Options/index.js
alljinx Nov 23, 2022
d999740
Update src/Options/index.js
alljinx Nov 23, 2022
3bada12
Update src/Options/index.js
alljinx Nov 23, 2022
1b61a2e
Minor correction on doc and regeneration.
alljinx Nov 23, 2022
6b4b787
prettier && lint-fix
alljinx Nov 23, 2022
dfae832
Update spec/CloudCodeLogger.spec.js
mtrezza Nov 23, 2022
4f466a6
Update spec/CloudCodeLogger.spec.js
mtrezza Nov 23, 2022
d0bed5d
Update spec/CloudCodeLogger.spec.js
mtrezza Nov 23, 2022
5fa9f72
Update src/Config.js
alljinx Nov 24, 2022
856d49f
Revert "Update src/Config.js"
alljinx Nov 24, 2022
41d47ce
Fix undefinedTRIGGER_AFTER in Definitions.js
alljinx Nov 24, 2022
6933894
Remove the 'none' option from LogLevels.
alljinx Nov 24, 2022
c768f8f
Update src/Config.js
alljinx Nov 25, 2022
25c0f61
Update src/Config.js
alljinx Nov 25, 2022
db15883
Fix ctrValidLogLevels -> validLogLevels
alljinx Nov 25, 2022
a6735c0
Merge branch 'alpha' into triggers-logger-config
mtrezza Nov 25, 2022
2f9b9ff
Merge branch 'alpha' into triggers-logger-config
mtrezza Nov 25, 2022
826cc63
Merge branch 'alpha' into triggers-logger-config
mtrezza Nov 26, 2022
477b03e
Fix postgres unit tests
alljinx Nov 26, 2022
a3be352
Merge branch 'alpha' into triggers-logger-config
alljinx Nov 26, 2022
21d2ee5
Merge branch 'alpha' into triggers-logger-config
alljinx Dec 7, 2022
69fdeaf
Merge branch 'alpha' into triggers-logger-config
mtrezza Dec 7, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
alljinx marked this conversation as resolved.
Show resolved Hide resolved
}
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
Loading