@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.
@@ -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 normalizeMetaFromLogs = require('../shared/normalizeMetaFromLogs')
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
- logger.__configured__ = true
184
- }
185
+ const originalLog = logger.log
185
186
 
186
- function getConfigurationOptions () {
187
- const normalizeMeta = winston.format((info) => {
188
- const { level, message, ...meta } = info
189
- const newMeta = normalizeMetaFromLogs(level, message, meta)
190
-
191
- if (newMeta != null) {
192
- return {
193
- level,
194
- message,
195
- ...newMeta
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 info
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
- normalizeMeta(),
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
 
@@ -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
- function runInProfilerChain (fn, req) {
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
- await fn()
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
- reporter.logger[m.level](m.message, { ...req, ...m.meta, timestamp: m.timestamp })
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
- if (req.context.profiling.logFilePath) {
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 (events, req) => {
93
- return emitProfiles(events, req)
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
- if (!reporter.blobStorage.supportsAppend) {
132
- const { pathToFile } = await reporter.writeTempFile((uuid) => `${uuid}.log`, '')
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([createProfileMessage({
155
- type: 'log',
156
- level: 'info',
157
- message: `Render request ${req.context.reportCounter} queued for execution and waiting for availible worker`,
158
- previousOperationId: profileStartOperation.operationId
159
- }, req)], req)
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.blobName = blobName
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([createProfileMessage({
201
- type: 'operationEnd',
202
- doDiffs: false,
203
- previousEventId: req.context.profiling.lastEventId,
204
- previousOperationId: req.context.profiling.lastOperationId,
205
- operationId: req.context.profiling.profileStartOperationId
206
- }, req)], req)
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
- if (req.context.profiling.logFilePath != null) {
212
- const content = await fs.readFile(req.context.profiling.logFilePath)
213
- await reporter.blobStorage.write(req.context.profiling.entity.blobName, content, req)
214
- await fs.unlink(req.context.profiling.logFilePath)
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 remove from profiler requests map, because the renderErrorListeners are invoked if the afterRenderListener fails
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
- try {
234
- res.meta.profileId = req.context.profiling?.entity?._id
278
+ res.meta.profileId = req.context.profiling?.entity?._id
235
279
 
236
- if (req.context.profiling?.entity != null) {
237
- emitProfiles([{
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
- }], req)
245
- runInProfilerChain(async () => {
246
- if (req.context.profiling.logFilePath != null) {
247
- const content = await fs.readFile(req.context.profiling.logFilePath, 'utf8')
248
- await reporter.blobStorage.write(req.context.profiling.entity.blobName, content, req)
249
- await fs.unlink(req.context.profiling.logFilePath)
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
- const update = {
253
- state: 'error',
254
- finishedOn: new Date(),
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
- req.context.skipValidationFor = update
258
- await reporter.documentStore.collection('profiles').update({
259
- _id: req.context.profiling.entity._id
260
- }, {
261
- $set: update
262
- }, req)
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
- } finally {
266
- profilersMap.delete(req.context.rootId)
267
- profilerOperationsChainsMap.delete(req.context.rootId)
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
- await reporter.blobStorage.remove(profile.blobName)
381
+ if (profile.blobName != null) {
382
+ await reporter.blobStorage.remove(profile.blobName)
383
+ }
278
384
  }
279
385
  })
280
386
 
281
- profilesCleanupInterval = setInterval(profilesCleanup, reporter.options.profiler.cleanupInterval)
387
+ function profilesCleanupExec () {
388
+ return reporter._profilesCleanup()
389
+ }
390
+
391
+ profilesCleanupInterval = setInterval(profilesCleanupExec, reporter.options.profiler.cleanupInterval)
282
392
  profilesCleanupInterval.unref()
283
- await profilesCleanup()
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
- async function profilesCleanup () {
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
  }
@@ -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
- Profiler(this)
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) {
@@ -58,5 +58,11 @@ module.exports = (name) => {
58
58
  })
59
59
  }
60
60
 
61
+ if (name === 'config.json') {
62
+ throw createError('Entity name "config.json" is reserved and can\'t be used', {
63
+ statusCode: 400
64
+ })
65
+ }
66
+
61
67
  return true
62
68
  }
@@ -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
- wrappedTopLevelFunctions[h] = wrapHelperForAsyncSupport(topLevelFunctions[h], asyncResultMap)
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
- await this.reporter.executeMainAction('profile', batch, profilingInfo.req).catch((e) => this.reporter.logger.error(e, profilingInfo.req))
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
- await this.reporter.executeMainAction('profile', profilingInfo.batch, req)
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.username : 'null')})`, request)
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
- reporter.logger[log.level](log.message, { ...req, timestamp: log.timestamp })
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}"] }`