@loopback/metrics 0.5.2 → 0.7.1
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/CHANGELOG.md +51 -0
- package/README.md +39 -8
- package/dist/controllers/metrics.controller.js +9 -8
- package/dist/controllers/metrics.controller.js.map +1 -1
- package/dist/interceptors/metrics.interceptor.js +59 -18
- package/dist/interceptors/metrics.interceptor.js.map +1 -1
- package/dist/metrics.component.js +4 -0
- package/dist/metrics.component.js.map +1 -1
- package/dist/observers/metrics.push.observer.js +11 -1
- package/dist/observers/metrics.push.observer.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.js.map +1 -1
- package/package.json +14 -14
- package/src/controllers/metrics.controller.ts +8 -7
- package/src/interceptors/metrics.interceptor.ts +86 -23
- package/src/metrics.component.ts +4 -0
- package/src/observers/metrics.push.observer.ts +9 -1
- package/src/types.ts +9 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,57 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [0.7.1](https://github.com/strongloop/loopback-next/compare/@loopback/metrics@0.7.0...@loopback/metrics@0.7.1) (2021-04-06)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @loopback/metrics
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# [0.7.0](https://github.com/strongloop/loopback-next/compare/@loopback/metrics@0.6.1...@loopback/metrics@0.7.0) (2021-03-18)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
* **metrics:** update to be compatible with prom-client 13.x ([967f018](https://github.com/strongloop/loopback-next/commit/967f018744a4eb07c5a6c2c827bea956e0488f7b))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Features
|
|
23
|
+
|
|
24
|
+
* update package-lock.json to v2 consistently ([dfc3fbd](https://github.com/strongloop/loopback-next/commit/dfc3fbdae0c9ca9f34c64154a471bef22d5ac6b7))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
## [0.6.1](https://github.com/strongloop/loopback-next/compare/@loopback/metrics@0.6.0...@loopback/metrics@0.6.1) (2021-02-09)
|
|
31
|
+
|
|
32
|
+
**Note:** Version bump only for package @loopback/metrics
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# [0.6.0](https://github.com/strongloop/loopback-next/compare/@loopback/metrics@0.5.2...@loopback/metrics@0.6.0) (2021-01-21)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
### Bug Fixes
|
|
42
|
+
|
|
43
|
+
* **metrics:** fix error thrown by interceptor if invoked by proxy ([2fd2da2](https://github.com/strongloop/loopback-next/commit/2fd2da2a5664651675a7510910a674706d04d5f8))
|
|
44
|
+
* **metrics:** use path pattern instead of raw path in path labels ([80a07bc](https://github.com/strongloop/loopback-next/commit/80a07bcb624fd60a72b7537d285723de3a7c04f8))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
### Features
|
|
48
|
+
|
|
49
|
+
* **metrics:** add new Pushgateway options ([6d73fff](https://github.com/strongloop/loopback-next/commit/6d73fff0e19eb1d5646878d77c38463422607c22))
|
|
50
|
+
* **metrics:** add option to configure static default labels ([8bce9e8](https://github.com/strongloop/loopback-next/commit/8bce9e81c916051633486dff2f4aa1af643ca15c))
|
|
51
|
+
* **metrics:** additional method invocation labels ([cb3b9a5](https://github.com/strongloop/loopback-next/commit/cb3b9a58683a5cd9c707e77b6896eb5bb4de4db2))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
6
57
|
## [0.5.2](https://github.com/strongloop/loopback-next/compare/@loopback/metrics@0.5.1...@loopback/metrics@0.5.2) (2020-12-07)
|
|
7
58
|
|
|
8
59
|
**Note:** Version bump only for package @loopback/metrics
|
package/README.md
CHANGED
|
@@ -44,9 +44,16 @@ this.configure(MetricsBindings.COMPONENT).to({
|
|
|
44
44
|
defaultMetrics: {
|
|
45
45
|
timeout: 5000,
|
|
46
46
|
},
|
|
47
|
+
defaultLabels: {
|
|
48
|
+
service: 'api',
|
|
49
|
+
version: '1.0.0',
|
|
50
|
+
},
|
|
47
51
|
});
|
|
48
52
|
```
|
|
49
53
|
|
|
54
|
+
{% include note.html content="this.configure() must be called before
|
|
55
|
+
this.component() to take effect." %}
|
|
56
|
+
|
|
50
57
|
It also has to be noted, that by default the OpenAPI spec is disabled and
|
|
51
58
|
therefore the metrics endpoint will not be visible in the API explorer. The spec
|
|
52
59
|
can be enabled by setting `openApiSpec` to `true`.
|
|
@@ -70,12 +77,12 @@ There are three types of metrics being collected by this component:
|
|
|
70
77
|
|
|
71
78
|
Prometheus supports two modes to collect metrics:
|
|
72
79
|
|
|
73
|
-
- pull - scraping from metrics http endpoint exposed by the system being
|
|
74
|
-
monitored
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
https://prometheus.io/docs/
|
|
80
|
+
- **pull** - scraping from metrics http endpoint exposed by the system being
|
|
81
|
+
monitored. This is the usual mode of operation. See
|
|
82
|
+
[Why do you pull rather than push?](https://prometheus.io/docs/introduction/faq/#why-do-you-pull-rather-than-push)
|
|
83
|
+
- **push** - pushing metrics from the system being monitored to a push gateway.
|
|
84
|
+
Generally used for ephemeral jobs - see
|
|
85
|
+
[When to use the Pushgateway](https://prometheus.io/docs/practices/pushing/)
|
|
79
86
|
|
|
80
87
|
## Try it out
|
|
81
88
|
|
|
@@ -172,13 +179,37 @@ nodejs_heap_space_size_available_bytes{space="new_large_object"} 1047952 1564508
|
|
|
172
179
|
nodejs_version_info{version="v12.4.0",major="12",minor="4",patch="0"} 1
|
|
173
180
|
# HELP loopback_invocation_duration_seconds method invocation
|
|
174
181
|
# TYPE loopback_invocation_duration_seconds gauge
|
|
182
|
+
loopback_invocation_duration_seconds{targetName="MockController.prototype.success",method="GET",path="/success",statusCode="204"} 0.0002056
|
|
175
183
|
# HELP loopback_invocation_duration_histogram method invocation histogram
|
|
176
184
|
# TYPE loopback_invocation_duration_histogram histogram
|
|
177
|
-
|
|
185
|
+
loopback_invocation_duration_histogram_bucket{le="0.005",targetName="MockController.prototype.success",method="GET",path="/success",statusCode="204"} 1
|
|
186
|
+
loopback_invocation_duration_histogram_bucket{le="0.01",targetName="MockController.prototype.success",method="GET",path="/success",statusCode="204"} 1
|
|
187
|
+
loopback_invocation_duration_histogram_bucket{le="0.025",targetName="MockController.prototype.success",method="GET",path="/success",statusCode="204"} 1
|
|
188
|
+
loopback_invocation_duration_histogram_bucket{le="0.05",targetName="MockController.prototype.success",method="GET",path="/success",statusCode="204"} 1
|
|
189
|
+
loopback_invocation_duration_histogram_bucket{le="0.1",targetName="MockController.prototype.success",method="GET",path="/success",statusCode="204"} 1
|
|
190
|
+
loopback_invocation_duration_histogram_bucket{le="0.25",targetName="MockController.prototype.success",method="GET",path="/success",statusCode="204"} 1
|
|
191
|
+
loopback_invocation_duration_histogram_bucket{le="0.5",targetName="MockController.prototype.success",method="GET",path="/success",statusCode="204"} 1
|
|
192
|
+
loopback_invocation_duration_histogram_bucket{le="1",targetName="MockController.prototype.success",method="GET",path="/success",statusCode="204"} 1
|
|
193
|
+
loopback_invocation_duration_histogram_bucket{le="2.5",targetName="MockController.prototype.success",method="GET",path="/success",statusCode="204"} 1
|
|
194
|
+
loopback_invocation_duration_histogram_bucket{le="5",targetName="MockController.prototype.success",method="GET",path="/success",statusCode="204"} 1
|
|
195
|
+
loopback_invocation_duration_histogram_bucket{le="10",targetName="MockController.prototype.success",method="GET",path="/success",statusCode="204"} 1
|
|
196
|
+
loopback_invocation_duration_histogram_bucket{le="+Inf",targetName="MockController.prototype.success",method="GET",path="/success",statusCode="204"} 1
|
|
197
|
+
loopback_invocation_duration_histogram_sum{targetName="MockController.prototype.success",method="GET",path="/success",statusCode="204"} 0.0002112
|
|
198
|
+
loopback_invocation_duration_histogram_count{targetName="MockController.prototype.success",method="GET",path="/success",statusCode="204"} 1
|
|
199
|
+
# HELP loopback_invocation_total method invocation count
|
|
178
200
|
# TYPE loopback_invocation_total counter
|
|
179
|
-
loopback_invocation_total 1
|
|
201
|
+
loopback_invocation_total{targetName="MockController.prototype.success",method="GET",path="/success",statusCode="204"} 1
|
|
180
202
|
# HELP loopback_invocation_duration_summary method invocation summary
|
|
181
203
|
# TYPE loopback_invocation_duration_summary summary
|
|
204
|
+
loopback_invocation_duration_summary{quantile="0.01",targetName="MockController.prototype.success",method="GET",path="/success",statusCode="204"} 0.0002363
|
|
205
|
+
loopback_invocation_duration_summary{quantile="0.05",targetName="MockController.prototype.success",method="GET",path="/success",statusCode="204"} 0.0002363
|
|
206
|
+
loopback_invocation_duration_summary{quantile="0.5",targetName="MockController.prototype.success",method="GET",path="/success",statusCode="204"} 0.0002363
|
|
207
|
+
loopback_invocation_duration_summary{quantile="0.9",targetName="MockController.prototype.success",method="GET",path="/success",statusCode="204"} 0.0002363
|
|
208
|
+
loopback_invocation_duration_summary{quantile="0.95",targetName="MockController.prototype.success",method="GET",path="/success",statusCode="204"} 0.0002363
|
|
209
|
+
loopback_invocation_duration_summary{quantile="0.99",targetName="MockController.prototype.success",method="GET",path="/success",statusCode="204"} 0.0002363
|
|
210
|
+
loopback_invocation_duration_summary{quantile="0.999",targetName="MockController.prototype.success",method="GET",path="/success",statusCode="204"} 0.0002363
|
|
211
|
+
loopback_invocation_duration_summary_sum{targetName="MockController.prototype.success",method="GET",path="/success",statusCode="204"} 0.0002363
|
|
212
|
+
loopback_invocation_duration_summary_count{targetName="MockController.prototype.success",method="GET",path="/success",statusCode="204"} 1
|
|
182
213
|
</pre>
|
|
183
214
|
|
|
184
215
|
</details>
|
|
@@ -13,7 +13,7 @@ const types_1 = require("../types");
|
|
|
13
13
|
/**
|
|
14
14
|
* OpenAPI definition of metrics response
|
|
15
15
|
*/
|
|
16
|
-
const
|
|
16
|
+
const METRICS_RESPONSE = {
|
|
17
17
|
description: 'Metrics Response',
|
|
18
18
|
content: {
|
|
19
19
|
'text/plain': {
|
|
@@ -26,30 +26,31 @@ const metricsResponse = {
|
|
|
26
26
|
/**
|
|
27
27
|
* OpenAPI spec for metrics endpoint
|
|
28
28
|
*/
|
|
29
|
-
const
|
|
29
|
+
const METRICS_SPEC = {
|
|
30
30
|
responses: {
|
|
31
|
-
'200':
|
|
31
|
+
'200': METRICS_RESPONSE,
|
|
32
32
|
},
|
|
33
33
|
};
|
|
34
34
|
/**
|
|
35
35
|
* OpenAPI spec to hide endpoints
|
|
36
36
|
*/
|
|
37
|
-
const
|
|
37
|
+
const HIDDEN_SPEC = {
|
|
38
38
|
responses: {},
|
|
39
39
|
'x-visibility': 'undocumented',
|
|
40
40
|
};
|
|
41
41
|
function metricsControllerFactory(options = types_1.DEFAULT_METRICS_OPTIONS) {
|
|
42
42
|
var _a, _b;
|
|
43
43
|
const basePath = (_b = (_a = options.endpoint) === null || _a === void 0 ? void 0 : _a.basePath) !== null && _b !== void 0 ? _b : '/metrics';
|
|
44
|
-
const spec = options.openApiSpec ?
|
|
44
|
+
const spec = options.openApiSpec ? METRICS_SPEC : HIDDEN_SPEC;
|
|
45
45
|
/**
|
|
46
46
|
* Controller for metrics endpoint
|
|
47
47
|
*/
|
|
48
48
|
let MetricsController = class MetricsController {
|
|
49
|
-
report(res) {
|
|
49
|
+
async report(res) {
|
|
50
50
|
// Set the content type from the register
|
|
51
51
|
res.contentType(prom_client_1.register.contentType);
|
|
52
|
-
|
|
52
|
+
const data = await prom_client_1.register.metrics();
|
|
53
|
+
res.send(data);
|
|
53
54
|
return res;
|
|
54
55
|
}
|
|
55
56
|
};
|
|
@@ -58,7 +59,7 @@ function metricsControllerFactory(options = types_1.DEFAULT_METRICS_OPTIONS) {
|
|
|
58
59
|
tslib_1.__param(0, core_1.inject(rest_1.RestBindings.Http.RESPONSE)),
|
|
59
60
|
tslib_1.__metadata("design:type", Function),
|
|
60
61
|
tslib_1.__metadata("design:paramtypes", [Object]),
|
|
61
|
-
tslib_1.__metadata("design:returntype",
|
|
62
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
62
63
|
], MetricsController.prototype, "report", null);
|
|
63
64
|
MetricsController = tslib_1.__decorate([
|
|
64
65
|
core_1.injectable({ scope: core_1.BindingScope.SINGLETON })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metrics.controller.js","sourceRoot":"","sources":["../../src/controllers/metrics.controller.ts"],"names":[],"mappings":";AAAA,iDAAiD;AACjD,iCAAiC;AACjC,+CAA+C;AAC/C,gEAAgE;;;;AAEhE,yCAA6E;AAC7E,yCAMwB;AACxB,6CAAqC;AACrC,oCAAiE;AAEjE;;GAEG;AACH,MAAM,
|
|
1
|
+
{"version":3,"file":"metrics.controller.js","sourceRoot":"","sources":["../../src/controllers/metrics.controller.ts"],"names":[],"mappings":";AAAA,iDAAiD;AACjD,iCAAiC;AACjC,+CAA+C;AAC/C,gEAAgE;;;;AAEhE,yCAA6E;AAC7E,yCAMwB;AACxB,6CAAqC;AACrC,oCAAiE;AAEjE;;GAEG;AACH,MAAM,gBAAgB,GAAmB;IACvC,WAAW,EAAE,kBAAkB;IAC/B,OAAO,EAAE;QACP,YAAY,EAAE;YACZ,MAAM,EAAE;gBACN,IAAI,EAAE,QAAQ;aACf;SACF;KACF;CACF,CAAC;AAEF;;GAEG;AACH,MAAM,YAAY,GAAoB;IACpC,SAAS,EAAE;QACT,KAAK,EAAE,gBAAgB;KACxB;CACF,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,GAAoB;IACnC,SAAS,EAAE,EAAE;IACb,cAAc,EAAE,cAAc;CAC/B,CAAC;AAEF,SAAgB,wBAAwB,CACtC,UAA0B,+BAAuB;;IAEjD,MAAM,QAAQ,GAAG,MAAA,MAAA,OAAO,CAAC,QAAQ,0CAAE,QAAQ,mCAAI,UAAU,CAAC;IAC1D,MAAM,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,WAAW,CAAC;IAE9D;;OAEG;IAEH,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;QAErB,KAAK,CAAC,MAAM,CAAqC,GAAa;YAC5D,yCAAyC;YACzC,GAAG,CAAC,WAAW,CAAC,sBAAQ,CAAC,WAAW,CAAC,CAAC;YACtC,MAAM,IAAI,GAAG,MAAM,sBAAQ,CAAC,OAAO,EAAE,CAAC;YACtC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACf,OAAO,GAAG,CAAC;QACb,CAAC;KACF,CAAA;IAPC;QADC,UAAG,CAAC,QAAQ,EAAE,IAAI,CAAC;QACN,mBAAA,aAAM,CAAC,mBAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;;;;mDAM/C;IARG,iBAAiB;QADtB,iBAAU,CAAC,EAAC,KAAK,EAAE,mBAAY,CAAC,SAAS,EAAC,CAAC;OACtC,iBAAiB,CAStB;IAED,OAAO,iBAAiB,CAAC;AAC3B,CAAC;AAtBD,4DAsBC"}
|
|
@@ -9,6 +9,7 @@ exports.MetricsInterceptor = void 0;
|
|
|
9
9
|
const tslib_1 = require("tslib");
|
|
10
10
|
const core_1 = require("@loopback/core");
|
|
11
11
|
const prom_client_1 = require("prom-client");
|
|
12
|
+
const labelNames = ['targetName', 'method', 'path', 'statusCode'];
|
|
12
13
|
/**
|
|
13
14
|
* This interceptor captures metrics for method invocations,
|
|
14
15
|
* excluding sequence actions and middleware executed before
|
|
@@ -26,22 +27,22 @@ let MetricsInterceptor = MetricsInterceptor_1 = class MetricsInterceptor {
|
|
|
26
27
|
this.gauge = new prom_client_1.Gauge({
|
|
27
28
|
name: 'loopback_invocation_duration_seconds',
|
|
28
29
|
help: 'method invocation',
|
|
29
|
-
labelNames
|
|
30
|
+
labelNames,
|
|
30
31
|
});
|
|
31
32
|
this.histogram = new prom_client_1.Histogram({
|
|
32
33
|
name: 'loopback_invocation_duration_histogram',
|
|
33
34
|
help: 'method invocation histogram',
|
|
34
|
-
labelNames
|
|
35
|
+
labelNames,
|
|
35
36
|
});
|
|
36
37
|
this.counter = new prom_client_1.Counter({
|
|
37
38
|
name: 'loopback_invocation_total',
|
|
38
|
-
help: 'method invocation
|
|
39
|
-
labelNames
|
|
39
|
+
help: 'method invocation count',
|
|
40
|
+
labelNames,
|
|
40
41
|
});
|
|
41
42
|
this.summary = new prom_client_1.Summary({
|
|
42
43
|
name: 'loopback_invocation_duration_summary',
|
|
43
44
|
help: 'method invocation summary',
|
|
44
|
-
labelNames
|
|
45
|
+
labelNames,
|
|
45
46
|
});
|
|
46
47
|
}
|
|
47
48
|
value() {
|
|
@@ -49,23 +50,37 @@ let MetricsInterceptor = MetricsInterceptor_1 = class MetricsInterceptor {
|
|
|
49
50
|
}
|
|
50
51
|
async intercept(invocationCtx, next) {
|
|
51
52
|
MetricsInterceptor_1.setup();
|
|
52
|
-
const
|
|
53
|
+
const { source, parent } = invocationCtx;
|
|
54
|
+
const labelValues = {
|
|
53
55
|
targetName: invocationCtx.targetName,
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
};
|
|
57
|
+
if (isRouteSource(source)) {
|
|
58
|
+
labelValues.method = getRequestMethod(source);
|
|
59
|
+
labelValues.path = getPathPattern(source);
|
|
60
|
+
}
|
|
61
|
+
const endGauge = MetricsInterceptor_1.gauge.startTimer();
|
|
62
|
+
const endHistogram = MetricsInterceptor_1.histogram.startTimer();
|
|
63
|
+
const endSummary = MetricsInterceptor_1.summary.startTimer();
|
|
61
64
|
try {
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
const result = await next();
|
|
66
|
+
if (isRouteSource(source)) {
|
|
67
|
+
labelValues.statusCode = getStatusCodeFromResponse(
|
|
68
|
+
// parent context will be request context if invocation source is route
|
|
69
|
+
parent.response, result);
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
if (isRouteSource(source)) {
|
|
75
|
+
labelValues.statusCode = getStatusCodeFromError(err);
|
|
76
|
+
}
|
|
77
|
+
throw err;
|
|
64
78
|
}
|
|
65
79
|
finally {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
80
|
+
MetricsInterceptor_1.counter.inc(labelValues);
|
|
81
|
+
endGauge(labelValues);
|
|
82
|
+
endHistogram(labelValues);
|
|
83
|
+
endSummary(labelValues);
|
|
69
84
|
}
|
|
70
85
|
}
|
|
71
86
|
};
|
|
@@ -74,4 +89,30 @@ MetricsInterceptor = MetricsInterceptor_1 = tslib_1.__decorate([
|
|
|
74
89
|
tslib_1.__metadata("design:paramtypes", [])
|
|
75
90
|
], MetricsInterceptor);
|
|
76
91
|
exports.MetricsInterceptor = MetricsInterceptor;
|
|
92
|
+
function getPathPattern(source) {
|
|
93
|
+
// make sure to use path pattern instead of raw path
|
|
94
|
+
// this is important since paths can contain unbounded sets of values
|
|
95
|
+
// such as IDs which would create a new time series for each unique value
|
|
96
|
+
return source.value.path;
|
|
97
|
+
}
|
|
98
|
+
function getRequestMethod(source) {
|
|
99
|
+
// request methods should be all-uppercase
|
|
100
|
+
return source.value.verb.toUpperCase();
|
|
101
|
+
}
|
|
102
|
+
function getStatusCodeFromResponse(response, result) {
|
|
103
|
+
// interceptors are invoked before result is written to response,
|
|
104
|
+
// the status code for 200 responses without a result should be 204
|
|
105
|
+
const noContent = response.statusCode === 200 && result === undefined;
|
|
106
|
+
return noContent ? 204 : response.statusCode;
|
|
107
|
+
}
|
|
108
|
+
function getStatusCodeFromError(err) {
|
|
109
|
+
var _a, _b;
|
|
110
|
+
// interceptors are invoked before error is written to response,
|
|
111
|
+
// it is required to retrieve the status code from the error
|
|
112
|
+
const notFound = err.code === 'ENTITY_NOT_FOUND';
|
|
113
|
+
return (_b = (_a = err.statusCode) !== null && _a !== void 0 ? _a : err.status) !== null && _b !== void 0 ? _b : (notFound ? 404 : 500);
|
|
114
|
+
}
|
|
115
|
+
function isRouteSource(source) {
|
|
116
|
+
return (source === null || source === void 0 ? void 0 : source.type) === 'route';
|
|
117
|
+
}
|
|
77
118
|
//# sourceMappingURL=metrics.interceptor.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metrics.interceptor.js","sourceRoot":"","sources":["../../src/interceptors/metrics.interceptor.ts"],"names":[],"mappings":";AAAA,sDAAsD;AACtD,iCAAiC;AACjC,+CAA+C;AAC/C,gEAAgE;;;;;AAEhE,
|
|
1
|
+
{"version":3,"file":"metrics.interceptor.js","sourceRoot":"","sources":["../../src/interceptors/metrics.interceptor.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,6 +7,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
7
7
|
exports.MetricsComponent = void 0;
|
|
8
8
|
const tslib_1 = require("tslib");
|
|
9
9
|
const core_1 = require("@loopback/core");
|
|
10
|
+
const prom_client_1 = require("prom-client");
|
|
10
11
|
const controllers_1 = require("./controllers");
|
|
11
12
|
const interceptors_1 = require("./interceptors");
|
|
12
13
|
const keys_1 = require("./keys");
|
|
@@ -32,6 +33,9 @@ let MetricsComponent = class MetricsComponent {
|
|
|
32
33
|
if (options.endpoint && !options.endpoint.disabled) {
|
|
33
34
|
this.application.controller(controllers_1.metricsControllerFactory(options));
|
|
34
35
|
}
|
|
36
|
+
if (options.defaultLabels) {
|
|
37
|
+
prom_client_1.register.setDefaultLabels(options.defaultLabels);
|
|
38
|
+
}
|
|
35
39
|
}
|
|
36
40
|
};
|
|
37
41
|
MetricsComponent = tslib_1.__decorate([
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metrics.component.js","sourceRoot":"","sources":["../src/metrics.component.ts"],"names":[],"mappings":";AAAA,iDAAiD;AACjD,iCAAiC;AACjC,+CAA+C;AAC/C,gEAAgE;;;;AAEhE,yCASwB;AACxB,+CAAuD;AACvD,iDAAkD;AAClD,iCAAuC;AACvC,2CAAiE;AACjE,mCAA+E;AAE/E;;GAEG;AAEH,IAAa,gBAAgB,GAA7B,MAAa,gBAAgB;IAC3B,YAEU,WAAwB,EAEhC,gBAA+B,EAAE;QAFzB,gBAAW,GAAX,WAAW,CAAa;QAIhC,MAAM,OAAO,GAAmB;YAC9B,GAAG,+BAAuB;YAC1B,GAAG,aAAa;SACjB,CAAC;QACF,IAAI,OAAO,CAAC,cAAc,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,QAAQ,EAAE;YAC9D,IAAI,CAAC,WAAW,CAAC,iBAAiB,CAAC,2BAAe,CAAC,CAAC;SACrD;QACD,IAAI,OAAO,CAAC,WAAW,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,QAAQ,EAAE;YACxD,IAAI,CAAC,WAAW,CAAC,iBAAiB,CAAC,+BAAmB,CAAC,CAAC;SACzD;QACD,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,6BAAsB,CAAC,iCAAkB,CAAC,CAAC,CAAC;QACjE,IAAI,OAAO,CAAC,QAAQ,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,EAAE;YAClD,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,sCAAwB,CAAC,OAAO,CAAC,CAAC,CAAC;SAChE;IACH,CAAC;CACF,CAAA;
|
|
1
|
+
{"version":3,"file":"metrics.component.js","sourceRoot":"","sources":["../src/metrics.component.ts"],"names":[],"mappings":";AAAA,iDAAiD;AACjD,iCAAiC;AACjC,+CAA+C;AAC/C,gEAAgE;;;;AAEhE,yCASwB;AACxB,6CAAqC;AACrC,+CAAuD;AACvD,iDAAkD;AAClD,iCAAuC;AACvC,2CAAiE;AACjE,mCAA+E;AAE/E;;GAEG;AAEH,IAAa,gBAAgB,GAA7B,MAAa,gBAAgB;IAC3B,YAEU,WAAwB,EAEhC,gBAA+B,EAAE;QAFzB,gBAAW,GAAX,WAAW,CAAa;QAIhC,MAAM,OAAO,GAAmB;YAC9B,GAAG,+BAAuB;YAC1B,GAAG,aAAa;SACjB,CAAC;QACF,IAAI,OAAO,CAAC,cAAc,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,QAAQ,EAAE;YAC9D,IAAI,CAAC,WAAW,CAAC,iBAAiB,CAAC,2BAAe,CAAC,CAAC;SACrD;QACD,IAAI,OAAO,CAAC,WAAW,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,QAAQ,EAAE;YACxD,IAAI,CAAC,WAAW,CAAC,iBAAiB,CAAC,+BAAmB,CAAC,CAAC;SACzD;QACD,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,6BAAsB,CAAC,iCAAkB,CAAC,CAAC,CAAC;QACjE,IAAI,OAAO,CAAC,QAAQ,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,EAAE;YAClD,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,sCAAwB,CAAC,OAAO,CAAC,CAAC,CAAC;SAChE;QACD,IAAI,OAAO,CAAC,aAAa,EAAE;YACzB,sBAAQ,CAAC,gBAAgB,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;SAClD;IACH,CAAC;CACF,CAAA;AAzBY,gBAAgB;IAD5B,iBAAU,CAAC,EAAC,IAAI,EAAE,EAAC,CAAC,kBAAW,CAAC,GAAG,CAAC,EAAE,sBAAe,CAAC,SAAS,EAAC,EAAC,CAAC;IAG9D,mBAAA,aAAM,CAAC,mBAAY,CAAC,oBAAoB,CAAC,CAAA;IAEzC,mBAAA,aAAM,EAAE,CAAA;6CADY,kBAAW;GAHvB,gBAAgB,CAyB5B;AAzBY,4CAAgB"}
|
|
@@ -24,7 +24,17 @@ let MetricsPushObserver = class MetricsPushObserver {
|
|
|
24
24
|
return;
|
|
25
25
|
this.gateway = new prom_client_1.Pushgateway(gwConfig.url);
|
|
26
26
|
this.interval = setInterval(() => {
|
|
27
|
-
|
|
27
|
+
var _a;
|
|
28
|
+
const params = {
|
|
29
|
+
jobName: (_a = gwConfig.jobName) !== null && _a !== void 0 ? _a : 'loopback',
|
|
30
|
+
groupings: gwConfig.groupingKey,
|
|
31
|
+
};
|
|
32
|
+
if (gwConfig.replaceAll) {
|
|
33
|
+
this.gateway.push(params, () => { });
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
this.gateway.pushAdd(params, () => { });
|
|
37
|
+
}
|
|
28
38
|
}, (_a = gwConfig.interval) !== null && _a !== void 0 ? _a : 5000);
|
|
29
39
|
}
|
|
30
40
|
stop() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metrics.push.observer.js","sourceRoot":"","sources":["../../src/observers/metrics.push.observer.ts"],"names":[],"mappings":";AAAA,iDAAiD;AACjD,iCAAiC;AACjC,+CAA+C;AAC/C,gEAAgE;;;;AAEhE,yCAAyD;AACzD,6CAAwC;AACxC,kCAAwC;AACxC,oCAAiE;AAEjE;;GAEG;AACH,IAAa,mBAAmB,GAAhC,MAAa,mBAAmB;IAI9B,YAEU,UAA0B,+BAAuB;QAAjD,YAAO,GAAP,OAAO,CAA0C;IACxD,CAAC;IAEJ,KAAK;;QACH,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC;QAC1C,IAAI,CAAC,QAAQ;YAAE,OAAO;QACtB,IAAI,CAAC,OAAO,GAAG,IAAI,yBAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC7C,IAAI,CAAC,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE
|
|
1
|
+
{"version":3,"file":"metrics.push.observer.js","sourceRoot":"","sources":["../../src/observers/metrics.push.observer.ts"],"names":[],"mappings":";AAAA,iDAAiD;AACjD,iCAAiC;AACjC,+CAA+C;AAC/C,gEAAgE;;;;AAEhE,yCAAyD;AACzD,6CAAwC;AACxC,kCAAwC;AACxC,oCAAiE;AAEjE;;GAEG;AACH,IAAa,mBAAmB,GAAhC,MAAa,mBAAmB;IAI9B,YAEU,UAA0B,+BAAuB;QAAjD,YAAO,GAAP,OAAO,CAA0C;IACxD,CAAC;IAEJ,KAAK;;QACH,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC;QAC1C,IAAI,CAAC,QAAQ;YAAE,OAAO;QACtB,IAAI,CAAC,OAAO,GAAG,IAAI,yBAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC7C,IAAI,CAAC,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE;;YAC/B,MAAM,MAAM,GAAG;gBACb,OAAO,EAAE,MAAA,QAAQ,CAAC,OAAO,mCAAI,UAAU;gBACvC,SAAS,EAAE,QAAQ,CAAC,WAAW;aAChC,CAAC;YACF,IAAI,QAAQ,CAAC,UAAU,EAAE;gBACvB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;aACrC;iBAAM;gBACL,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;aACxC;QACH,CAAC,EAAE,MAAA,QAAQ,CAAC,QAAQ,mCAAI,IAAI,CAAC,CAAC;IAChC,CAAC;IAED,IAAI;QACF,IAAI,IAAI,CAAC,QAAQ,EAAE;YACjB,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC7B,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;SAC3B;IACH,CAAC;CACF,CAAA;AAhCY,mBAAmB;IAK3B,mBAAA,aAAM,CAAC,EAAC,WAAW,EAAE,sBAAe,CAAC,SAAS,EAAC,CAAC,CAAA;;GALxC,mBAAmB,CAgC/B;AAhCY,kDAAmB"}
|
package/dist/types.d.ts
CHANGED
|
@@ -10,10 +10,18 @@ export interface MetricsOptions {
|
|
|
10
10
|
defaultMetrics?: {
|
|
11
11
|
disabled?: boolean;
|
|
12
12
|
} & DefaultMetricsCollectorConfiguration;
|
|
13
|
+
defaultLabels?: {
|
|
14
|
+
[labelName: string]: string;
|
|
15
|
+
};
|
|
13
16
|
pushGateway?: {
|
|
14
17
|
disabled?: boolean;
|
|
15
18
|
url: string;
|
|
16
19
|
interval?: number;
|
|
20
|
+
jobName?: string;
|
|
21
|
+
groupingKey?: {
|
|
22
|
+
[key: string]: string;
|
|
23
|
+
};
|
|
24
|
+
replaceAll?: boolean;
|
|
17
25
|
};
|
|
18
26
|
openApiSpec?: boolean;
|
|
19
27
|
}
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA,sDAAsD;AACtD,iCAAiC;AACjC,+CAA+C;AAC/C,gEAAgE;;;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA,sDAAsD;AACtD,iCAAiC;AACjC,+CAA+C;AAC/C,gEAAgE;;;AAwCnD,QAAA,uBAAuB,GAAmB;IACrD,QAAQ,EAAE;QACR,QAAQ,EAAE,UAAU;KACrB;IACD,cAAc,EAAE,EAAE;IAClB,WAAW,EAAE,KAAK;CACnB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loopback/metrics",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "An extension exposes metrics for Prometheus with LoopBack 4",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"engines": {
|
|
8
|
-
"node": "^10.16 || 12 || 14"
|
|
8
|
+
"node": "^10.16 || 12 || 14 || 15"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"build": "lb-tsc",
|
|
@@ -21,21 +21,21 @@
|
|
|
21
21
|
"access": "public"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
|
-
"@loopback/core": "^2.
|
|
25
|
-
"@loopback/rest": "^9.
|
|
24
|
+
"@loopback/core": "^2.15.1",
|
|
25
|
+
"@loopback/rest": "^9.2.1"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"prom-client": "^
|
|
29
|
-
"tslib": "^2.0
|
|
28
|
+
"prom-client": "^13.1.0",
|
|
29
|
+
"tslib": "^2.1.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@loopback/build": "^6.
|
|
33
|
-
"@loopback/core": "^2.
|
|
34
|
-
"@loopback/eslint-config": "^10.
|
|
35
|
-
"@loopback/rest": "^9.
|
|
36
|
-
"@loopback/testlab": "^3.
|
|
37
|
-
"@types/express": "^4.17.
|
|
38
|
-
"@types/node": "^10.17.
|
|
32
|
+
"@loopback/build": "^6.3.1",
|
|
33
|
+
"@loopback/core": "^2.15.1",
|
|
34
|
+
"@loopback/eslint-config": "^10.1.1",
|
|
35
|
+
"@loopback/rest": "^9.2.1",
|
|
36
|
+
"@loopback/testlab": "^3.3.1",
|
|
37
|
+
"@types/express": "^4.17.11",
|
|
38
|
+
"@types/node": "^10.17.56",
|
|
39
39
|
"express": "^4.17.1"
|
|
40
40
|
},
|
|
41
41
|
"keywords": [
|
|
@@ -56,5 +56,5 @@
|
|
|
56
56
|
"url": "https://github.com/strongloop/loopback-next.git",
|
|
57
57
|
"directory": "extensions/metrics"
|
|
58
58
|
},
|
|
59
|
-
"gitHead": "
|
|
59
|
+
"gitHead": "156ca0fcf8fa246ca380ab28b44b3708896be2b6"
|
|
60
60
|
}
|
|
@@ -17,7 +17,7 @@ import {DEFAULT_METRICS_OPTIONS, MetricsOptions} from '../types';
|
|
|
17
17
|
/**
|
|
18
18
|
* OpenAPI definition of metrics response
|
|
19
19
|
*/
|
|
20
|
-
const
|
|
20
|
+
const METRICS_RESPONSE: ResponseObject = {
|
|
21
21
|
description: 'Metrics Response',
|
|
22
22
|
content: {
|
|
23
23
|
'text/plain': {
|
|
@@ -31,16 +31,16 @@ const metricsResponse: ResponseObject = {
|
|
|
31
31
|
/**
|
|
32
32
|
* OpenAPI spec for metrics endpoint
|
|
33
33
|
*/
|
|
34
|
-
const
|
|
34
|
+
const METRICS_SPEC: OperationObject = {
|
|
35
35
|
responses: {
|
|
36
|
-
'200':
|
|
36
|
+
'200': METRICS_RESPONSE,
|
|
37
37
|
},
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* OpenAPI spec to hide endpoints
|
|
42
42
|
*/
|
|
43
|
-
const
|
|
43
|
+
const HIDDEN_SPEC: OperationObject = {
|
|
44
44
|
responses: {},
|
|
45
45
|
'x-visibility': 'undocumented',
|
|
46
46
|
};
|
|
@@ -49,7 +49,7 @@ export function metricsControllerFactory(
|
|
|
49
49
|
options: MetricsOptions = DEFAULT_METRICS_OPTIONS,
|
|
50
50
|
): Constructor<unknown> {
|
|
51
51
|
const basePath = options.endpoint?.basePath ?? '/metrics';
|
|
52
|
-
const spec = options.openApiSpec ?
|
|
52
|
+
const spec = options.openApiSpec ? METRICS_SPEC : HIDDEN_SPEC;
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
55
|
* Controller for metrics endpoint
|
|
@@ -57,10 +57,11 @@ export function metricsControllerFactory(
|
|
|
57
57
|
@injectable({scope: BindingScope.SINGLETON})
|
|
58
58
|
class MetricsController {
|
|
59
59
|
@get(basePath, spec)
|
|
60
|
-
report(@inject(RestBindings.Http.RESPONSE) res: Response) {
|
|
60
|
+
async report(@inject(RestBindings.Http.RESPONSE) res: Response) {
|
|
61
61
|
// Set the content type from the register
|
|
62
62
|
res.contentType(register.contentType);
|
|
63
|
-
|
|
63
|
+
const data = await register.metrics();
|
|
64
|
+
res.send(data);
|
|
64
65
|
return res;
|
|
65
66
|
}
|
|
66
67
|
}
|
|
@@ -9,10 +9,28 @@ import {
|
|
|
9
9
|
injectable,
|
|
10
10
|
Interceptor,
|
|
11
11
|
InvocationContext,
|
|
12
|
+
InvocationSource,
|
|
12
13
|
Provider,
|
|
13
14
|
ValueOrPromise,
|
|
14
15
|
} from '@loopback/core';
|
|
15
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
HttpErrors,
|
|
18
|
+
RequestContext,
|
|
19
|
+
Response,
|
|
20
|
+
RouteSource,
|
|
21
|
+
} from '@loopback/rest';
|
|
22
|
+
import {
|
|
23
|
+
Counter,
|
|
24
|
+
Gauge,
|
|
25
|
+
Histogram,
|
|
26
|
+
LabelValues,
|
|
27
|
+
register,
|
|
28
|
+
Summary,
|
|
29
|
+
} from 'prom-client';
|
|
30
|
+
|
|
31
|
+
type LabelNames = 'targetName' | 'method' | 'path' | 'statusCode';
|
|
32
|
+
|
|
33
|
+
const labelNames: LabelNames[] = ['targetName', 'method', 'path', 'statusCode'];
|
|
16
34
|
|
|
17
35
|
/**
|
|
18
36
|
* This interceptor captures metrics for method invocations,
|
|
@@ -22,13 +40,13 @@ import {Counter, Gauge, Histogram, register, Summary} from 'prom-client';
|
|
|
22
40
|
*/
|
|
23
41
|
@injectable(asGlobalInterceptor('metrics'), {scope: BindingScope.SINGLETON})
|
|
24
42
|
export class MetricsInterceptor implements Provider<Interceptor> {
|
|
25
|
-
private static gauge: Gauge<
|
|
43
|
+
private static gauge: Gauge<LabelNames>;
|
|
26
44
|
|
|
27
|
-
private static histogram: Histogram<
|
|
45
|
+
private static histogram: Histogram<LabelNames>;
|
|
28
46
|
|
|
29
|
-
private static counter: Counter<
|
|
47
|
+
private static counter: Counter<LabelNames>;
|
|
30
48
|
|
|
31
|
-
private static summary: Summary<
|
|
49
|
+
private static summary: Summary<LabelNames>;
|
|
32
50
|
|
|
33
51
|
private static setup() {
|
|
34
52
|
// Check if the gauge is registered
|
|
@@ -41,25 +59,25 @@ export class MetricsInterceptor implements Provider<Interceptor> {
|
|
|
41
59
|
this.gauge = new Gauge({
|
|
42
60
|
name: 'loopback_invocation_duration_seconds',
|
|
43
61
|
help: 'method invocation',
|
|
44
|
-
labelNames
|
|
62
|
+
labelNames,
|
|
45
63
|
});
|
|
46
64
|
|
|
47
65
|
this.histogram = new Histogram({
|
|
48
66
|
name: 'loopback_invocation_duration_histogram',
|
|
49
67
|
help: 'method invocation histogram',
|
|
50
|
-
labelNames
|
|
68
|
+
labelNames,
|
|
51
69
|
});
|
|
52
70
|
|
|
53
71
|
this.counter = new Counter({
|
|
54
72
|
name: 'loopback_invocation_total',
|
|
55
|
-
help: 'method invocation
|
|
56
|
-
labelNames
|
|
73
|
+
help: 'method invocation count',
|
|
74
|
+
labelNames,
|
|
57
75
|
});
|
|
58
76
|
|
|
59
77
|
this.summary = new Summary({
|
|
60
78
|
name: 'loopback_invocation_duration_summary',
|
|
61
79
|
help: 'method invocation summary',
|
|
62
|
-
labelNames
|
|
80
|
+
labelNames,
|
|
63
81
|
});
|
|
64
82
|
}
|
|
65
83
|
|
|
@@ -74,22 +92,67 @@ export class MetricsInterceptor implements Provider<Interceptor> {
|
|
|
74
92
|
next: () => ValueOrPromise<T>,
|
|
75
93
|
) {
|
|
76
94
|
MetricsInterceptor.setup();
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
});
|
|
80
|
-
const endHistogram = MetricsInterceptor.histogram.startTimer({
|
|
81
|
-
targetName: invocationCtx.targetName,
|
|
82
|
-
});
|
|
83
|
-
const endSummary = MetricsInterceptor.summary.startTimer({
|
|
95
|
+
const {source, parent} = invocationCtx;
|
|
96
|
+
const labelValues: LabelValues<LabelNames> = {
|
|
84
97
|
targetName: invocationCtx.targetName,
|
|
85
|
-
}
|
|
98
|
+
};
|
|
99
|
+
if (isRouteSource(source)) {
|
|
100
|
+
labelValues.method = getRequestMethod(source);
|
|
101
|
+
labelValues.path = getPathPattern(source);
|
|
102
|
+
}
|
|
103
|
+
const endGauge = MetricsInterceptor.gauge.startTimer();
|
|
104
|
+
const endHistogram = MetricsInterceptor.histogram.startTimer();
|
|
105
|
+
const endSummary = MetricsInterceptor.summary.startTimer();
|
|
86
106
|
try {
|
|
87
|
-
|
|
88
|
-
|
|
107
|
+
const result = await next();
|
|
108
|
+
if (isRouteSource(source)) {
|
|
109
|
+
labelValues.statusCode = getStatusCodeFromResponse(
|
|
110
|
+
// parent context will be request context if invocation source is route
|
|
111
|
+
(parent as RequestContext).response,
|
|
112
|
+
result,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
} catch (err) {
|
|
117
|
+
if (isRouteSource(source)) {
|
|
118
|
+
labelValues.statusCode = getStatusCodeFromError(err);
|
|
119
|
+
}
|
|
120
|
+
throw err;
|
|
89
121
|
} finally {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
122
|
+
MetricsInterceptor.counter.inc(labelValues);
|
|
123
|
+
endGauge(labelValues);
|
|
124
|
+
endHistogram(labelValues);
|
|
125
|
+
endSummary(labelValues);
|
|
93
126
|
}
|
|
94
127
|
}
|
|
95
128
|
}
|
|
129
|
+
|
|
130
|
+
function getPathPattern(source: RouteSource) {
|
|
131
|
+
// make sure to use path pattern instead of raw path
|
|
132
|
+
// this is important since paths can contain unbounded sets of values
|
|
133
|
+
// such as IDs which would create a new time series for each unique value
|
|
134
|
+
return source.value.path;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function getRequestMethod(source: RouteSource) {
|
|
138
|
+
// request methods should be all-uppercase
|
|
139
|
+
return source.value.verb.toUpperCase();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function getStatusCodeFromResponse(response: Response, result?: unknown) {
|
|
143
|
+
// interceptors are invoked before result is written to response,
|
|
144
|
+
// the status code for 200 responses without a result should be 204
|
|
145
|
+
const noContent = response.statusCode === 200 && result === undefined;
|
|
146
|
+
return noContent ? 204 : response.statusCode;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function getStatusCodeFromError(err: HttpErrors.HttpError) {
|
|
150
|
+
// interceptors are invoked before error is written to response,
|
|
151
|
+
// it is required to retrieve the status code from the error
|
|
152
|
+
const notFound = err.code === 'ENTITY_NOT_FOUND';
|
|
153
|
+
return err.statusCode ?? err.status ?? (notFound ? 404 : 500);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function isRouteSource(source?: InvocationSource): source is RouteSource {
|
|
157
|
+
return source?.type === 'route';
|
|
158
|
+
}
|
package/src/metrics.component.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
inject,
|
|
14
14
|
injectable,
|
|
15
15
|
} from '@loopback/core';
|
|
16
|
+
import {register} from 'prom-client';
|
|
16
17
|
import {metricsControllerFactory} from './controllers';
|
|
17
18
|
import {MetricsInterceptor} from './interceptors';
|
|
18
19
|
import {MetricsBindings} from './keys';
|
|
@@ -44,5 +45,8 @@ export class MetricsComponent implements Component {
|
|
|
44
45
|
if (options.endpoint && !options.endpoint.disabled) {
|
|
45
46
|
this.application.controller(metricsControllerFactory(options));
|
|
46
47
|
}
|
|
48
|
+
if (options.defaultLabels) {
|
|
49
|
+
register.setDefaultLabels(options.defaultLabels);
|
|
50
|
+
}
|
|
47
51
|
}
|
|
48
52
|
}
|
|
@@ -25,7 +25,15 @@ export class MetricsPushObserver implements LifeCycleObserver {
|
|
|
25
25
|
if (!gwConfig) return;
|
|
26
26
|
this.gateway = new Pushgateway(gwConfig.url);
|
|
27
27
|
this.interval = setInterval(() => {
|
|
28
|
-
|
|
28
|
+
const params = {
|
|
29
|
+
jobName: gwConfig.jobName ?? 'loopback',
|
|
30
|
+
groupings: gwConfig.groupingKey,
|
|
31
|
+
};
|
|
32
|
+
if (gwConfig.replaceAll) {
|
|
33
|
+
this.gateway.push(params, () => {});
|
|
34
|
+
} else {
|
|
35
|
+
this.gateway.pushAdd(params, () => {});
|
|
36
|
+
}
|
|
29
37
|
}, gwConfig.interval ?? 5000);
|
|
30
38
|
}
|
|
31
39
|
|
package/src/types.ts
CHANGED
|
@@ -18,10 +18,19 @@ export interface MetricsOptions {
|
|
|
18
18
|
disabled?: boolean;
|
|
19
19
|
} & DefaultMetricsCollectorConfiguration;
|
|
20
20
|
|
|
21
|
+
defaultLabels?: {
|
|
22
|
+
[labelName: string]: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
21
25
|
pushGateway?: {
|
|
22
26
|
disabled?: boolean;
|
|
23
27
|
url: string;
|
|
24
28
|
interval?: number;
|
|
29
|
+
jobName?: string;
|
|
30
|
+
groupingKey?: {
|
|
31
|
+
[key: string]: string;
|
|
32
|
+
};
|
|
33
|
+
replaceAll?: boolean;
|
|
25
34
|
};
|
|
26
35
|
|
|
27
36
|
openApiSpec?: boolean;
|