Skip to content

Commit 3079270

Browse files
authored
Optimizations (#4135)
* removes costly json serialization to InMemoryCacheAdapter * Always cache a copy of the array * Use own mapValues * Makes sure we dont make unnecessary calls to the logger * Do not bypass loggers with silent logging (only applies to stdout) * warn is not warning * use === * Wrap logRequest / logResponse in the loggerController for more granular control Also give the ability to pass functions to the logger so we don't serialize too early in JSON (costly) * reconfiguring winston would override the transports levels and make subsequent tests fail
1 parent 17f4dcd commit 3079270

File tree

8 files changed

+118
-33
lines changed

8 files changed

+118
-33
lines changed

spec/LoggerController.spec.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,28 @@ describe('LoggerController', () => {
8888
}).toThrow();
8989
done();
9090
});
91+
92+
it('should replace implementations with verbose', (done) => {
93+
const adapter = new WinstonLoggerAdapter();
94+
const logger = new LoggerController(adapter, null, {verbose: true });
95+
spyOn(adapter, "log");
96+
logger.silly('yo!');
97+
expect(adapter.log).not.toHaveBeenCalled();
98+
done();
99+
});
100+
101+
it('should replace implementations with logLevel', (done) => {
102+
const adapter = new WinstonLoggerAdapter();
103+
const logger = new LoggerController(adapter, null, { logLevel: 'error' });
104+
spyOn(adapter, "log");
105+
logger.warn('yo!');
106+
logger.info('yo!');
107+
logger.debug('yo!');
108+
logger.verbose('yo!');
109+
logger.silly('yo!');
110+
expect(adapter.log).not.toHaveBeenCalled();
111+
logger.error('error');
112+
expect(adapter.log).toHaveBeenCalled();
113+
done();
114+
});
91115
});

src/Adapters/Cache/InMemoryCacheAdapter.js

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,15 @@ export class InMemoryCacheAdapter {
77
}
88

99
get(key) {
10-
return new Promise((resolve) => {
11-
const record = this.cache.get(key);
12-
if (record == null) {
13-
return resolve(null);
14-
}
15-
16-
return resolve(JSON.parse(record));
17-
})
10+
const record = this.cache.get(key);
11+
if (record === null) {
12+
return Promise.resolve(null);
13+
}
14+
return Promise.resolve(record);
1815
}
1916

2017
put(key, value, ttl) {
21-
this.cache.put(key, JSON.stringify(value), ttl);
18+
this.cache.put(key, value, ttl);
2219
return Promise.resolve();
2320
}
2421

src/Adapters/Storage/Mongo/MongoTransform.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ const transformKeyValueForUpdate = (className, restKey, restValue, parseFormatSc
107107
}
108108

109109
// Handle normal objects by recursing
110-
value = _.mapValues(restValue, transformInteriorValue);
110+
value = mapValues(restValue, transformInteriorValue);
111111
return {key, value};
112112
}
113113

@@ -132,7 +132,7 @@ const transformInteriorValue = restValue => {
132132
}
133133

134134
// Handle normal objects by recursing
135-
return _.mapValues(restValue, transformInteriorValue);
135+
return mapValues(restValue, transformInteriorValue);
136136
}
137137

138138
const valueAsDate = value => {
@@ -332,7 +332,7 @@ const parseObjectKeyValueToMongoObjectKeyValue = (restKey, restValue, schema) =>
332332
if (Object.keys(restValue).some(key => key.includes('$') || key.includes('.'))) {
333333
throw new Parse.Error(Parse.Error.INVALID_NESTED_KEY, "Nested keys should not contain the '$' or '.' characters");
334334
}
335-
value = _.mapValues(restValue, transformInteriorValue);
335+
value = mapValues(restValue, transformInteriorValue);
336336
return {key: restKey, value};
337337
}
338338

@@ -789,6 +789,13 @@ function transformUpdateOperator({
789789
throw new Parse.Error(Parse.Error.COMMAND_UNAVAILABLE, `The ${__op} operator is not supported yet.`);
790790
}
791791
}
792+
function mapValues(object, iterator) {
793+
const result = {};
794+
Object.keys(object).forEach((key) => {
795+
result[key] = iterator(object[key]);
796+
});
797+
return result;
798+
}
792799

793800
const nestedMongoObjectToNestedParseObject = mongoObject => {
794801
switch(typeof mongoObject) {
@@ -829,7 +836,7 @@ const nestedMongoObjectToNestedParseObject = mongoObject => {
829836
return mongoObject;
830837
}
831838

832-
return _.mapValues(mongoObject, nestedMongoObjectToNestedParseObject);
839+
return mapValues(mongoObject, nestedMongoObjectToNestedParseObject);
833840
default:
834841
throw 'unknown js type';
835842
}

src/Auth.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ Auth.prototype._loadRoles = function() {
135135
this.fetchedRoles = true;
136136
this.rolePromise = null;
137137

138-
cacheAdapter.role.put(this.user.id, this.userRoles);
138+
cacheAdapter.role.put(this.user.id, Array(...this.userRoles));
139139
return Promise.resolve(this.userRoles);
140140
}
141141
var rolesMap = results.reduce((m, r) => {
@@ -152,8 +152,7 @@ Auth.prototype._loadRoles = function() {
152152
});
153153
this.fetchedRoles = true;
154154
this.rolePromise = null;
155-
156-
cacheAdapter.role.put(this.user.id, this.userRoles);
155+
cacheAdapter.role.put(this.user.id, Array(...this.userRoles));
157156
return Promise.resolve(this.userRoles);
158157
});
159158
});

src/Controllers/LoggerController.js

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,34 @@ export const LogOrder = {
1717
ASCENDING: 'asc'
1818
}
1919

20+
const logLevels = [
21+
'error',
22+
'warn',
23+
'info',
24+
'debug',
25+
'verbose',
26+
'silly',
27+
]
28+
2029
export class LoggerController extends AdaptableController {
2130

31+
constructor(adapter, appId, options = {logLevel: 'info'}) {
32+
super(adapter, appId, options);
33+
let level = 'info';
34+
if (options.verbose) {
35+
level = 'verbose';
36+
}
37+
if (options.logLevel) {
38+
level = options.logLevel;
39+
}
40+
const index = logLevels.indexOf(level); // info by default
41+
logLevels.forEach((level, levelIndex) => {
42+
if (levelIndex > index) { // silence the levels that are > maxIndex
43+
this[level] = () => {};
44+
}
45+
});
46+
}
47+
2248
maskSensitiveUrl(urlString) {
2349
const password = url.parse(urlString, true).query.password;
2450

@@ -80,7 +106,10 @@ export class LoggerController extends AdaptableController {
80106
log(level, args) {
81107
// make the passed in arguments object an array with the spread operator
82108
args = this.maskSensitive([...args]);
83-
args = [].concat(level, args);
109+
args = [].concat(level, args.map((arg) => {
110+
if (typeof arg === 'function') { return arg(); }
111+
return arg;
112+
}));
84113
this.adapter.log.apply(this.adapter, args);
85114
}
86115

@@ -107,6 +136,36 @@ export class LoggerController extends AdaptableController {
107136
silly() {
108137
return this.log('silly', arguments);
109138
}
139+
140+
logRequest({
141+
method,
142+
url,
143+
headers,
144+
body
145+
}) {
146+
this.verbose(() => {
147+
const stringifiedBody = JSON.stringify(body, null, 2);
148+
return `REQUEST for [${method}] ${url}: ${stringifiedBody}`;
149+
}, {
150+
method,
151+
url,
152+
headers,
153+
body
154+
});
155+
}
156+
157+
logResponse({
158+
method,
159+
url,
160+
result
161+
}) {
162+
this.verbose(
163+
() => { const stringifiedResponse = JSON.stringify(result, null, 2);
164+
return `RESPONSE from [${method}] ${url}: ${stringifiedResponse}`;
165+
},
166+
{result: result}
167+
);
168+
}
110169
// check that date input is valid
111170
static validDateTime(date) {
112171
if (!date) {

src/ParseServer.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,9 @@ class ParseServer {
173173
masterKeyIps
174174
)));
175175

176-
const loggerControllerAdapter = loadAdapter(loggerAdapter, WinstonLoggerAdapter, { jsonLogs, logsFolder, verbose, logLevel, silent });
177-
const loggerController = new LoggerController(loggerControllerAdapter, appId);
176+
const loggerOptions = { jsonLogs, logsFolder, verbose, logLevel, silent };
177+
const loggerControllerAdapter = loadAdapter(loggerAdapter, WinstonLoggerAdapter, loggerOptions);
178+
const loggerController = new LoggerController(loggerControllerAdapter, appId, loggerOptions);
178179
logging.setLogger(loggerController);
179180

180181
const filesControllerAdapter = loadAdapter(filesAdapter, () => {

src/PromiseRouter.js

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -142,24 +142,21 @@ function makeExpressHandler(appId, promiseHandler) {
142142
try {
143143
const url = maskSensitiveUrl(req);
144144
const body = Object.assign({}, req.body);
145-
const stringifiedBody = JSON.stringify(body, null, 2);
146-
log.verbose(`REQUEST for [${req.method}] ${url}: ${stringifiedBody}`, {
147-
method: req.method,
148-
url: url,
149-
headers: req.headers,
150-
body: body
145+
const method = req.method;
146+
const headers = req.headers;
147+
log.logRequest({
148+
method,
149+
url,
150+
headers,
151+
body
151152
});
152153
promiseHandler(req).then((result) => {
153154
if (!result.response && !result.location && !result.text) {
154155
log.error('the handler did not include a "response" or a "location" field');
155156
throw 'control should not get here';
156157
}
157158

158-
const stringifiedResponse = JSON.stringify(result, null, 2);
159-
log.verbose(
160-
`RESPONSE from [${req.method}] ${url}: ${stringifiedResponse}`,
161-
{result: result}
162-
);
159+
log.logResponse({ method, url, result });
163160

164161
var status = result.status || 200;
165162
res.status(status);

src/logger.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ import { WinstonLoggerAdapter } from './Adapters/Logger/WinstonLoggerAdapter';
44
import { LoggerController } from './Controllers/LoggerController';
55

66
function defaultLogger() {
7-
const adapter = new WinstonLoggerAdapter({
7+
const options = {
88
logsFolder: defaults.logsFolder,
99
jsonLogs: defaults.jsonLogs,
1010
verbose: defaults.verbose,
11-
silent: defaults.silent });
12-
return new LoggerController(adapter);
11+
silent: defaults.silent };
12+
const adapter = new WinstonLoggerAdapter(options);
13+
return new LoggerController(adapter, null, options);
1314
}
1415

1516
let logger = defaultLogger();

0 commit comments

Comments
 (0)