@jsreport/jsreport-core
Advanced tools
Comparing version 3.10.0 to 3.11.0
@@ -77,2 +77,3 @@ const resolveEntityPath = require('../../shared/folders/resolveEntityPath') | ||
statusCode: 400, | ||
weak: true, | ||
code: 'DUPLICATED_ENTITY', | ||
@@ -79,0 +80,0 @@ existingEntity: existingEntity.entity, |
const EventEmitter = require('events') | ||
const winston = require('winston') | ||
const Transport = require('winston-transport') | ||
const extend = require('node.extend.without.arrays') | ||
const generateRequestId = require('../shared/generateRequestId') | ||
const fs = require('fs/promises') | ||
const { SPLAT } = require('triple-beam') | ||
module.exports = (reporter) => { | ||
@@ -15,3 +15,4 @@ reporter.documentStore.registerEntityType('ProfileType', { | ||
mode: { type: 'Edm.String', schema: { enum: ['full', 'standard', 'disabled'] } }, | ||
blobName: { type: 'Edm.String' } | ||
blobName: { type: 'Edm.String' }, | ||
timeout: { type: 'Edm.Int32' } | ||
}) | ||
@@ -27,3 +28,2 @@ | ||
const profilerRequestMap = new Map() | ||
const profilerLogRequestMap = new Map() | ||
@@ -94,3 +94,3 @@ function runInProfilerChain (fnOrOptions, req) { | ||
if (log) { | ||
reporter.logger[m.level](m.message, { ...req, ...m.meta, timestamp: m.timestamp, fromEmitProfile: true }) | ||
reporter.logger[m.level](m.message, { ...req, ...m.meta, timestamp: m.timestamp, logged: true }) | ||
} | ||
@@ -173,3 +173,4 @@ } else { | ||
state: 'queued', | ||
mode: req.context.profiling.mode | ||
mode: req.context.profiling.mode, | ||
timeout: reporter.options.enableRequestReportTimeout && req.options.timeout ? req.options.timeout : reporter.options.reportTimeout | ||
} | ||
@@ -332,46 +333,37 @@ | ||
// we want to add to profiles also log messages from the main | ||
const configuredPreviously = reporter.logger.__profilerConfigured__ === true | ||
if (!configuredPreviously) { | ||
const originalLog = reporter.logger.log | ||
// we emit from winston transport, so winston formatters can still format message | ||
class EmittingProfilesTransport extends Transport { | ||
log (info, callback) { | ||
setImmediate(() => { | ||
this.emit('logged', info) | ||
}) | ||
// we want to catch the original request | ||
reporter.logger.log = function (level, msg, ...splat) { | ||
const [meta] = splat | ||
if (info[SPLAT]) { | ||
const [req] = info[SPLAT] | ||
if (typeof meta === 'object' && meta !== null && meta.context?.rootId != null) { | ||
profilerLogRequestMap.set(meta.context.rootId, meta) | ||
if (req && req.context && req.logged !== true) { | ||
emitProfiles({ | ||
events: [createProfileMessage({ | ||
type: 'log', | ||
level: info.level, | ||
message: info.message, | ||
previousOperationId: req.context.profiling.lastOperationId | ||
}, req)], | ||
log: false | ||
}, req) | ||
} | ||
} | ||
callback() | ||
} | ||
return originalLog.call(this, level, msg, ...splat) | ||
} | ||
const mainLogsToProfile = winston.format((info) => { | ||
// propagate the request logs occurring on main to the profile | ||
if (info.rootId != null && info.fromEmitProfile == null && profilerLogRequestMap.has(info.rootId)) { | ||
const req = profilerLogRequestMap.get(info.rootId) | ||
reporter.logger.add(new EmittingProfilesTransport({ | ||
format: reporter.logger.format, | ||
level: 'debug' | ||
})) | ||
emitProfiles({ | ||
events: [createProfileMessage({ | ||
type: 'log', | ||
level: info.level, | ||
message: info.message, | ||
previousOperationId: req.context.profiling.lastOperationId | ||
}, req)], | ||
log: false | ||
}, req) | ||
} | ||
if (info.fromEmitProfile != null) { | ||
delete info.fromEmitProfile | ||
} | ||
return info | ||
}) | ||
reporter.logger.format = winston.format.combine( | ||
reporter.logger.format, | ||
mainLogsToProfile() | ||
) | ||
reporter.logger.__profilerConfigured__ = true | ||
@@ -418,3 +410,2 @@ } | ||
profilerRequestMap.clear() | ||
profilerLogRequestMap.clear() | ||
}) | ||
@@ -431,7 +422,9 @@ | ||
let lastRemoveError | ||
let lastError | ||
try { | ||
const profiles = await reporter.documentStore.collection('profiles').find({}).sort({ timestamp: -1 }) | ||
const profilesToRemove = profiles.slice(reporter.options.profiler.maxProfilesHistory) | ||
const profilesToRemove = await reporter.documentStore.collection('profiles') | ||
.find({}, { _id: 1 }).sort({ timestamp: -1 }) | ||
.skip(reporter.options.profiler.maxProfilesHistory) | ||
.toArray() | ||
@@ -448,5 +441,38 @@ for (const profile of profilesToRemove) { | ||
} catch (e) { | ||
lastRemoveError = e | ||
lastError = e | ||
} | ||
} | ||
const notFinishedProfiles = await reporter.documentStore.collection('profiles') | ||
.find({ $or: [{ state: 'running' }, { state: 'queued' }] }, { _id: 1, timeout: 1, timestamp: 1 }) | ||
.toArray() | ||
for (const profile of notFinishedProfiles) { | ||
if (reporter.closed || reporter.closing) { | ||
return | ||
} | ||
if (!profile.timeout) { | ||
continue | ||
} | ||
const whenShouldBeFinished = profile.timestamp + profile.timeout + reporter.options.reportTimeoutMargin * 2 | ||
if (whenShouldBeFinished < new Date().getTime()) { | ||
continue | ||
} | ||
try { | ||
await reporter.documentStore.collection('profiles').update({ | ||
_id: profile._id | ||
}, { | ||
$set: { | ||
state: 'error', | ||
finishedOn: new Date(), | ||
error: 'The server did not update the report profile before its timeout. This can happen when the server is unexpectedly stopped.' | ||
} | ||
}) | ||
} catch (e) { | ||
lastError = e | ||
} | ||
} | ||
} catch (e) { | ||
@@ -458,4 +484,4 @@ reporter.logger.warn('Profile cleanup failed', e) | ||
if (lastRemoveError) { | ||
reporter.logger.warn('Profile cleanup failed for some entities, last error:', lastRemoveError) | ||
if (lastError) { | ||
reporter.logger.warn('Profile cleanup failed for some entities, last error:', lastError) | ||
} | ||
@@ -473,3 +499,2 @@ } | ||
profilerRequestMap.delete(req.context.rootId) | ||
profilerLogRequestMap.delete(req.context.rootId) | ||
return | ||
@@ -484,3 +509,2 @@ } | ||
profilerRequestMap.delete(req.context.rootId) | ||
profilerLogRequestMap.delete(req.context.rootId) | ||
} | ||
@@ -487,0 +511,0 @@ }, req) |
@@ -452,2 +452,3 @@ /*! | ||
} finally { | ||
this._cleanProfileInRequest(req) | ||
if (!workerAborted) { | ||
@@ -481,2 +482,6 @@ await worker.release(req) | ||
if (!res.content) { | ||
this.logger.error('Worker didnt return render res.content, returned:' + JSON.stringify(responseResult), req) | ||
} | ||
res.stream = Readable.from(res.content) | ||
@@ -483,0 +488,0 @@ |
@@ -9,3 +9,3 @@ /*! | ||
const Settings = module.exports = function () { | ||
this._collection = [] | ||
} | ||
@@ -19,11 +19,5 @@ | ||
this._collection.push(settingItem) | ||
return this.documentStore.collection('settings').insert(settingItem, localReqWithoutAuthorization(req)) | ||
} | ||
Settings.prototype.get = function (key) { | ||
return this._collection.find((s) => s.key === key) | ||
} | ||
Settings.prototype.findValue = async function (key, req) { | ||
@@ -41,4 +35,2 @@ const res = await this.documentStore.collection('settings').find({ key: key }, localReqWithoutAuthorization(req)) | ||
this.get(key).value = value | ||
return this.documentStore.collection('settings').update({ | ||
@@ -64,4 +56,2 @@ key: key | ||
const incompatibleSettingsToRemove = [] | ||
if (authorization != null) { | ||
@@ -102,26 +92,2 @@ const col = documentStore.collection('settings') | ||
} | ||
const res = await documentStore.collection('settings').find({}) | ||
res.forEach((v) => { | ||
if (typeof v.value !== 'string') { | ||
return this._collection.push({ | ||
key: v.key, | ||
value: v.value | ||
}) | ||
} | ||
try { | ||
return this._collection.push({ | ||
key: v.key, | ||
value: JSON.parse(v.value) | ||
}) | ||
} catch (e) { | ||
incompatibleSettingsToRemove.push(v._id) | ||
} | ||
}) | ||
if (incompatibleSettingsToRemove.length) { | ||
return documentStore.collection('settings').remove({ _id: { $in: incompatibleSettingsToRemove } }) | ||
} | ||
} | ||
@@ -128,0 +94,0 @@ |
@@ -16,3 +16,3 @@ const omit = require('lodash.omit') | ||
// excluding non relevant properties for the log | ||
const newMeta = Object.assign({}, omit(meta, ['rawContent', 'template', 'options', 'data', 'context', 'timestamp', 'cancel'])) | ||
const newMeta = Object.assign({}, omit(meta, ['logged', 'rawContent', 'template', 'options', 'data', 'context', 'timestamp', 'cancel'])) | ||
@@ -19,0 +19,0 @@ if (newMeta.rootId == null && meta.context.rootId != null) { |
{ | ||
"name": "@jsreport/jsreport-core", | ||
"version": "3.10.0", | ||
"version": "3.11.0", | ||
"description": "javascript based business reporting", | ||
@@ -43,3 +43,3 @@ "keywords": [ | ||
"@colors/colors": "1.5.0", | ||
"@jsreport/advanced-workers": "1.2.3", | ||
"@jsreport/advanced-workers": "1.2.4", | ||
"@jsreport/mingo": "2.4.1", | ||
@@ -46,0 +46,0 @@ "@jsreport/reap": "0.1.0", |
@@ -285,2 +285,10 @@ # @jsreport/jsreport-core | ||
### 3.11.0 | ||
- log when worker returns bad res.content | ||
- fix profiler leaks | ||
- remove settings sync API and avoid loading all items to memory | ||
- throw weak error when validating duplicated entity | ||
- ensure we end with profiles with error state when there is server or req timeout | ||
### 3.10.0 | ||
@@ -287,0 +295,0 @@ |
367405
9029
378
+ Added@jsreport/advanced-workers@1.2.4(transitive)
- Removed@jsreport/advanced-workers@1.2.3(transitive)