aggregate-metric-logger
Advanced tools
Comparing version 1.0.0 to 1.1.0
{ | ||
"name": "aggregate-metric-logger", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"description": "aggregate metrics in memory for logging", | ||
"main": "src/index.js", | ||
"scripts": { | ||
"test": "mocha 'src/*.spec.js'", | ||
"test:watch": "mocha 'src/*.spec.js' --watch" | ||
"test": "jest", | ||
"test:watch": "jest --watch --bail" | ||
}, | ||
@@ -28,10 +28,11 @@ "repository": { | ||
"date-fns": "^2.3.0", | ||
"json-stable-stringify": "^1.0.1", | ||
"lodash": "^4.17.15", | ||
"uuid": "^3.3.3" | ||
}, | ||
"devDependencies": { | ||
"chai": "^4.2.0", | ||
"mocha": "^6.2.0", | ||
"sinon": "^7.5.0", | ||
"sinon-chai": "^3.3.0" | ||
"@sinonjs/fake-timers": "^6.0.0", | ||
"@types/jest": "^25.1.3", | ||
"jest": "^25.1.0" | ||
} | ||
} |
@@ -41,2 +41,23 @@ # aggregate-metric-logger | ||
} | ||
``` | ||
``` | ||
### Group by extra parameters | ||
If you want to group by additional parameters inside a tag, you can pass down these extra | ||
params in an object to `count` and `start` as the last optional argument: | ||
```js | ||
metricLogger.count('thing-to-count', 14, { customer_id: 12 }); | ||
const measurementId = metricLogger.start( | ||
'tag-for-the-measuement', | ||
{ event_type: 'nyul', account: 3 } | ||
); | ||
``` | ||
A separete log entry will be created for each seen value combinations seen in the extra params | ||
and these extra params will appended to the log entry. | ||
Be very carefull when using extra params, and only include information you really need. | ||
If you include too many parameters, you could loose the advantage you gain by aggregating your logs | ||
with this package. |
@@ -6,2 +6,4 @@ 'use strict'; | ||
const { getSeconds, startOfMinute, addSeconds } = require('date-fns'); | ||
const { omit, pick } = require('lodash'); | ||
const stringify = require('json-stable-stringify'); | ||
@@ -12,3 +14,3 @@ module.exports = ({ enabled = true, namespace = 'aggregate-metric-logger' } = {}) => { | ||
let measurements = {}; | ||
let metricsByTag = {}; | ||
let metrics = {}; | ||
let initialized = false; | ||
@@ -18,3 +20,3 @@ | ||
logMetrics(); | ||
metricsByTag = {}; | ||
metrics = {}; | ||
setupNextFlush(); | ||
@@ -31,5 +33,8 @@ }; | ||
const addMeasurement = ({ tag, value }) => { | ||
if (!metricsByTag[tag]) { | ||
metricsByTag[tag] = { | ||
const addMeasurement = ({ tag, value, params }) => { | ||
const key = `${tag}_${stringify(params)}`; | ||
if (!metrics[key]) { | ||
metrics[key] = { | ||
tag, | ||
...params, | ||
min: value, | ||
@@ -42,8 +47,8 @@ max: value, | ||
} else { | ||
const metricsSoFar = metricsByTag[tag]; | ||
metricsByTag[tag].min = Math.min(metricsSoFar.min, value); | ||
metricsByTag[tag].max = Math.max(metricsSoFar.max, value); | ||
metricsByTag[tag].sum = metricsSoFar.sum + value; | ||
metricsByTag[tag].count = metricsSoFar.count + 1; | ||
metricsByTag[tag].avarage = metricsByTag[tag].sum / metricsByTag[tag].count | ||
const metricsSoFar = metrics[key]; | ||
metrics[key].min = Math.min(metricsSoFar.min, value); | ||
metrics[key].max = Math.max(metricsSoFar.max, value); | ||
metrics[key].sum = metricsSoFar.sum + value; | ||
metrics[key].count = metricsSoFar.count + 1; | ||
metrics[key].avarage = metrics[key].sum / metrics[key].count | ||
} | ||
@@ -53,6 +58,6 @@ }; | ||
const logMetrics = () => | ||
Object.entries(metricsByTag).forEach(([tag, metrics]) => logger.info(tag, metrics)); | ||
Object.values(metrics).forEach(metrics => logger.info(metrics.tag, omit(metrics, ['tag']))); | ||
return { | ||
count(tag, value) { | ||
count(tag, value, params) { | ||
if (!enabled) { | ||
@@ -65,5 +70,5 @@ return; | ||
} | ||
addMeasurement({ tag, value }); | ||
addMeasurement({ tag, value, params }); | ||
}, | ||
start(tag) { | ||
start(tag, params) { | ||
if (!enabled) { | ||
@@ -78,3 +83,3 @@ return; | ||
const id = uuid(); | ||
measurements[id] = { tag, start: Date.now() }; | ||
measurements[id] = { tag, params, start: Date.now() }; | ||
return id; | ||
@@ -90,3 +95,6 @@ }, | ||
} | ||
addMeasurement({ tag: measurement.tag, value: Date.now() - measurement.start }); | ||
addMeasurement({ | ||
...pick(measurement, ['tag', 'params']), | ||
value: Date.now() - measurement.start | ||
}); | ||
delete measurements[measurementId]; | ||
@@ -93,0 +101,0 @@ } |
'use strict'; | ||
const sinon = require('sinon'); | ||
const chai = require('chai'); | ||
chai.use(require('sinon-chai')); | ||
const expect = chai.expect; | ||
afterEach(() => sinon.restore()); | ||
const metricLoggerFactory = require('./metric-logger'); | ||
const Logger = require('@emartech/json-logger').Logger; | ||
const fakeTimers = require('@sinonjs/fake-timers'); | ||
jest.spyOn(Logger.prototype, 'info'); | ||
let metricLogger; | ||
beforeEach(() => (metricLogger = metricLoggerFactory())); | ||
let clock; | ||
beforeEach(() => { | ||
jest.resetAllMocks(); | ||
metricLogger = metricLoggerFactory(); | ||
clock = fakeTimers.install(); | ||
}); | ||
afterEach(() => clock.uninstall()); | ||
@@ -18,9 +20,6 @@ describe('metricLogger', () => { | ||
it('should log a single measurement at the next flush period', () => { | ||
const clock = sinon.useFakeTimers(); | ||
sinon.stub(Logger.prototype, 'info'); | ||
metricLogger.count('elfogyasztot-tap', 4); | ||
clock.tick(60 * 1000 + 1); | ||
expect(Logger.prototype.info).to.have.been.calledWith('elfogyasztot-tap', { | ||
expect(Logger.prototype.info).toBeCalledWith('elfogyasztot-tap', { | ||
min: 4, | ||
@@ -35,5 +34,2 @@ max: 4, | ||
it('should aggregate measurements withing a flush period', () => { | ||
const clock = sinon.useFakeTimers(); | ||
sinon.stub(Logger.prototype, 'info'); | ||
metricLogger.count('elfogyasztot-tap', 4); | ||
@@ -44,3 +40,3 @@ metricLogger.count('elfogyasztot-tap', 8); | ||
clock.tick(60 * 1000 + 1); | ||
expect(Logger.prototype.info).to.have.been.calledWith('elfogyasztot-tap', { | ||
expect(Logger.prototype.info).toBeCalledWith('elfogyasztot-tap', { | ||
min: 4, | ||
@@ -52,3 +48,3 @@ max: 8, | ||
}); | ||
expect(Logger.prototype.info).to.have.been.calledWith('kacsa-cucc', { | ||
expect(Logger.prototype.info).toBeCalledWith('kacsa-cucc', { | ||
min: 18, | ||
@@ -61,2 +57,42 @@ max: 18, | ||
}); | ||
it('should log counts for each combination of extra params provedid within a tag', () => { | ||
metricLogger.count('elfogyasztot-tap', 4, { emberke: 'bela' }); | ||
metricLogger.count('elfogyasztot-tap', 8, { emberke: 'bela' }); | ||
metricLogger.count('elfogyasztot-tap', 18, { emberke: 'jano' }); | ||
clock.tick(60 * 1000 + 1); | ||
expect(Logger.prototype.info).toBeCalledWith('elfogyasztot-tap', { | ||
min: 4, | ||
max: 8, | ||
count: 2, | ||
sum: 12, | ||
avarage: 6, | ||
emberke: 'bela' | ||
}); | ||
expect(Logger.prototype.info).toBeCalledWith('elfogyasztot-tap', { | ||
min: 18, | ||
max: 18, | ||
count: 1, | ||
sum: 18, | ||
avarage: 18, | ||
emberke: 'jano' | ||
}); | ||
}); | ||
it('should group logs with same tag and deeply equal params together', () => { | ||
metricLogger.count('elfogyasztot-tap', 4, { a: 1, b: 2 }); | ||
metricLogger.count('elfogyasztot-tap', 8, { b: 2, a: 1 }); | ||
clock.tick(60 * 1000 + 1); | ||
expect(Logger.prototype.info).toBeCalledWith('elfogyasztot-tap', { | ||
min: 4, | ||
max: 8, | ||
count: 2, | ||
sum: 12, | ||
avarage: 6, | ||
a: 1, | ||
b: 2 | ||
}); | ||
}); | ||
}); | ||
@@ -66,5 +102,2 @@ | ||
it('should log min, max, count and sum when log was called only once', () => { | ||
const clock = sinon.useFakeTimers(); | ||
sinon.stub(Logger.prototype, 'info'); | ||
const measurementId = metricLogger.start('utlegeles'); | ||
@@ -75,3 +108,3 @@ clock.tick(50); | ||
clock.tick(60 * 1000); | ||
expect(Logger.prototype.info).to.have.been.calledWith('utlegeles', { | ||
expect(Logger.prototype.info).toBeCalledWith('utlegeles', { | ||
min: 50, | ||
@@ -86,5 +119,2 @@ max: 50, | ||
it('should aggregate metrics for two measurements and log after a timeout', () => { | ||
const clock = sinon.useFakeTimers(); | ||
sinon.stub(Logger.prototype, 'info'); | ||
const measurement1Id = metricLogger.start('utlegeles'); | ||
@@ -98,3 +128,3 @@ const measurement2Id = metricLogger.start('utlegeles'); | ||
clock.tick(60 * 1000); | ||
expect(Logger.prototype.info).to.have.been.calledWith('utlegeles', { | ||
expect(Logger.prototype.info).toBeCalledWith('utlegeles', { | ||
min: 50, | ||
@@ -109,18 +139,13 @@ max: 70, | ||
it('should flush results at the closes minute:30', () => { | ||
const clock = sinon.useFakeTimers(10 * 1000); | ||
sinon.stub(Logger.prototype, 'info'); | ||
const measurementId = metricLogger.start('utlegeles'); | ||
metricLogger.stop(measurementId); | ||
clock.tick(19 * 1000 + 999); | ||
expect(Logger.prototype.info).not.to.have.been.called; | ||
clock.tick(29 * 1000 + 999); | ||
expect(Logger.prototype.info).not.toBeCalled(); | ||
clock.tick(1 * 1000); | ||
expect(Logger.prototype.info).to.have.been.calledWith('utlegeles'); | ||
expect(Logger.prototype.info).toBeCalledWith('utlegeles', expect.anything()); | ||
}); | ||
it('should flush at next minute:30 if we are after 30 in current minute', () => { | ||
const clock = sinon.useFakeTimers(45 * 1000); | ||
sinon.stub(Logger.prototype, 'info'); | ||
clock.tick(45 * 1000); | ||
const measurementId = metricLogger.start('utlegeles'); | ||
@@ -130,11 +155,8 @@ metricLogger.stop(measurementId); | ||
clock.tick(44 * 1000); | ||
expect(Logger.prototype.info).not.to.have.been.called; | ||
expect(Logger.prototype.info).not.toBeCalled(); | ||
clock.tick(1 * 1000); | ||
expect(Logger.prototype.info).to.have.been.calledWith('utlegeles'); | ||
expect(Logger.prototype.info).toBeCalledWith('utlegeles', expect.anything()); | ||
}); | ||
it('should periodically log measuerments', () => { | ||
const clock = sinon.useFakeTimers(0); | ||
sinon.stub(Logger.prototype, 'info'); | ||
const measurement1 = metricLogger.start('utlegeles'); | ||
@@ -147,3 +169,3 @@ const measurement2 = metricLogger.start('utlegeles'); | ||
clock.tick(21 * 1000); | ||
expect(Logger.prototype.info).to.have.been.calledWith('utlegeles', { | ||
expect(Logger.prototype.info).toBeCalledWith('utlegeles', { | ||
count: 1, | ||
@@ -159,3 +181,3 @@ max: 10000, | ||
clock.tick(60 * 1000); | ||
expect(Logger.prototype.info).to.have.been.calledWith('utlegeles', { | ||
expect(Logger.prototype.info).toBeCalledWith('utlegeles', { | ||
count: 2, | ||
@@ -169,7 +191,4 @@ max: 31000, | ||
it('should log measurements for multiple tags', () => { | ||
const clock = sinon.useFakeTimers(0); | ||
sinon.stub(Logger.prototype, 'info'); | ||
const measurement1 = metricLogger.start('utlegeles'); | ||
it('should log measurements for multiple tags and include extra parameters', () => { | ||
const measurement1 = metricLogger.start('utlegeles', { a: 21 }); | ||
const measurement2 = metricLogger.start('nyavogas'); | ||
@@ -182,3 +201,3 @@ clock.tick(10 * 1000); | ||
expect(Logger.prototype.info).to.have.been.calledWith('utlegeles', { | ||
expect(Logger.prototype.info).toBeCalledWith('utlegeles', { | ||
count: 1, | ||
@@ -188,5 +207,6 @@ max: 10000, | ||
sum: 10000, | ||
avarage: 10000 | ||
avarage: 10000, | ||
a: 21 | ||
}); | ||
expect(Logger.prototype.info).to.have.been.calledWith('nyavogas', { | ||
expect(Logger.prototype.info).toBeCalledWith('nyavogas', { | ||
count: 1, | ||
@@ -193,0 +213,0 @@ max: 15000, |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
11598
3
272
63
5
+ Addedjson-stable-stringify@^1.0.1
+ Addedlodash@^4.17.15
+ Addedcall-bind@1.0.7(transitive)
+ Addeddefine-data-property@1.1.4(transitive)
+ Addedes-define-property@1.0.0(transitive)
+ Addedes-errors@1.3.0(transitive)
+ Addedfunction-bind@1.1.2(transitive)
+ Addedget-intrinsic@1.2.4(transitive)
+ Addedgopd@1.0.1(transitive)
+ Addedhas-property-descriptors@1.0.2(transitive)
+ Addedhas-proto@1.0.3(transitive)
+ Addedhas-symbols@1.0.3(transitive)
+ Addedhasown@2.0.2(transitive)
+ Addedisarray@2.0.5(transitive)
+ Addedjson-stable-stringify@1.1.1(transitive)
+ Addedjsonify@0.0.1(transitive)
+ Addedlodash@4.17.21(transitive)
+ Addedobject-keys@1.1.1(transitive)
+ Addedset-function-length@1.2.2(transitive)