@jsreport/jsreport-core 3.6.1 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/index.js +1 -1
- package/lib/main/createDefaultLoggerFormat.js +6 -1
- package/lib/main/createNormalizeMetaLoggerFormat.js +19 -0
- package/lib/main/folders/moveBetweenFolders.js +244 -215
- package/lib/main/logger.js +25 -17
- package/lib/main/profiler.js +230 -86
- package/lib/main/reporter.js +8 -1
- package/lib/main/validateEntityName.js +6 -0
- package/lib/worker/render/executeEngine.js +12 -2
- package/lib/worker/render/profiler.js +8 -2
- package/lib/worker/render/render.js +1 -1
- package/lib/worker/sandbox/runInSandbox.js +3 -1
- package/package.json +13 -11
- package/test/extensions/validExtensions/listeners/jsreport.dontdiscover.config.js +9 -0
- package/test/extensions/validExtensions/listeners/main.js +51 -0
- package/test/extensions/validExtensions/listeners/worker.js +68 -0
- package/test/store/common.js +1 -1
package/lib/main/logger.js
CHANGED
|
@@ -5,10 +5,12 @@ const winston = require('winston')
|
|
|
5
5
|
const Transport = require('winston-transport')
|
|
6
6
|
const debug = require('debug')('jsreport')
|
|
7
7
|
const createDefaultLoggerFormat = require('./createDefaultLoggerFormat')
|
|
8
|
-
const
|
|
8
|
+
const createNormalizeMetaLoggerFormat = require('./createNormalizeMetaLoggerFormat')
|
|
9
|
+
const Request = require('./request')
|
|
9
10
|
|
|
10
11
|
const defaultLoggerFormat = createDefaultLoggerFormat()
|
|
11
12
|
const defaultLoggerFormatWithTimestamp = createDefaultLoggerFormat({ timestamp: true })
|
|
13
|
+
const normalizeMetaLoggerFormat = createNormalizeMetaLoggerFormat()
|
|
12
14
|
|
|
13
15
|
function createLogger () {
|
|
14
16
|
const logger = winston.createLogger(getConfigurationOptions())
|
|
@@ -180,25 +182,30 @@ function configureLogger (logger, _transports) {
|
|
|
180
182
|
logger.add(transportInstance)
|
|
181
183
|
}
|
|
182
184
|
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
+
const originalLog = logger.log
|
|
185
186
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
187
|
+
// we want to normalize the req has httpIncomingRequest early
|
|
188
|
+
// otherwise we will get serialization issues when trying to
|
|
189
|
+
// log http.IncomingRequest
|
|
190
|
+
logger.log = function (level, msg, ...splat) {
|
|
191
|
+
const [meta] = splat
|
|
192
|
+
|
|
193
|
+
if (
|
|
194
|
+
typeof meta === 'object' &&
|
|
195
|
+
meta !== null &&
|
|
196
|
+
meta.context != null &&
|
|
197
|
+
meta.socket != null
|
|
198
|
+
) {
|
|
199
|
+
splat[0] = Request(meta)
|
|
197
200
|
}
|
|
198
201
|
|
|
199
|
-
return
|
|
200
|
-
}
|
|
202
|
+
return originalLog.call(this, level, msg, ...splat)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
logger.__configured__ = true
|
|
206
|
+
}
|
|
201
207
|
|
|
208
|
+
function getConfigurationOptions () {
|
|
202
209
|
return {
|
|
203
210
|
levels: {
|
|
204
211
|
error: 0,
|
|
@@ -207,7 +214,7 @@ function getConfigurationOptions () {
|
|
|
207
214
|
debug: 3
|
|
208
215
|
},
|
|
209
216
|
format: winston.format.combine(
|
|
210
|
-
|
|
217
|
+
normalizeMetaLoggerFormat(),
|
|
211
218
|
defaultLoggerFormatWithTimestamp()
|
|
212
219
|
),
|
|
213
220
|
transports: [new DebugTransport()]
|
|
@@ -231,6 +238,7 @@ class DebugTransport extends Transport {
|
|
|
231
238
|
|
|
232
239
|
this.format = options.format || winston.format.combine(
|
|
233
240
|
winston.format.colorize(),
|
|
241
|
+
normalizeMetaLoggerFormat(),
|
|
234
242
|
defaultLoggerFormat()
|
|
235
243
|
)
|
|
236
244
|
|
package/lib/main/profiler.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const EventEmitter = require('events')
|
|
2
|
+
const winston = require('winston')
|
|
2
3
|
const extend = require('node.extend.without.arrays')
|
|
3
4
|
const generateRequestId = require('../shared/generateRequestId')
|
|
4
5
|
const fs = require('fs/promises')
|
|
@@ -20,20 +21,44 @@ module.exports = (reporter) => {
|
|
|
20
21
|
})
|
|
21
22
|
|
|
22
23
|
const profilersMap = new Map()
|
|
23
|
-
|
|
24
24
|
const profilerOperationsChainsMap = new Map()
|
|
25
|
-
|
|
25
|
+
const profilerRequestMap = new Map()
|
|
26
|
+
const profilerLogRequestMap = new Map()
|
|
27
|
+
|
|
28
|
+
function runInProfilerChain (fnOrOptions, req) {
|
|
26
29
|
if (req.context.profiling.mode === 'disabled') {
|
|
27
30
|
return
|
|
28
31
|
}
|
|
29
32
|
|
|
33
|
+
let fn
|
|
34
|
+
let cleanFn
|
|
35
|
+
|
|
36
|
+
if (typeof fnOrOptions === 'function') {
|
|
37
|
+
fn = fnOrOptions
|
|
38
|
+
} else {
|
|
39
|
+
fn = fnOrOptions.fn
|
|
40
|
+
cleanFn = fnOrOptions.cleanFn
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// this only happens when rendering remote delegated requests on docker workers
|
|
44
|
+
// there won't be operations chain because the request started from another server
|
|
45
|
+
if (!profilerOperationsChainsMap.has(req.context.rootId)) {
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
30
49
|
profilerOperationsChainsMap.set(req.context.rootId, profilerOperationsChainsMap.get(req.context.rootId).then(async () => {
|
|
50
|
+
if (cleanFn) {
|
|
51
|
+
cleanFn()
|
|
52
|
+
}
|
|
53
|
+
|
|
31
54
|
if (req.context.profiling.chainFailed) {
|
|
32
55
|
return
|
|
33
56
|
}
|
|
34
57
|
|
|
35
58
|
try {
|
|
36
|
-
|
|
59
|
+
if (fn) {
|
|
60
|
+
await fn()
|
|
61
|
+
}
|
|
37
62
|
} catch (e) {
|
|
38
63
|
reporter.logger.warn('Failed persist profile', e)
|
|
39
64
|
req.context.profiling.chainFailed = true
|
|
@@ -54,7 +79,7 @@ module.exports = (reporter) => {
|
|
|
54
79
|
return m
|
|
55
80
|
}
|
|
56
81
|
|
|
57
|
-
function emitProfiles (events, req) {
|
|
82
|
+
function emitProfiles ({ events, log = true }, req) {
|
|
58
83
|
if (events.length === 0) {
|
|
59
84
|
return
|
|
60
85
|
}
|
|
@@ -63,7 +88,9 @@ module.exports = (reporter) => {
|
|
|
63
88
|
|
|
64
89
|
for (const m of events) {
|
|
65
90
|
if (m.type === 'log') {
|
|
66
|
-
|
|
91
|
+
if (log) {
|
|
92
|
+
reporter.logger[m.level](m.message, { ...req, ...m.meta, timestamp: m.timestamp, fromEmitProfile: true })
|
|
93
|
+
}
|
|
67
94
|
} else {
|
|
68
95
|
lastOperation = m
|
|
69
96
|
}
|
|
@@ -78,19 +105,37 @@ module.exports = (reporter) => {
|
|
|
78
105
|
}
|
|
79
106
|
|
|
80
107
|
runInProfilerChain(() => {
|
|
81
|
-
|
|
82
|
-
return fs.appendFile(req.context.profiling.logFilePath, Buffer.from(events.map(m => JSON.stringify(m)).join('\n') + '\n'))
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return reporter.blobStorage.append(
|
|
86
|
-
req.context.profiling.entity.blobName,
|
|
87
|
-
Buffer.from(events.map(m => JSON.stringify(m)).join('\n') + '\n'), req
|
|
88
|
-
)
|
|
108
|
+
return fs.appendFile(req.context.profiling.logFilePath, Buffer.from(events.map(m => JSON.stringify(m)).join('\n') + '\n'))
|
|
89
109
|
}, req)
|
|
90
110
|
}
|
|
91
111
|
|
|
92
|
-
reporter.registerMainAction('profile', async (
|
|
93
|
-
|
|
112
|
+
reporter.registerMainAction('profile', async (eventsOrOptions, _req) => {
|
|
113
|
+
let req = _req
|
|
114
|
+
|
|
115
|
+
// if there is request stored here then take it, this is needed
|
|
116
|
+
// for docker workers remote requests, so the emitProfile can work
|
|
117
|
+
// with the real render request object
|
|
118
|
+
if (profilerRequestMap.has(req.context.rootId) && req.__isJsreportRequest__ == null) {
|
|
119
|
+
req = profilerRequestMap.get(req.context.rootId)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let events
|
|
123
|
+
let log
|
|
124
|
+
|
|
125
|
+
if (Array.isArray(eventsOrOptions)) {
|
|
126
|
+
events = eventsOrOptions
|
|
127
|
+
} else {
|
|
128
|
+
events = eventsOrOptions.events
|
|
129
|
+
log = eventsOrOptions.log
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const params = { events }
|
|
133
|
+
|
|
134
|
+
if (log != null) {
|
|
135
|
+
params.log = log
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return emitProfiles(params, req)
|
|
94
139
|
})
|
|
95
140
|
|
|
96
141
|
reporter.attachProfiler = (req, profileMode) => {
|
|
@@ -118,20 +163,15 @@ module.exports = (reporter) => {
|
|
|
118
163
|
|
|
119
164
|
req.context.profiling.lastOperation = null
|
|
120
165
|
|
|
121
|
-
const blobName = `profiles/${req.context.rootId}.log`
|
|
122
|
-
|
|
123
166
|
const profile = {
|
|
124
167
|
_id: reporter.documentStore.generateId(),
|
|
125
168
|
timestamp: new Date(),
|
|
126
169
|
state: 'queued',
|
|
127
|
-
mode: req.context.profiling.mode
|
|
128
|
-
blobName
|
|
170
|
+
mode: req.context.profiling.mode
|
|
129
171
|
}
|
|
130
172
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
req.context.profiling.logFilePath = pathToFile
|
|
134
|
-
}
|
|
173
|
+
const { pathToFile } = await reporter.writeTempFile((uuid) => `${uuid}.log`, '')
|
|
174
|
+
req.context.profiling.logFilePath = pathToFile
|
|
135
175
|
|
|
136
176
|
runInProfilerChain(async () => {
|
|
137
177
|
req.context.skipValidationFor = profile
|
|
@@ -149,14 +189,16 @@ module.exports = (reporter) => {
|
|
|
149
189
|
|
|
150
190
|
req.context.profiling.profileStartOperationId = profileStartOperation.operationId
|
|
151
191
|
|
|
152
|
-
emitProfiles([profileStartOperation], req)
|
|
192
|
+
emitProfiles({ events: [profileStartOperation] }, req)
|
|
153
193
|
|
|
154
|
-
emitProfiles(
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
194
|
+
emitProfiles({
|
|
195
|
+
events: [createProfileMessage({
|
|
196
|
+
type: 'log',
|
|
197
|
+
level: 'info',
|
|
198
|
+
message: `Render request ${req.context.reportCounter} queued for execution and waiting for available worker`,
|
|
199
|
+
previousOperationId: profileStartOperation.operationId
|
|
200
|
+
}, req)]
|
|
201
|
+
}, req)
|
|
160
202
|
})
|
|
161
203
|
|
|
162
204
|
reporter.beforeRenderListeners.add('profiler', async (req, res) => {
|
|
@@ -164,28 +206,20 @@ module.exports = (reporter) => {
|
|
|
164
206
|
state: 'running'
|
|
165
207
|
}
|
|
166
208
|
|
|
209
|
+
// we set the request here because this listener will container the req which
|
|
210
|
+
// the .render() starts
|
|
211
|
+
profilerRequestMap.set(req.context.rootId, req)
|
|
212
|
+
|
|
167
213
|
const template = await reporter.templates.resolveTemplate(req)
|
|
168
214
|
if (template && template._id) {
|
|
169
215
|
req.context.resolvedTemplate = extend(true, {}, template)
|
|
170
|
-
const templatePath = await reporter.folders.resolveEntityPath(template, 'templates', req)
|
|
171
|
-
const blobName = `profiles/${templatePath.substring(1)}/${req.context.rootId}.log`
|
|
172
|
-
update.templateShortid = template.shortid
|
|
173
|
-
|
|
174
|
-
const originalBlobName = req.context.profiling.entity.blobName
|
|
175
|
-
// we want to store the profile into blobName path reflecting the template path so we need to copy the blob to new path now
|
|
176
|
-
runInProfilerChain(async () => {
|
|
177
|
-
if (req.context.profiling.logFilePath == null) {
|
|
178
|
-
const content = await reporter.blobStorage.read(originalBlobName, req)
|
|
179
|
-
await reporter.blobStorage.write(blobName, content, req)
|
|
180
|
-
return reporter.blobStorage.remove(originalBlobName, req)
|
|
181
|
-
}
|
|
182
|
-
}, req)
|
|
183
216
|
|
|
184
|
-
update.
|
|
217
|
+
update.templateShortid = template.shortid
|
|
185
218
|
}
|
|
186
219
|
|
|
187
220
|
runInProfilerChain(() => {
|
|
188
221
|
req.context.skipValidationFor = update
|
|
222
|
+
|
|
189
223
|
return reporter.documentStore.collection('profiles').update({
|
|
190
224
|
_id: req.context.profiling.entity._id
|
|
191
225
|
}, {
|
|
@@ -197,28 +231,38 @@ module.exports = (reporter) => {
|
|
|
197
231
|
})
|
|
198
232
|
|
|
199
233
|
reporter.afterRenderListeners.add('profiler', async (req, res) => {
|
|
200
|
-
emitProfiles(
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
234
|
+
emitProfiles({
|
|
235
|
+
events: [createProfileMessage({
|
|
236
|
+
type: 'operationEnd',
|
|
237
|
+
doDiffs: false,
|
|
238
|
+
previousEventId: req.context.profiling.lastEventId,
|
|
239
|
+
previousOperationId: req.context.profiling.lastOperationId,
|
|
240
|
+
operationId: req.context.profiling.profileStartOperationId
|
|
241
|
+
}, req)]
|
|
242
|
+
}, req)
|
|
207
243
|
|
|
208
244
|
res.meta.profileId = req.context.profiling?.entity?._id
|
|
209
245
|
|
|
210
246
|
runInProfilerChain(async () => {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
await
|
|
247
|
+
let blobName = `profiles/${req.context.rootId}.log`
|
|
248
|
+
|
|
249
|
+
if (req.context.resolvedTemplate) {
|
|
250
|
+
const templatePath = await reporter.folders.resolveEntityPath(req.context.resolvedTemplate, 'templates', req)
|
|
251
|
+
blobName = `profiles/${templatePath.substring(1)}/${req.context.rootId}.log`
|
|
215
252
|
}
|
|
216
253
|
|
|
254
|
+
const content = await fs.readFile(req.context.profiling.logFilePath)
|
|
255
|
+
blobName = await reporter.blobStorage.write(blobName, content, req)
|
|
256
|
+
await fs.unlink(req.context.profiling.logFilePath)
|
|
257
|
+
|
|
217
258
|
const update = {
|
|
218
259
|
state: 'success',
|
|
219
|
-
finishedOn: new Date()
|
|
260
|
+
finishedOn: new Date(),
|
|
261
|
+
blobName
|
|
220
262
|
}
|
|
263
|
+
|
|
221
264
|
req.context.skipValidationFor = update
|
|
265
|
+
|
|
222
266
|
await reporter.documentStore.collection('profiles').update({
|
|
223
267
|
_id: req.context.profiling.entity._id
|
|
224
268
|
}, {
|
|
@@ -226,61 +270,128 @@ module.exports = (reporter) => {
|
|
|
226
270
|
}, req)
|
|
227
271
|
}, req)
|
|
228
272
|
|
|
229
|
-
// we don't
|
|
273
|
+
// we don't clean the profiler maps here, we do it later in main reporter .render,
|
|
274
|
+
// because the renderErrorListeners can be invoked if the afterRenderListener fails
|
|
230
275
|
})
|
|
231
276
|
|
|
232
277
|
reporter.renderErrorListeners.add('profiler', async (req, res, e) => {
|
|
233
|
-
|
|
234
|
-
res.meta.profileId = req.context.profiling?.entity?._id
|
|
278
|
+
res.meta.profileId = req.context.profiling?.entity?._id
|
|
235
279
|
|
|
236
|
-
|
|
237
|
-
|
|
280
|
+
if (req.context.profiling?.entity != null) {
|
|
281
|
+
emitProfiles({
|
|
282
|
+
events: [{
|
|
238
283
|
type: 'error',
|
|
239
284
|
timestamp: new Date().getTime(),
|
|
240
285
|
...e,
|
|
241
286
|
id: generateRequestId(),
|
|
242
287
|
stack: e.stack,
|
|
243
288
|
message: e.message
|
|
244
|
-
}]
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
289
|
+
}]
|
|
290
|
+
}, req)
|
|
291
|
+
|
|
292
|
+
runInProfilerChain(async () => {
|
|
293
|
+
const update = {
|
|
294
|
+
state: 'error',
|
|
295
|
+
finishedOn: new Date(),
|
|
296
|
+
error: e.toString()
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (req.context.profiling.logFilePath != null) {
|
|
300
|
+
let blobName = `profiles/${req.context.rootId}.log`
|
|
251
301
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
error: e.toString()
|
|
302
|
+
if (req.context.resolvedTemplate) {
|
|
303
|
+
const templatePath = await reporter.folders.resolveEntityPath(req.context.resolvedTemplate, 'templates', req)
|
|
304
|
+
blobName = `profiles/${templatePath.substring(1)}/${req.context.rootId}.log`
|
|
256
305
|
}
|
|
257
|
-
|
|
258
|
-
await
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
306
|
+
|
|
307
|
+
const content = await fs.readFile(req.context.profiling.logFilePath)
|
|
308
|
+
blobName = await reporter.blobStorage.write(blobName, content, req)
|
|
309
|
+
await fs.unlink(req.context.profiling.logFilePath)
|
|
310
|
+
update.blobName = blobName
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
req.context.skipValidationFor = update
|
|
314
|
+
|
|
315
|
+
await reporter.documentStore.collection('profiles').update({
|
|
316
|
+
_id: req.context.profiling.entity._id
|
|
317
|
+
}, {
|
|
318
|
+
$set: update
|
|
263
319
|
}, req)
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
320
|
+
}, req)
|
|
321
|
+
|
|
322
|
+
// we don't clean the profiler maps here, we do it later in main reporter .render,
|
|
323
|
+
// we do this to ensure a single and clear order
|
|
268
324
|
}
|
|
269
325
|
})
|
|
270
326
|
|
|
327
|
+
const configuredPreviously = reporter.logger.__profilerConfigured__ === true
|
|
328
|
+
|
|
329
|
+
if (!configuredPreviously) {
|
|
330
|
+
const originalLog = reporter.logger.log
|
|
331
|
+
|
|
332
|
+
// we want to catch the original request
|
|
333
|
+
reporter.logger.log = function (level, msg, ...splat) {
|
|
334
|
+
const [meta] = splat
|
|
335
|
+
|
|
336
|
+
if (typeof meta === 'object' && meta !== null && meta.context?.rootId != null) {
|
|
337
|
+
profilerLogRequestMap.set(meta.context.rootId, meta)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return originalLog.call(this, level, msg, ...splat)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const mainLogsToProfile = winston.format((info) => {
|
|
344
|
+
// propagate the request logs occurring on main to the profile
|
|
345
|
+
if (info.rootId != null && info.fromEmitProfile == null && profilerLogRequestMap.has(info.rootId)) {
|
|
346
|
+
const req = profilerLogRequestMap.get(info.rootId)
|
|
347
|
+
|
|
348
|
+
emitProfiles({
|
|
349
|
+
events: [createProfileMessage({
|
|
350
|
+
type: 'log',
|
|
351
|
+
level: info.level,
|
|
352
|
+
message: info.message,
|
|
353
|
+
previousOperationId: req.context.profiling.lastOperationId
|
|
354
|
+
}, req)],
|
|
355
|
+
log: false
|
|
356
|
+
}, req)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (info.fromEmitProfile != null) {
|
|
360
|
+
delete info.fromEmitProfile
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return info
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
reporter.logger.format = winston.format.combine(
|
|
367
|
+
reporter.logger.format,
|
|
368
|
+
mainLogsToProfile()
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
reporter.logger.__profilerConfigured__ = true
|
|
372
|
+
}
|
|
373
|
+
|
|
271
374
|
let profilesCleanupInterval
|
|
375
|
+
|
|
272
376
|
reporter.initializeListeners.add('profiler', async () => {
|
|
273
377
|
reporter.documentStore.collection('profiles').beforeRemoveListeners.add('profiles', async (query, req) => {
|
|
274
378
|
const profiles = await reporter.documentStore.collection('profiles').find(query, req)
|
|
275
379
|
|
|
276
380
|
for (const profile of profiles) {
|
|
277
|
-
|
|
381
|
+
if (profile.blobName != null) {
|
|
382
|
+
await reporter.blobStorage.remove(profile.blobName)
|
|
383
|
+
}
|
|
278
384
|
}
|
|
279
385
|
})
|
|
280
386
|
|
|
281
|
-
|
|
387
|
+
function profilesCleanupExec () {
|
|
388
|
+
return reporter._profilesCleanup()
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
profilesCleanupInterval = setInterval(profilesCleanupExec, reporter.options.profiler.cleanupInterval)
|
|
282
392
|
profilesCleanupInterval.unref()
|
|
283
|
-
|
|
393
|
+
|
|
394
|
+
await reporter._profilesCleanup()
|
|
284
395
|
})
|
|
285
396
|
|
|
286
397
|
reporter.closeListeners.add('profiler', async () => {
|
|
@@ -294,15 +405,24 @@ module.exports = (reporter) => {
|
|
|
294
405
|
await profileAppendPromise
|
|
295
406
|
}
|
|
296
407
|
}
|
|
408
|
+
|
|
409
|
+
profilersMap.clear()
|
|
410
|
+
profilerOperationsChainsMap.clear()
|
|
411
|
+
profilerRequestMap.clear()
|
|
412
|
+
profilerLogRequestMap.clear()
|
|
297
413
|
})
|
|
298
414
|
|
|
299
415
|
let profilesCleanupRunning = false
|
|
300
|
-
|
|
416
|
+
|
|
417
|
+
reporter._profilesCleanup = async function profilesCleanup () {
|
|
301
418
|
if (profilesCleanupRunning) {
|
|
302
419
|
return
|
|
303
420
|
}
|
|
421
|
+
|
|
304
422
|
profilesCleanupRunning = true
|
|
423
|
+
|
|
305
424
|
let lastRemoveError
|
|
425
|
+
|
|
306
426
|
try {
|
|
307
427
|
const profiles = await reporter.documentStore.collection('profiles').find({}).sort({ timestamp: -1 })
|
|
308
428
|
const profilesToRemove = profiles.slice(reporter.options.profiler.maxProfilesHistory)
|
|
@@ -330,4 +450,28 @@ module.exports = (reporter) => {
|
|
|
330
450
|
reporter.logger.warn('Profile cleanup failed for some entities, last error:', lastRemoveError)
|
|
331
451
|
}
|
|
332
452
|
}
|
|
453
|
+
|
|
454
|
+
return function cleanProfileInRequest (req) {
|
|
455
|
+
// - req.context.profiling is empty only on an early error
|
|
456
|
+
// that happens before setting the profiler.
|
|
457
|
+
// - when profiling.mode is "disabled" there is no profiler chain to append
|
|
458
|
+
// in both cases we want the clean code to happen immediately
|
|
459
|
+
if (req.context.profiling?.entity == null || req.context.profiling?.mode === 'disabled') {
|
|
460
|
+
profilersMap.delete(req.context.rootId)
|
|
461
|
+
profilerOperationsChainsMap.delete(req.context.rootId)
|
|
462
|
+
profilerRequestMap.delete(req.context.rootId)
|
|
463
|
+
profilerLogRequestMap.delete(req.context.rootId)
|
|
464
|
+
return
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// this will get executed always even if some fn in the chain fails
|
|
468
|
+
runInProfilerChain({
|
|
469
|
+
cleanFn: () => {
|
|
470
|
+
profilersMap.delete(req.context.rootId)
|
|
471
|
+
profilerOperationsChainsMap.delete(req.context.rootId)
|
|
472
|
+
profilerRequestMap.delete(req.context.rootId)
|
|
473
|
+
profilerLogRequestMap.delete(req.context.rootId)
|
|
474
|
+
}
|
|
475
|
+
}, req)
|
|
476
|
+
}
|
|
333
477
|
}
|
package/lib/main/reporter.js
CHANGED
|
@@ -196,7 +196,8 @@ class MainReporter extends Reporter {
|
|
|
196
196
|
this.blobStorage = BlobStorage(this, this.options)
|
|
197
197
|
blobStorageActions(this)
|
|
198
198
|
Templates(this)
|
|
199
|
-
|
|
199
|
+
|
|
200
|
+
this._cleanProfileInRequest = Profiler(this)
|
|
200
201
|
|
|
201
202
|
this.folders = Object.assign(this.folders, Folders(this))
|
|
202
203
|
|
|
@@ -472,11 +473,17 @@ class MainReporter extends Reporter {
|
|
|
472
473
|
}, req)
|
|
473
474
|
|
|
474
475
|
Object.assign(res, responseResult)
|
|
476
|
+
|
|
475
477
|
await this.afterRenderListeners.fire(req, res)
|
|
478
|
+
|
|
476
479
|
res.stream = Readable.from(res.content)
|
|
480
|
+
|
|
481
|
+
this._cleanProfileInRequest(req)
|
|
482
|
+
|
|
477
483
|
return res
|
|
478
484
|
} catch (err) {
|
|
479
485
|
await this._handleRenderError(req, res, err)
|
|
486
|
+
this._cleanProfileInRequest(req)
|
|
480
487
|
throw err
|
|
481
488
|
} finally {
|
|
482
489
|
if (worker && !workerAborted && !dontCloseProcessing) {
|
|
@@ -93,6 +93,12 @@ module.exports = (reporter) => {
|
|
|
93
93
|
const asyncResultMap = executionAsyncResultsMap.get(context.__executionId)
|
|
94
94
|
return Promise.all([...asyncResultMap.keys()].map((k) => asyncResultMap.get(k)))
|
|
95
95
|
}
|
|
96
|
+
},
|
|
97
|
+
createAsyncHelperResult: (v) => {
|
|
98
|
+
const asyncResultMap = executionAsyncResultsMap.get(context.__executionId)
|
|
99
|
+
const asyncResultId = nanoid(7)
|
|
100
|
+
asyncResultMap.set(asyncResultId, v)
|
|
101
|
+
return `{#asyncHelperResult ${asyncResultId}}`
|
|
96
102
|
}
|
|
97
103
|
}
|
|
98
104
|
})
|
|
@@ -197,7 +203,11 @@ module.exports = (reporter) => {
|
|
|
197
203
|
const wrappedTopLevelFunctions = {}
|
|
198
204
|
|
|
199
205
|
for (const h of Object.keys(topLevelFunctions)) {
|
|
200
|
-
|
|
206
|
+
if (engine.getWrappingHelpersEnabled && engine.getWrappingHelpersEnabled(req) === false) {
|
|
207
|
+
wrappedTopLevelFunctions[h] = engine.wrapHelper(topLevelFunctions[h], { context })
|
|
208
|
+
} else {
|
|
209
|
+
wrappedTopLevelFunctions[h] = wrapHelperForAsyncSupport(topLevelFunctions[h], asyncResultMap)
|
|
210
|
+
}
|
|
201
211
|
}
|
|
202
212
|
|
|
203
213
|
let contentResult = await engine.execute(compiledTemplate, wrappedTopLevelFunctions, data, { require })
|
|
@@ -249,7 +259,7 @@ module.exports = (reporter) => {
|
|
|
249
259
|
try {
|
|
250
260
|
return await reporter.runInSandbox({
|
|
251
261
|
context: {
|
|
252
|
-
...(engine.createContext ? engine.createContext() : {})
|
|
262
|
+
...(engine.createContext ? engine.createContext(req) : {})
|
|
253
263
|
},
|
|
254
264
|
userCode: normalizedHelpers,
|
|
255
265
|
initFn,
|
|
@@ -36,7 +36,10 @@ class Profiler {
|
|
|
36
36
|
if (profilingInfo) {
|
|
37
37
|
const batch = profilingInfo.batch
|
|
38
38
|
profilingInfo.batch = []
|
|
39
|
-
|
|
39
|
+
|
|
40
|
+
if (batch.length > 0) {
|
|
41
|
+
await this.reporter.executeMainAction('profile', batch, profilingInfo.req).catch((e) => this.reporter.logger.error(e, profilingInfo.req))
|
|
42
|
+
}
|
|
40
43
|
}
|
|
41
44
|
}
|
|
42
45
|
}
|
|
@@ -158,7 +161,10 @@ class Profiler {
|
|
|
158
161
|
const profilingInfo = this.profiledRequestsMap.get(req.context.rootId)
|
|
159
162
|
if (profilingInfo) {
|
|
160
163
|
this.profiledRequestsMap.delete(req.context.rootId)
|
|
161
|
-
|
|
164
|
+
|
|
165
|
+
if (profilingInfo.batch.length > 0) {
|
|
166
|
+
await this.reporter.executeMainAction('profile', profilingInfo.batch, req)
|
|
167
|
+
}
|
|
162
168
|
}
|
|
163
169
|
}
|
|
164
170
|
}
|
|
@@ -130,7 +130,7 @@ module.exports = (reporter) => {
|
|
|
130
130
|
reporter.requestModulesCache.set(request.context.rootId, Object.create(null))
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
reporter.logger.info(`Starting rendering request ${request.context.reportCounter} (user: ${(request.context.user ? request.context.user.
|
|
133
|
+
reporter.logger.info(`Starting rendering request ${request.context.reportCounter} (user: ${(request.context.user ? request.context.user.name : 'null')})`, request)
|
|
134
134
|
|
|
135
135
|
// TODO
|
|
136
136
|
/* if (reporter.entityTypeValidator.getSchema('TemplateType') != null) {
|
|
@@ -34,7 +34,9 @@ module.exports = (reporter) => {
|
|
|
34
34
|
|
|
35
35
|
const { sourceFilesInfo, run, compileScript, restore, sandbox, sandboxRequire } = createSandbox(context, {
|
|
36
36
|
onLog: (log) => {
|
|
37
|
-
|
|
37
|
+
// we mark any log done in sandbox as userLevel: true, this allows us to detect which logs belongs to user
|
|
38
|
+
// and can potentially contain sensitive information
|
|
39
|
+
reporter.logger[log.level](log.message, { ...req, timestamp: log.timestamp, userLevel: true })
|
|
38
40
|
},
|
|
39
41
|
formatError: (error, moduleName) => {
|
|
40
42
|
error.message += ` To be able to require custom modules you need to add to configuration { "trustUserCode": true } or enable just specific module using { sandbox: { allowedModules": ["${moduleName}"] }`
|