@emartech/aggregate-metric-logger 1.0.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/.eslintrc.js +24 -0
- package/.nvmrc +1 -0
- package/LICENSE.txt +7 -0
- package/README.md +80 -0
- package/package.json +44 -0
- package/repo-info.json +7 -0
- package/src/index.js +29 -0
- package/src/metric-logger.js +145 -0
- package/src/metric-logger.spec.js +288 -0
package/.eslintrc.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
env: {
|
|
5
|
+
es6: true,
|
|
6
|
+
browser: true,
|
|
7
|
+
node: true
|
|
8
|
+
},
|
|
9
|
+
parserOptions: {
|
|
10
|
+
ecmaVersion: 2018
|
|
11
|
+
},
|
|
12
|
+
extends: ['eslint:recommended', 'plugin:jest/recommended', 'emarsys'],
|
|
13
|
+
rules: {
|
|
14
|
+
'curly': ['error', 'multi-line']
|
|
15
|
+
},
|
|
16
|
+
overrides: [
|
|
17
|
+
{
|
|
18
|
+
files: ['*.spec.js'],
|
|
19
|
+
env: {
|
|
20
|
+
jest: true
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
};
|
package/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
v16.14.0
|
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2022 Emarsys
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# aggregate-metric-logger
|
|
2
|
+
aggregate metrics in memory and log them every minute
|
|
3
|
+
|
|
4
|
+
## Setup
|
|
5
|
+
|
|
6
|
+
Set `METRIC_LOGGER_ENABLED` env variable to `true` for aggregate-metric-logger to
|
|
7
|
+
start collecting and logging measuremnts.
|
|
8
|
+
|
|
9
|
+
To change the default `aggregate-metric-logger` namespace used for logs, set
|
|
10
|
+
`METRIC_LOGGER_NAMESPACE` to a custom value.
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
The package exposes similar logging methods as the `@emartech/json-logger` package:
|
|
15
|
+
`trace`, `debug`, `info`, `warn`, `error` and `fatal`.
|
|
16
|
+
|
|
17
|
+
When these are called, it will aggregate logs with the same parameters withing a minute, add
|
|
18
|
+
an extra `count` property to the logs and log them at the end of the minute with
|
|
19
|
+
`@emartech/json-logger`.
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
const metrictLogger = require('aggregate-metric-logger')
|
|
23
|
+
|
|
24
|
+
metricLogger.warn('etwas-went-wrong', { customer_id: 11 });
|
|
25
|
+
metricLogger.warn('etwas-went-wrong', { customer_id: 11 });
|
|
26
|
+
|
|
27
|
+
// will aggrete these to a single warning log
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Measure values
|
|
31
|
+
|
|
32
|
+
If you want aggregate measuremts about something you need to simply call the `measure` method
|
|
33
|
+
for each value:
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
const metrictLogger = require('aggregate-metric-logger')
|
|
37
|
+
|
|
38
|
+
metricLogger.measure('thing-to-measure', 14)
|
|
39
|
+
metricLogger.measure('thing-to-measure', 20)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Every minute metric logger will log `min`, `max`, `sum`, `count` and `average` aggregated metrics
|
|
43
|
+
for each measured tags. (Uses `@emartech/json-logger` behind the scenes for logging).
|
|
44
|
+
|
|
45
|
+
### Measuring durations
|
|
46
|
+
|
|
47
|
+
For duration measuremts, there are two convinience methods: `start`, and `stop`;
|
|
48
|
+
|
|
49
|
+
You start the measurement with `start`, the counted value for aggregation will be the duration
|
|
50
|
+
between this `start` and the matcing `stop` call;
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
function exampleIOHeavyFunction() {
|
|
54
|
+
const measurementId = metricLogger.start('tag-for-the-measuement');
|
|
55
|
+
const result = db.findItems();
|
|
56
|
+
metricLogger.stop(measurementId);
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Group by extra parameters
|
|
62
|
+
|
|
63
|
+
If you want to group by additional parameters inside a tag, you can pass down these extra
|
|
64
|
+
params in an object to `count` and `start` as the last optional argument:
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
metricLogger.count('thing-to-count', 14, { customer_id: 12 });
|
|
68
|
+
|
|
69
|
+
const measurementId = metricLogger.start(
|
|
70
|
+
'tag-for-the-measuement',
|
|
71
|
+
{ event_type: 'nyul', account: 3 }
|
|
72
|
+
);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
A separete log entry will be created for each seen value combinations seen in the extra params
|
|
76
|
+
and these extra params will appended to the log entry.
|
|
77
|
+
|
|
78
|
+
Be very carefull when using extra params, and only include information you really need.
|
|
79
|
+
If you include too many parameters, you could loose the advantage you gain by aggregating your logs
|
|
80
|
+
with this package.
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@emartech/aggregate-metric-logger",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "aggregate metrics in memory for logging",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "npm run lint && npm run test:once",
|
|
8
|
+
"lint": "eslint src/",
|
|
9
|
+
"test:once": "jest",
|
|
10
|
+
"test:watch": "jest --watch --bail",
|
|
11
|
+
"semantic-release": "CI=true semantic-release"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/emartech/aggregate-metric-logger.git"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"metric",
|
|
19
|
+
"logger",
|
|
20
|
+
"aggregate"
|
|
21
|
+
],
|
|
22
|
+
"author": "RTI team",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/emartech/aggregate-metric-logger/issues"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/emartech/aggregate-metric-logger#readme",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@emartech/json-logger": "^3.1.0",
|
|
30
|
+
"date-fns": "^2.3.0",
|
|
31
|
+
"json-stable-stringify": "^1.0.1",
|
|
32
|
+
"lodash": "^4.17.15",
|
|
33
|
+
"uuid": "8.3.2"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@sinonjs/fake-timers": "9.1.1",
|
|
37
|
+
"@types/jest": "27.4.1",
|
|
38
|
+
"eslint": "^8.12.0",
|
|
39
|
+
"eslint-config-emarsys": "^5.1.0",
|
|
40
|
+
"eslint-plugin-jest": "^26.1.3",
|
|
41
|
+
"jest": "27.5.1",
|
|
42
|
+
"semantic-release": "^19.0.2"
|
|
43
|
+
}
|
|
44
|
+
}
|
package/repo-info.json
ADDED
package/src/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const metricLoggerFactory = require('./metric-logger');
|
|
4
|
+
|
|
5
|
+
const enabled = process.env.METRIC_LOGGER_ENABLED === 'true';
|
|
6
|
+
const inProgressMeasurementWarningLimit =
|
|
7
|
+
parseInt(process.env.METRIC_LOGGER_IN_PROGRESS_MEASUREMENT_WARNING_LIMIT, 10) || 10000;
|
|
8
|
+
|
|
9
|
+
const exposedInstance = metricLoggerFactory({
|
|
10
|
+
enabled,
|
|
11
|
+
namespace: process.env.METRIC_LOGGER_NAMESPACE || 'aggregate-metric-logger',
|
|
12
|
+
inProgressMeasurementWarningLimit
|
|
13
|
+
});
|
|
14
|
+
const exposedFactoryInstanceHybrid = namespace => metricLoggerFactory({ enabled, namespace });
|
|
15
|
+
|
|
16
|
+
exposedFactoryInstanceHybrid.setThresholds = exposedInstance.setThresholds;
|
|
17
|
+
exposedFactoryInstanceHybrid.measure = exposedInstance.measure;
|
|
18
|
+
exposedFactoryInstanceHybrid.count = exposedInstance.count;
|
|
19
|
+
exposedFactoryInstanceHybrid.trace = exposedInstance.trace;
|
|
20
|
+
exposedFactoryInstanceHybrid.debug = exposedInstance.debug;
|
|
21
|
+
exposedFactoryInstanceHybrid.info = exposedInstance.info;
|
|
22
|
+
exposedFactoryInstanceHybrid.warn = exposedInstance.warn;
|
|
23
|
+
exposedFactoryInstanceHybrid.error = exposedInstance.error;
|
|
24
|
+
exposedFactoryInstanceHybrid.fatal = exposedInstance.fatal;
|
|
25
|
+
exposedFactoryInstanceHybrid.start = exposedInstance.start;
|
|
26
|
+
exposedFactoryInstanceHybrid.stop = exposedInstance.stop;
|
|
27
|
+
exposedFactoryInstanceHybrid.cancel = exposedInstance.cancel;
|
|
28
|
+
|
|
29
|
+
module.exports = exposedFactoryInstanceHybrid;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { v4: uuid } = require('uuid');
|
|
4
|
+
const loggerFactory = require('@emartech/json-logger');
|
|
5
|
+
const { getSeconds, startOfMinute, addSeconds } = require('date-fns');
|
|
6
|
+
const { omit, pick, fromPairs, toPairs } = require('lodash');
|
|
7
|
+
const stringify = require('json-stable-stringify');
|
|
8
|
+
const util = require('util');
|
|
9
|
+
|
|
10
|
+
module.exports = ({
|
|
11
|
+
enabled = true,
|
|
12
|
+
namespace = 'aggregate-metric-logger',
|
|
13
|
+
inProgressMeasurementWarningLimit = 10000
|
|
14
|
+
} = {}) => {
|
|
15
|
+
const logger = loggerFactory(namespace);
|
|
16
|
+
|
|
17
|
+
const thresholdsByTag = {};
|
|
18
|
+
let measurements = {};
|
|
19
|
+
let metrics = {};
|
|
20
|
+
let initialized = false;
|
|
21
|
+
|
|
22
|
+
const flush = () => {
|
|
23
|
+
logMetrics();
|
|
24
|
+
const inProgressMeasurementCount = Object.keys(measurements).length;
|
|
25
|
+
if (inProgressMeasurementCount > inProgressMeasurementWarningLimit) {
|
|
26
|
+
logger.warn('too-many-in-progress-metric-log-measurements', { count: inProgressMeasurementCount });
|
|
27
|
+
}
|
|
28
|
+
metrics = {};
|
|
29
|
+
setupNextFlush();
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const setupNextFlush = () => {
|
|
33
|
+
const secondsInCurrentMinute = getSeconds(new Date());
|
|
34
|
+
const flushOffset = secondsInCurrentMinute >= 30 ? 90 : 30;
|
|
35
|
+
const targetFlushtime = addSeconds(startOfMinute(new Date()), flushOffset);
|
|
36
|
+
const timeout = targetFlushtime - Date.now();
|
|
37
|
+
setTimeout(flush, timeout);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const incrementThresholdCount = (tag, value, countsSoFar) => {
|
|
41
|
+
const thresholds = thresholdsByTag[tag];
|
|
42
|
+
if (!thresholds) return countsSoFar;
|
|
43
|
+
|
|
44
|
+
return fromPairs(thresholds.map(threshold => {
|
|
45
|
+
const previousCount = countsSoFar[threshold] || 0;
|
|
46
|
+
const newCount = value > threshold ? previousCount + 1 : previousCount;
|
|
47
|
+
return [threshold, newCount];
|
|
48
|
+
}));
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const addMeasurement = ({ tag, value, params, method = 'info', countOnly = false }) => {
|
|
52
|
+
if (!enabled) return;
|
|
53
|
+
|
|
54
|
+
if (!initialized) {
|
|
55
|
+
setupNextFlush();
|
|
56
|
+
initialized = true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const key = `${tag}_${stringify(params)}`;
|
|
60
|
+
if (!metrics[key]) {
|
|
61
|
+
const metricForKey = { method, tag, ...params, count: 1 };
|
|
62
|
+
if (!countOnly) {
|
|
63
|
+
metricForKey.min = metricForKey.max = metricForKey.sum = metricForKey.average = value;
|
|
64
|
+
metricForKey.thresholdCounts = incrementThresholdCount(tag, value, {});
|
|
65
|
+
}
|
|
66
|
+
metrics[key] = metricForKey;
|
|
67
|
+
} else {
|
|
68
|
+
const metricsSoFar = metrics[key];
|
|
69
|
+
metrics[key].count = metricsSoFar.count + 1;
|
|
70
|
+
if (!countOnly) {
|
|
71
|
+
metrics[key].min = Math.min(metricsSoFar.min, value);
|
|
72
|
+
metrics[key].max = Math.max(metricsSoFar.max, value);
|
|
73
|
+
metrics[key].sum = metricsSoFar.sum + value;
|
|
74
|
+
metrics[key].average = metrics[key].sum / metrics[key].count;
|
|
75
|
+
metrics[key].thresholdCounts = incrementThresholdCount(
|
|
76
|
+
tag,
|
|
77
|
+
value,
|
|
78
|
+
metricsSoFar.thresholdCounts
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const logMetrics = () => {
|
|
85
|
+
Object.values(metrics).forEach(metrics => {
|
|
86
|
+
const params = {
|
|
87
|
+
is_metric: true,
|
|
88
|
+
...omit(metrics, ['tag', 'method', 'thresholdCounts']),
|
|
89
|
+
...fromPairs(
|
|
90
|
+
toPairs(metrics.thresholdCounts).map(([limit, count]) => [`above_${limit}`, count])
|
|
91
|
+
)
|
|
92
|
+
};
|
|
93
|
+
logger[metrics.method](metrics.tag, params);
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
setThresholds(tag, thresholds) {
|
|
99
|
+
thresholdsByTag[tag] = thresholds;
|
|
100
|
+
},
|
|
101
|
+
measure(tag, value, params = {}) {
|
|
102
|
+
addMeasurement({ tag, value, params });
|
|
103
|
+
},
|
|
104
|
+
count: util.deprecate(
|
|
105
|
+
(tag, value, params = {}) => addMeasurement({ tag, value, params }),
|
|
106
|
+
'count method is deprecated, use measure instead'
|
|
107
|
+
),
|
|
108
|
+
trace(tag, params = {}) {
|
|
109
|
+
addMeasurement({ method: 'trace', tag, params, value: 1, countOnly: true });
|
|
110
|
+
},
|
|
111
|
+
debug(tag, params = {}) {
|
|
112
|
+
addMeasurement({ method: 'debug', tag, params, value: 1, countOnly: true });
|
|
113
|
+
},
|
|
114
|
+
info(tag, params = {}) {
|
|
115
|
+
addMeasurement({ method: 'info', tag, params, value: 1, countOnly: true });
|
|
116
|
+
},
|
|
117
|
+
warn(tag, params = {}) {
|
|
118
|
+
addMeasurement({ method: 'warn', tag, params, value: 1, countOnly: true });
|
|
119
|
+
},
|
|
120
|
+
error(tag, params = {}) {
|
|
121
|
+
addMeasurement({ method: 'error', tag, params, value: 1, countOnly: true });
|
|
122
|
+
},
|
|
123
|
+
fatal(tag, params = {}) {
|
|
124
|
+
addMeasurement({ method: 'fatal', tag, params, value: 1, countOnly: true });
|
|
125
|
+
},
|
|
126
|
+
start(tag, params = {}) {
|
|
127
|
+
const id = uuid();
|
|
128
|
+
measurements[id] = { tag, params, start: Date.now() };
|
|
129
|
+
return id;
|
|
130
|
+
},
|
|
131
|
+
stop(measurementId) {
|
|
132
|
+
const measurement = measurements[measurementId];
|
|
133
|
+
if (!measurement) return;
|
|
134
|
+
|
|
135
|
+
addMeasurement({
|
|
136
|
+
...pick(measurement, ['tag', 'params']),
|
|
137
|
+
value: Date.now() - measurement.start
|
|
138
|
+
});
|
|
139
|
+
delete measurements[measurementId];
|
|
140
|
+
},
|
|
141
|
+
cancel(measurementId) {
|
|
142
|
+
delete measurements[measurementId];
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
};
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const metricLoggerFactory = require('./metric-logger');
|
|
4
|
+
const Logger = require('@emartech/json-logger').Logger;
|
|
5
|
+
const fakeTimers = require('@sinonjs/fake-timers');
|
|
6
|
+
|
|
7
|
+
jest.spyOn(Logger.prototype, 'info');
|
|
8
|
+
let metricLogger;
|
|
9
|
+
let clock;
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
jest.resetAllMocks();
|
|
12
|
+
metricLogger = metricLoggerFactory();
|
|
13
|
+
clock = fakeTimers.install();
|
|
14
|
+
});
|
|
15
|
+
afterEach(() => clock.uninstall());
|
|
16
|
+
|
|
17
|
+
describe('metricLogger', () => {
|
|
18
|
+
describe('measure', () => {
|
|
19
|
+
it('should log a single measurement at the next flush period', () => {
|
|
20
|
+
metricLogger.measure('elfogyasztot-tap', 4);
|
|
21
|
+
|
|
22
|
+
clock.tick(60 * 1000 + 1);
|
|
23
|
+
expect(Logger.prototype.info).toBeCalledWith('elfogyasztot-tap', {
|
|
24
|
+
min: 4,
|
|
25
|
+
max: 4,
|
|
26
|
+
count: 1,
|
|
27
|
+
sum: 4,
|
|
28
|
+
average: 4,
|
|
29
|
+
is_metric: true
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should aggregate measurements withing a flush period', () => {
|
|
34
|
+
metricLogger.measure('elfogyasztot-tap', 4);
|
|
35
|
+
metricLogger.measure('elfogyasztot-tap', 8);
|
|
36
|
+
metricLogger.measure('kacsa-cucc', 18);
|
|
37
|
+
|
|
38
|
+
clock.tick(60 * 1000 + 1);
|
|
39
|
+
expect(Logger.prototype.info).toBeCalledWith('elfogyasztot-tap', {
|
|
40
|
+
min: 4,
|
|
41
|
+
max: 8,
|
|
42
|
+
count: 2,
|
|
43
|
+
sum: 12,
|
|
44
|
+
average: 6,
|
|
45
|
+
is_metric: true
|
|
46
|
+
});
|
|
47
|
+
expect(Logger.prototype.info).toBeCalledWith('kacsa-cucc', {
|
|
48
|
+
min: 18,
|
|
49
|
+
max: 18,
|
|
50
|
+
count: 1,
|
|
51
|
+
sum: 18,
|
|
52
|
+
average: 18,
|
|
53
|
+
is_metric: true
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should log counts for each combination of extra params provedid within a tag', () => {
|
|
58
|
+
metricLogger.measure('elfogyasztot-tap', 4, { emberke: 'bela' });
|
|
59
|
+
metricLogger.measure('elfogyasztot-tap', 8, { emberke: 'bela' });
|
|
60
|
+
metricLogger.measure('elfogyasztot-tap', 18, { emberke: 'jano' });
|
|
61
|
+
|
|
62
|
+
clock.tick(60 * 1000 + 1);
|
|
63
|
+
expect(Logger.prototype.info).toBeCalledWith('elfogyasztot-tap', {
|
|
64
|
+
min: 4,
|
|
65
|
+
max: 8,
|
|
66
|
+
count: 2,
|
|
67
|
+
sum: 12,
|
|
68
|
+
average: 6,
|
|
69
|
+
emberke: 'bela',
|
|
70
|
+
is_metric: true
|
|
71
|
+
});
|
|
72
|
+
expect(Logger.prototype.info).toBeCalledWith('elfogyasztot-tap', {
|
|
73
|
+
min: 18,
|
|
74
|
+
max: 18,
|
|
75
|
+
count: 1,
|
|
76
|
+
sum: 18,
|
|
77
|
+
average: 18,
|
|
78
|
+
emberke: 'jano',
|
|
79
|
+
is_metric: true
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should group logs with same tag and deeply equal params together', () => {
|
|
84
|
+
metricLogger.measure('elfogyasztot-tap', 4, { a: 1, b: 2 });
|
|
85
|
+
metricLogger.measure('elfogyasztot-tap', 8, { b: 2, a: 1 });
|
|
86
|
+
|
|
87
|
+
clock.tick(60 * 1000 + 1);
|
|
88
|
+
expect(Logger.prototype.info).toBeCalledWith('elfogyasztot-tap', {
|
|
89
|
+
min: 4,
|
|
90
|
+
max: 8,
|
|
91
|
+
count: 2,
|
|
92
|
+
sum: 12,
|
|
93
|
+
average: 6,
|
|
94
|
+
a: 1,
|
|
95
|
+
b: 2,
|
|
96
|
+
is_metric: true
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should count event distribution within specified thresholds', () => {
|
|
101
|
+
metricLogger.setThresholds('etwas', [5, 10, 20, 1000]);
|
|
102
|
+
|
|
103
|
+
metricLogger.measure('etwas', 2);
|
|
104
|
+
metricLogger.measure('etwas', 3);
|
|
105
|
+
metricLogger.measure('etwas', 4);
|
|
106
|
+
metricLogger.measure('etwas', 6);
|
|
107
|
+
metricLogger.measure('etwas', 11);
|
|
108
|
+
metricLogger.measure('etwas', 12);
|
|
109
|
+
metricLogger.measure('etwas', 816);
|
|
110
|
+
|
|
111
|
+
clock.tick(60 * 1000 + 1);
|
|
112
|
+
expect(Logger.prototype.info).toBeCalledWith('etwas', {
|
|
113
|
+
min: 2,
|
|
114
|
+
max: 816,
|
|
115
|
+
count: 7,
|
|
116
|
+
sum: 854,
|
|
117
|
+
average: 122,
|
|
118
|
+
above_5: 4,
|
|
119
|
+
above_10: 3,
|
|
120
|
+
above_20: 1,
|
|
121
|
+
above_1000: 0,
|
|
122
|
+
is_metric: true
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('start/stop', () => {
|
|
128
|
+
it('should log min, max, count and sum when log was called only once', () => {
|
|
129
|
+
const measurementId = metricLogger.start('utlegeles');
|
|
130
|
+
clock.tick(50);
|
|
131
|
+
metricLogger.stop(measurementId);
|
|
132
|
+
|
|
133
|
+
clock.tick(60 * 1000);
|
|
134
|
+
expect(Logger.prototype.info).toBeCalledWith('utlegeles', {
|
|
135
|
+
min: 50,
|
|
136
|
+
max: 50,
|
|
137
|
+
count: 1,
|
|
138
|
+
sum: 50,
|
|
139
|
+
average: 50,
|
|
140
|
+
is_metric: true
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should aggregate metrics for two measurements and log after a timeout', () => {
|
|
145
|
+
const measurement1Id = metricLogger.start('utlegeles');
|
|
146
|
+
const measurement2Id = metricLogger.start('utlegeles');
|
|
147
|
+
clock.tick(50);
|
|
148
|
+
metricLogger.stop(measurement1Id);
|
|
149
|
+
clock.tick(20);
|
|
150
|
+
metricLogger.stop(measurement2Id);
|
|
151
|
+
|
|
152
|
+
clock.tick(60 * 1000);
|
|
153
|
+
expect(Logger.prototype.info).toBeCalledWith('utlegeles', {
|
|
154
|
+
min: 50,
|
|
155
|
+
max: 70,
|
|
156
|
+
sum: 120,
|
|
157
|
+
count: 2,
|
|
158
|
+
average: 60,
|
|
159
|
+
is_metric: true
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should not count a measurment if it is cancelled with cancel', () => {
|
|
164
|
+
const measurement1Id = metricLogger.start('utlegeles');
|
|
165
|
+
const measurement2Id = metricLogger.start('utlegeles');
|
|
166
|
+
clock.tick(50);
|
|
167
|
+
metricLogger.stop(measurement1Id);
|
|
168
|
+
clock.tick(20);
|
|
169
|
+
metricLogger.cancel(measurement2Id);
|
|
170
|
+
metricLogger.stop(measurement2Id);
|
|
171
|
+
|
|
172
|
+
clock.tick(60 * 1000);
|
|
173
|
+
expect(Logger.prototype.info).toBeCalledWith('utlegeles', expect.objectContaining({
|
|
174
|
+
count: 1
|
|
175
|
+
}));
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('should flush results at the closes minute:30', () => {
|
|
179
|
+
const measurementId = metricLogger.start('utlegeles');
|
|
180
|
+
metricLogger.stop(measurementId);
|
|
181
|
+
|
|
182
|
+
clock.tick(29 * 1000 + 999);
|
|
183
|
+
expect(Logger.prototype.info).not.toBeCalled();
|
|
184
|
+
clock.tick(1 * 1000);
|
|
185
|
+
expect(Logger.prototype.info).toBeCalledWith('utlegeles', expect.anything());
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should flush at next minute:30 if we are after 30 in current minute', () => {
|
|
189
|
+
clock.tick(45 * 1000);
|
|
190
|
+
const measurementId = metricLogger.start('utlegeles');
|
|
191
|
+
metricLogger.stop(measurementId);
|
|
192
|
+
|
|
193
|
+
clock.tick(44 * 1000);
|
|
194
|
+
expect(Logger.prototype.info).not.toBeCalled();
|
|
195
|
+
clock.tick(1 * 1000);
|
|
196
|
+
expect(Logger.prototype.info).toBeCalledWith('utlegeles', expect.anything());
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('should periodically log measuerments', () => {
|
|
200
|
+
const measurement1 = metricLogger.start('utlegeles');
|
|
201
|
+
const measurement2 = metricLogger.start('utlegeles');
|
|
202
|
+
clock.tick(10 * 1000);
|
|
203
|
+
const measurement3 = metricLogger.start('utlegeles');
|
|
204
|
+
metricLogger.stop(measurement1);
|
|
205
|
+
|
|
206
|
+
clock.tick(21 * 1000);
|
|
207
|
+
expect(Logger.prototype.info).toBeCalledWith('utlegeles', {
|
|
208
|
+
count: 1,
|
|
209
|
+
max: 10000,
|
|
210
|
+
min: 10000,
|
|
211
|
+
sum: 10000,
|
|
212
|
+
average: 10000,
|
|
213
|
+
is_metric: true
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
metricLogger.stop(measurement2);
|
|
217
|
+
metricLogger.stop(measurement3);
|
|
218
|
+
clock.tick(60 * 1000);
|
|
219
|
+
expect(Logger.prototype.info).toBeCalledWith('utlegeles', {
|
|
220
|
+
count: 2,
|
|
221
|
+
max: 31000,
|
|
222
|
+
min: 21000,
|
|
223
|
+
sum: 52000,
|
|
224
|
+
average: 26000,
|
|
225
|
+
is_metric: true
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('should log measurements for multiple tags and include extra parameters', () => {
|
|
230
|
+
const measurement1 = metricLogger.start('utlegeles', { a: 21 });
|
|
231
|
+
const measurement2 = metricLogger.start('nyavogas');
|
|
232
|
+
clock.tick(10 * 1000);
|
|
233
|
+
metricLogger.stop(measurement1);
|
|
234
|
+
clock.tick(5 * 1000);
|
|
235
|
+
metricLogger.stop(measurement2);
|
|
236
|
+
clock.tick(15 * 1000);
|
|
237
|
+
|
|
238
|
+
expect(Logger.prototype.info).toBeCalledWith('utlegeles', {
|
|
239
|
+
count: 1,
|
|
240
|
+
max: 10000,
|
|
241
|
+
min: 10000,
|
|
242
|
+
sum: 10000,
|
|
243
|
+
average: 10000,
|
|
244
|
+
a: 21,
|
|
245
|
+
is_metric: true
|
|
246
|
+
});
|
|
247
|
+
expect(Logger.prototype.info).toBeCalledWith('nyavogas', {
|
|
248
|
+
count: 1,
|
|
249
|
+
max: 15000,
|
|
250
|
+
min: 15000,
|
|
251
|
+
sum: 15000,
|
|
252
|
+
average: 15000,
|
|
253
|
+
is_metric: true
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('should log warning if there are too many measurements in progress', () => {
|
|
258
|
+
jest.spyOn(Logger.prototype, 'warn');
|
|
259
|
+
const metricLogger = metricLoggerFactory({ inProgressMeasurementWarningLimit: 1 });
|
|
260
|
+
|
|
261
|
+
metricLogger.measure('kacsa');
|
|
262
|
+
metricLogger.start('nyavogas');
|
|
263
|
+
metricLogger.start('nyavogas');
|
|
264
|
+
clock.tick(60 * 1000);
|
|
265
|
+
|
|
266
|
+
expect(Logger.prototype.warn).toBeCalledWith('too-many-in-progress-metric-log-measurements', { count: 2 });
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
describe('simple count one log methods', () => {
|
|
271
|
+
it.each(['trace', 'debug', 'info', 'warn', 'error', 'fatal'])(
|
|
272
|
+
'should log a only the count when %s method is called',
|
|
273
|
+
method => {
|
|
274
|
+
jest.spyOn(Logger.prototype, method);
|
|
275
|
+
|
|
276
|
+
metricLogger[method]('elfogyasztot-tap', { alma: 2 });
|
|
277
|
+
metricLogger[method]('elfogyasztot-tap', { alma: 2 });
|
|
278
|
+
|
|
279
|
+
clock.tick(60 * 1000 + 11500);
|
|
280
|
+
expect(Logger.prototype[method]).toBeCalledWith('elfogyasztot-tap', {
|
|
281
|
+
count: 2,
|
|
282
|
+
alma: 2,
|
|
283
|
+
is_metric: true
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
);
|
|
287
|
+
});
|
|
288
|
+
});
|