@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 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
- - push - pushing metrics from the system being monitored to a push gateway
76
-
77
- See
78
- https://prometheus.io/docs/introduction/faq/#why-do-you-pull-rather-than-push
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
- # HELP loopback_invocation_total method invocation counts
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 metricsResponse = {
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 metricsSpec = {
29
+ const METRICS_SPEC = {
30
30
  responses: {
31
- '200': metricsResponse,
31
+ '200': METRICS_RESPONSE,
32
32
  },
33
33
  };
34
34
  /**
35
35
  * OpenAPI spec to hide endpoints
36
36
  */
37
- const hiddenSpec = {
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 ? metricsSpec : hiddenSpec;
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
- res.send(prom_client_1.register.metrics());
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", void 0)
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,eAAe,GAAmB;IACtC,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,WAAW,GAAoB;IACnC,SAAS,EAAE;QACT,KAAK,EAAE,eAAe;KACvB;CACF,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,GAAoB;IAClC,SAAS,EAAE,EAAE;IACb,cAAc,EAAE,cAAc;CAC/B,CAAC;AAEF,SAAgB,wBAAwB,CACtC,UAA0B,+BAAuB;;IAEjD,MAAM,QAAQ,eAAG,OAAO,CAAC,QAAQ,0CAAE,QAAQ,mCAAI,UAAU,CAAC;IAC1D,MAAM,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC;IAE5D;;OAEG;IAEH,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;QAErB,MAAM,CAAqC,GAAa;YACtD,yCAAyC;YACzC,GAAG,CAAC,WAAW,CAAC,sBAAQ,CAAC,WAAW,CAAC,CAAC;YACtC,GAAG,CAAC,IAAI,CAAC,sBAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;YAC7B,OAAO,GAAG,CAAC;QACb,CAAC;KACF,CAAA;IANC;QADC,UAAG,CAAC,QAAQ,EAAE,IAAI,CAAC;QACZ,mBAAA,aAAM,CAAC,mBAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;;;;mDAKzC;IAPG,iBAAiB;QADtB,iBAAU,CAAC,EAAC,KAAK,EAAE,mBAAY,CAAC,SAAS,EAAC,CAAC;OACtC,iBAAiB,CAQtB;IAED,OAAO,iBAAiB,CAAC;AAC3B,CAAC;AArBD,4DAqBC"}
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: ['targetName'],
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: ['targetName'],
35
+ labelNames,
35
36
  });
36
37
  this.counter = new prom_client_1.Counter({
37
38
  name: 'loopback_invocation_total',
38
- help: 'method invocation counts',
39
- labelNames: ['targetName'],
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: ['targetName'],
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 endGauge = MetricsInterceptor_1.gauge.startTimer({
53
+ const { source, parent } = invocationCtx;
54
+ const labelValues = {
53
55
  targetName: invocationCtx.targetName,
54
- });
55
- const endHistogram = MetricsInterceptor_1.histogram.startTimer({
56
- targetName: invocationCtx.targetName,
57
- });
58
- const endSummary = MetricsInterceptor_1.summary.startTimer({
59
- targetName: invocationCtx.targetName,
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
- MetricsInterceptor_1.counter.inc();
63
- return await next();
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
- endGauge();
67
- endHistogram();
68
- endSummary();
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,yCAQwB;AACxB,6CAAyE;AAEzE;;;;;GAKG;AAEH,IAAa,kBAAkB,0BAA/B,MAAa,kBAAkB;IA0C7B,gBAAe,CAAC;IAjCR,MAAM,CAAC,KAAK;QAClB,mCAAmC;QACnC,IACE,IAAI,CAAC,KAAK;YACV,sBAAQ,CAAC,eAAe,CAAC,sCAAsC,CAAC;YAEhE,OAAO;QACT,oEAAoE;QACpE,IAAI,CAAC,KAAK,GAAG,IAAI,mBAAK,CAAC;YACrB,IAAI,EAAE,sCAAsC;YAC5C,IAAI,EAAE,mBAAmB;YACzB,UAAU,EAAE,CAAC,YAAY,CAAC;SAC3B,CAAC,CAAC;QAEH,IAAI,CAAC,SAAS,GAAG,IAAI,uBAAS,CAAC;YAC7B,IAAI,EAAE,wCAAwC;YAC9C,IAAI,EAAE,6BAA6B;YACnC,UAAU,EAAE,CAAC,YAAY,CAAC;SAC3B,CAAC,CAAC;QAEH,IAAI,CAAC,OAAO,GAAG,IAAI,qBAAO,CAAC;YACzB,IAAI,EAAE,2BAA2B;YACjC,IAAI,EAAE,0BAA0B;YAChC,UAAU,EAAE,CAAC,YAAY,CAAC;SAC3B,CAAC,CAAC;QAEH,IAAI,CAAC,OAAO,GAAG,IAAI,qBAAO,CAAC;YACzB,IAAI,EAAE,sCAAsC;YAC5C,IAAI,EAAE,2BAA2B;YACjC,UAAU,EAAE,CAAC,YAAY,CAAC;SAC3B,CAAC,CAAC;IACL,CAAC;IAID,KAAK;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,SAAS,CACb,aAAgC,EAChC,IAA6B;QAE7B,oBAAkB,CAAC,KAAK,EAAE,CAAC;QAC3B,MAAM,QAAQ,GAAG,oBAAkB,CAAC,KAAK,CAAC,UAAU,CAAC;YACnD,UAAU,EAAE,aAAa,CAAC,UAAU;SACrC,CAAC,CAAC;QACH,MAAM,YAAY,GAAG,oBAAkB,CAAC,SAAS,CAAC,UAAU,CAAC;YAC3D,UAAU,EAAE,aAAa,CAAC,UAAU;SACrC,CAAC,CAAC;QACH,MAAM,UAAU,GAAG,oBAAkB,CAAC,OAAO,CAAC,UAAU,CAAC;YACvD,UAAU,EAAE,aAAa,CAAC,UAAU;SACrC,CAAC,CAAC;QACH,IAAI;YACF,oBAAkB,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;YACjC,OAAO,MAAM,IAAI,EAAE,CAAC;SACrB;gBAAS;YACR,QAAQ,EAAE,CAAC;YACX,YAAY,EAAE,CAAC;YACf,UAAU,EAAE,CAAC;SACd;IACH,CAAC;CACF,CAAA;AAvEY,kBAAkB;IAD9B,iBAAU,CAAC,0BAAmB,CAAC,SAAS,CAAC,EAAE,EAAC,KAAK,EAAE,mBAAY,CAAC,SAAS,EAAC,CAAC;;GAC/D,kBAAkB,CAuE9B;AAvEY,gDAAkB"}
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;AAtBY,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,CAsB5B;AAtBY,4CAAgB"}
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
- this.gateway.pushAdd({ jobName: 'loopback' }, () => { });
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;YAC/B,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAC,OAAO,EAAE,UAAU,EAAC,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACxD,CAAC,QAAE,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;AAxBY,mBAAmB;IAK3B,mBAAA,aAAM,CAAC,EAAC,WAAW,EAAE,sBAAe,CAAC,SAAS,EAAC,CAAC,CAAA;;GALxC,mBAAmB,CAwB/B;AAxBY,kDAAmB"}
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;;;AA+BnD,QAAA,uBAAuB,GAAmB;IACrD,QAAQ,EAAE;QACR,QAAQ,EAAE,UAAU;KACrB;IACD,cAAc,EAAE,EAAE;IAClB,WAAW,EAAE,KAAK;CACnB,CAAC"}
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.5.2",
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.13.1",
25
- "@loopback/rest": "^9.1.1"
24
+ "@loopback/core": "^2.15.1",
25
+ "@loopback/rest": "^9.2.1"
26
26
  },
27
27
  "dependencies": {
28
- "prom-client": "^12.0.0",
29
- "tslib": "^2.0.3"
28
+ "prom-client": "^13.1.0",
29
+ "tslib": "^2.1.0"
30
30
  },
31
31
  "devDependencies": {
32
- "@loopback/build": "^6.2.8",
33
- "@loopback/core": "^2.13.1",
34
- "@loopback/eslint-config": "^10.0.4",
35
- "@loopback/rest": "^9.1.1",
36
- "@loopback/testlab": "^3.2.10",
37
- "@types/express": "^4.17.9",
38
- "@types/node": "^10.17.35",
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": "44fc7765fa322c716e8f2914b3831748041ebd8c"
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 metricsResponse: ResponseObject = {
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 metricsSpec: OperationObject = {
34
+ const METRICS_SPEC: OperationObject = {
35
35
  responses: {
36
- '200': metricsResponse,
36
+ '200': METRICS_RESPONSE,
37
37
  },
38
38
  };
39
39
 
40
40
  /**
41
41
  * OpenAPI spec to hide endpoints
42
42
  */
43
- const hiddenSpec: OperationObject = {
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 ? metricsSpec : hiddenSpec;
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
- res.send(register.metrics());
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 {Counter, Gauge, Histogram, register, Summary} from 'prom-client';
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<'targetName'>;
43
+ private static gauge: Gauge<LabelNames>;
26
44
 
27
- private static histogram: Histogram<'targetName'>;
45
+ private static histogram: Histogram<LabelNames>;
28
46
 
29
- private static counter: Counter<'targetName'>;
47
+ private static counter: Counter<LabelNames>;
30
48
 
31
- private static summary: Summary<'targetName'>;
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: ['targetName'],
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: ['targetName'],
68
+ labelNames,
51
69
  });
52
70
 
53
71
  this.counter = new Counter({
54
72
  name: 'loopback_invocation_total',
55
- help: 'method invocation counts',
56
- labelNames: ['targetName'],
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: ['targetName'],
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 endGauge = MetricsInterceptor.gauge.startTimer({
78
- targetName: invocationCtx.targetName,
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
- MetricsInterceptor.counter.inc();
88
- return await next();
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
- endGauge();
91
- endHistogram();
92
- endSummary();
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
+ }
@@ -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
- this.gateway.pushAdd({jobName: 'loopback'}, () => {});
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;