@fluojs/metrics 1.0.4 → 2.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/README.ko.md +8 -7
- package/README.md +7 -6
- package/dist/http-metrics-middleware.d.ts.map +1 -1
- package/dist/http-metrics-middleware.js +40 -11
- package/dist/metrics-module.d.ts +1 -0
- package/dist/metrics-module.d.ts.map +1 -1
- package/dist/metrics-module.js +216 -64
- package/package.json +5 -5
package/README.ko.md
CHANGED
|
@@ -158,7 +158,7 @@ new Counter({
|
|
|
158
158
|
class AppModule {}
|
|
159
159
|
```
|
|
160
160
|
|
|
161
|
-
여러 `MetricsModule` 인스턴스가 같은 Registry를 의도적으로 공유하는 경우, 내장 HTTP 메트릭은 기존 `http_requests_total`, `http_errors_total`, `http_request_duration_seconds` collector를 재사용합니다. 내장 플랫폼 텔레메트리 Gauge도 같은 ownership 규칙을 따릅니다. 모듈이 만든 `fluo_component_ready`, `fluo_component_health`, `fluo_metrics_registry_mode` Gauge는 framework ownership과 label schema가 일치할 때만 재사용합니다. 애플리케이션이 직접 등록한 중복 메트릭 이름은 Prometheus Registry 규칙대로 계속 빠르게 실패합니다.
|
|
161
|
+
여러 `MetricsModule` 인스턴스가 같은 Registry를 의도적으로 공유하는 경우, 내장 HTTP 메트릭은 framework ownership, label schema, effective path-label configuration이 모두 일치할 때만 기존 `http_requests_total`, `http_errors_total`, `http_request_duration_seconds` collector를 재사용합니다. Path-label compatibility 검사는 `pathLabelMode`, 정확히 같은 `pathLabelNormalizer` 함수 참조, `unknownPathLabel` fallback 의미론을 포함하므로 서로 다른 HTTP series policy를 하나의 collector set에 섞는 module instance는 빠르게 실패합니다. 내장 플랫폼 텔레메트리 Gauge도 같은 ownership 규칙을 따릅니다. 모듈이 만든 `fluo_component_ready`, `fluo_component_health`, `fluo_metrics_registry_mode` Gauge는 framework ownership과 label schema가 일치할 때만 재사용합니다. 플랫폼 텔레메트리 상태는 재사용된 Registry별로 추적되므로, 이후 스크레이프는 이전 module instance가 남긴 stale component readiness/health series를 제거한 뒤 메트릭을 반환합니다. Registry scrape wrapper는 최신 active module registration을 사용하며 마지막 registration이 종료되면 Registry의 원래 `metrics()` 함수를 복원합니다. 애플리케이션이 직접 등록한 중복 메트릭 이름은 Prometheus Registry 규칙대로 계속 빠르게 실패합니다.
|
|
162
162
|
|
|
163
163
|
### 중복 메트릭 이름은 계속 빠르게 실패합니다
|
|
164
164
|
|
|
@@ -170,9 +170,9 @@ Prometheus 메트릭 이름은 하나의 Registry 안에서 고유해야 합니
|
|
|
170
170
|
|
|
171
171
|
- `fluo_component_ready`: 준비 완료 시 1, 아닐 시 0.
|
|
172
172
|
- `fluo_component_health`: 정상 상태 시 1, 아닐 시 0.
|
|
173
|
-
- `fluo_metrics_registry_mode`: active registry mode
|
|
173
|
+
- `fluo_metrics_registry_mode`: active registry mode를 `mode="isolated"` 또는 `mode="shared"` label과 gauge value `1`로 나타냅니다.
|
|
174
174
|
|
|
175
|
-
이 데이터는
|
|
175
|
+
이 데이터는 built-in `/metrics` controller와 `MetricsService.getRegistry().metrics()`를 사용하는 advanced custom scraper를 포함해 active Registry가 스크레이프될 때마다 `PLATFORM_SHELL`을 쿼리하여 갱신됩니다. 초기화 시 환경 라벨을 제공할 수 있습니다.
|
|
176
176
|
|
|
177
177
|
```ts
|
|
178
178
|
MetricsModule.forRoot({
|
|
@@ -185,10 +185,10 @@ MetricsModule.forRoot({
|
|
|
185
185
|
|
|
186
186
|
### 런타임 플랫폼 텔레메트리 스크레이프 계약
|
|
187
187
|
|
|
188
|
-
플랫폼 텔레메트리는
|
|
188
|
+
플랫폼 텔레메트리는 built-in `/metrics` controller 또는 `MetricsService.getRegistry()`를 사용하는 advanced custom scraper가 active Registry를 스크레이프할 때마다 `PLATFORM_SHELL`을 resolve하여 `fluo_component_ready`와 `fluo_component_health`를 갱신합니다.
|
|
189
189
|
|
|
190
190
|
- `PLATFORM_SHELL` 등록 자체가 빠진 경우에는 스크레이프가 계속 성공하고 플랫폼 텔레메트리 시리즈만 생략됩니다.
|
|
191
|
-
- 직전 성공 스크레이프에서 플랫폼 텔레메트리를 노출한 뒤 `PLATFORM_SHELL`을 사용할 수 없게
|
|
191
|
+
- 직전 성공 스크레이프에서 플랫폼 텔레메트리를 노출한 뒤 `PLATFORM_SHELL`을 사용할 수 없게 되거나 이후 module instance가 재사용된 Registry를 다른 플랫폼 snapshot으로 갱신하면, stale `fluo_component_ready` 및 `fluo_component_health` 시리즈를 제거한 뒤 메트릭을 반환합니다.
|
|
192
192
|
- 그 외의 `PLATFORM_SHELL` resolve 실패는 조용히 삼키지 않고 스크레이프 실패로 그대로 드러납니다.
|
|
193
193
|
|
|
194
194
|
### 기본 프로세스/Node 메트릭 비활성화
|
|
@@ -220,10 +220,11 @@ MetricsModule.forRoot({
|
|
|
220
220
|
- `defaultMetrics`의 기본값은 `true`이며, `defaultMetrics: false`로 해당 Registry의 Prometheus 기본 프로세스/Node.js collector를 끌 수 있습니다.
|
|
221
221
|
- `endpointMiddleware`는 class-based route-scoped middleware를 스크레이프 엔드포인트에만 바인딩합니다. HTTP 계측이 활성화된 경우 endpoint middleware 실패는 내장 HTTP collector에 집계됩니다.
|
|
222
222
|
- HTTP 메트릭은 `http: true` 또는 `http` 옵션 객체를 전달한 경우에만 설치되며, 설치된 뒤에는 기본적으로 템플릿 기반 경로 라벨 정규화를 사용합니다.
|
|
223
|
-
- 내장 HTTP collector
|
|
223
|
+
- 내장 HTTP collector는 같은 Registry를 공유하는 모듈 인스턴스 사이에서 framework-owned이고 예상 label schema 및 일치하는 path-label configuration을 가진 경우에만 재사용됩니다. 플랫폼 텔레메트리 Gauge는 framework-owned이고 예상 label schema를 가진 경우에만 재사용되며, 커스텀 애플리케이션 메트릭 이름 충돌은 Prometheus의 중복 이름 실패 동작을 유지합니다.
|
|
224
|
+
- Shared Registry 텔레메트리 refresh는 소유 metrics module이 하나라도 active인 동안 유지되고 마지막 module이 종료되면 원래 `metrics()` 함수를 복원합니다.
|
|
224
225
|
- raw path 라벨은 `allowUnsafeRawPathLabelMode: true`를 명시한 bounded internal route에서만 사용해야 합니다.
|
|
225
226
|
- 플랫폼 텔레메트리는 `PLATFORM_SHELL`이 실제로 누락된 경우에만 생략되며, 그 외 resolve 실패는 스크레이프를 실패시킵니다.
|
|
226
|
-
- 직전 성공 스크레이프에서 노출된 플랫폼 텔레메트리 시리즈는 `PLATFORM_SHELL`을 사용할 수 없게 된 스크레이프에서 제거됩니다.
|
|
227
|
+
- 직전 성공 스크레이프에서 노출된 플랫폼 텔레메트리 시리즈는 `PLATFORM_SHELL`을 사용할 수 없게 된 스크레이프 또는 이후 module instance가 재사용된 Registry를 다른 플랫폼 snapshot으로 갱신한 스크레이프에서 제거됩니다.
|
|
227
228
|
|
|
228
229
|
## 관련 패키지
|
|
229
230
|
|
package/README.md
CHANGED
|
@@ -158,7 +158,7 @@ new Counter({
|
|
|
158
158
|
class AppModule {}
|
|
159
159
|
```
|
|
160
160
|
|
|
161
|
-
When multiple metrics module instances intentionally share the same registry, built-in HTTP metrics reuse the existing `http_requests_total`, `http_errors_total`, and `http_request_duration_seconds` collectors instead of registering duplicate framework metrics. Built-in platform telemetry gauges follow the same ownership rule: module-created `fluo_component_ready`, `fluo_component_health`, and `fluo_metrics_registry_mode` gauges are reused only when their framework ownership and label schema match. Application-defined duplicate names still fail fast.
|
|
161
|
+
When multiple metrics module instances intentionally share the same registry, built-in HTTP metrics reuse the existing `http_requests_total`, `http_errors_total`, and `http_request_duration_seconds` collectors instead of registering duplicate framework metrics only when their framework ownership, label schema, and effective path-label configuration match. The path-label compatibility check includes `pathLabelMode`, the exact `pathLabelNormalizer` function reference, and `unknownPathLabel` fallback semantics, so incompatible module instances fail fast instead of mixing different HTTP series policies into one collector set. Built-in platform telemetry gauges follow the same ownership rule: module-created `fluo_component_ready`, `fluo_component_health`, and `fluo_metrics_registry_mode` gauges are reused only when their framework ownership and label schema match. Platform telemetry state is tracked per reused registry, so a later scrape replaces stale module-owned component readiness and health series from an earlier module instance before metrics are returned. The registry scrape wrapper keeps using the latest active module registration and restores the Registry's original `metrics()` function after the last registration closes. Application-defined duplicate names still fail fast.
|
|
162
162
|
|
|
163
163
|
### Duplicate metric names still fail fast
|
|
164
164
|
|
|
@@ -170,9 +170,9 @@ The module emits fluo-specific gauges that mirror the platform shell and registe
|
|
|
170
170
|
|
|
171
171
|
- `fluo_component_ready`: `1` when a component is ready, otherwise `0`.
|
|
172
172
|
- `fluo_component_health`: `1` when a component is healthy, otherwise `0`.
|
|
173
|
-
- `fluo_metrics_registry_mode`: `isolated` or `shared` for the active registry mode.
|
|
173
|
+
- `fluo_metrics_registry_mode`: gauge value `1` with a `mode="isolated"` or `mode="shared"` label for the active registry mode.
|
|
174
174
|
|
|
175
|
-
The platform snapshot is refreshed during each scrape, and you can attach environment labels up front.
|
|
175
|
+
The platform snapshot is refreshed during each registry scrape, including advanced `MetricsService.getRegistry().metrics()` scrape paths, and you can attach environment labels up front.
|
|
176
176
|
|
|
177
177
|
```ts
|
|
178
178
|
MetricsModule.forRoot({
|
|
@@ -185,7 +185,7 @@ MetricsModule.forRoot({
|
|
|
185
185
|
|
|
186
186
|
### Runtime platform telemetry scrape contract
|
|
187
187
|
|
|
188
|
-
Platform telemetry refreshes `fluo_component_ready` and `fluo_component_health` on each
|
|
188
|
+
Platform telemetry refreshes `fluo_component_ready` and `fluo_component_health` on each active registry scrape by resolving `PLATFORM_SHELL`, whether the scrape flows through the built-in `/metrics` controller or an advanced custom scraper using `MetricsService.getRegistry()`.
|
|
189
189
|
|
|
190
190
|
- If `PLATFORM_SHELL` is not registered, the scrape still succeeds and omits the platform telemetry series.
|
|
191
191
|
- If `PLATFORM_SHELL` becomes unavailable after the last successful scrape, stale `fluo_component_ready` and `fluo_component_health` series are removed before metrics are returned.
|
|
@@ -220,10 +220,11 @@ MetricsModule.forRoot({
|
|
|
220
220
|
- `defaultMetrics` defaults to `true`, and `defaultMetrics: false` disables Prometheus default process and Node.js collectors for that registry.
|
|
221
221
|
- `endpointMiddleware` binds class-based route-scoped middleware only to the scrape endpoint; with HTTP instrumentation enabled, endpoint middleware failures are counted by the built-in HTTP collectors.
|
|
222
222
|
- HTTP metrics are installed only when `http: true` or an `http` options object is provided, and then default to template-normalized path labels.
|
|
223
|
-
- Built-in HTTP collectors
|
|
223
|
+
- Built-in HTTP collectors are reused when module instances share one registry only if they are framework-owned, have the expected label schema, and use matching path-label configuration; platform telemetry gauges are reused only if they are framework-owned and have the expected label schema; custom application metric name collisions keep Prometheus' duplicate-name failure behavior.
|
|
224
|
+
- Shared Registry telemetry refresh remains installed while any owning metrics module is active and restores the original `metrics()` function after the last module closes.
|
|
224
225
|
- Raw path labels require `allowUnsafeRawPathLabelMode: true` and should stay limited to bounded internal routes.
|
|
225
226
|
- Platform telemetry is omitted only when `PLATFORM_SHELL` is genuinely missing; other resolution failures fail the scrape.
|
|
226
|
-
- Stale platform telemetry series are removed when `PLATFORM_SHELL` becomes unavailable after the last successful scrape.
|
|
227
|
+
- Stale platform telemetry series are removed when `PLATFORM_SHELL` becomes unavailable after the last successful scrape or when a later module instance refreshes a reused registry with a different platform snapshot.
|
|
227
228
|
|
|
228
229
|
## Related Packages
|
|
229
230
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-metrics-middleware.d.ts","sourceRoot":"","sources":["../src/http-metrics-middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,iBAAiB,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAC1F,OAAO,EAAsB,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"http-metrics-middleware.d.ts","sourceRoot":"","sources":["../src/http-metrics-middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,iBAAiB,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAC1F,OAAO,EAAsB,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AA4BhE,oEAAoE;AACpE,MAAM,MAAM,wBAAwB,GAAG,KAAK,GAAG,UAAU,CAAC;AAE1D,wDAAwD;AACxD,MAAM,WAAW,2BAA2B;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,gBAAgB,CAAC;CAC3B;AAED,2EAA2E;AAC3E,MAAM,MAAM,8BAA8B,GAAG,CAAC,OAAO,EAAE,2BAA2B,KAAK,MAAM,CAAC;AAE9F,8DAA8D;AAC9D,MAAM,WAAW,4BAA4B;IAC3C,aAAa,CAAC,EAAE,wBAAwB,CAAC;IACzC,mBAAmB,CAAC,EAAE,8BAA8B,CAAC;IACrD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC;AAsBD;;GAEG;AACH,qBAAa,qBAAsB,YAAW,UAAU;IACtD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoB;IAClD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAoB;IAChD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAsB;IACtD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA2B;IACzD,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAiC;IACtE,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;gBAE9B,QAAQ,EAAE,QAAQ,EAAE,OAAO,GAAE,4BAAiC;IAuB1E,OAAO,CAAC,gBAAgB;IAmBlB,MAAM,CAAC,OAAO,EAAE,iBAAiB,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBnE,OAAO,CAAC,iBAAiB;IAYzB,OAAO,CAAC,oBAAoB;CAsB7B"}
|
|
@@ -2,6 +2,7 @@ import { Counter, Histogram } from 'prom-client';
|
|
|
2
2
|
import { createPrometheusCounter, createPrometheusHistogram } from './providers/prometheus-metrics-factory.js';
|
|
3
3
|
const FRAMEWORK_HTTP_COUNTERS = new WeakSet();
|
|
4
4
|
const FRAMEWORK_HTTP_HISTOGRAMS = new WeakSet();
|
|
5
|
+
const FRAMEWORK_HTTP_COLLECTOR_CONFIGURATION = new WeakMap();
|
|
5
6
|
|
|
6
7
|
/** Strategy used to label request paths in emitted HTTP metrics. */
|
|
7
8
|
|
|
@@ -38,27 +39,25 @@ export class HttpMetricsMiddleware {
|
|
|
38
39
|
pathLabelNormalizer;
|
|
39
40
|
unknownPathLabel;
|
|
40
41
|
constructor(registry, options = {}) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
this.
|
|
45
|
-
this.pathLabelNormalizer = options.pathLabelNormalizer;
|
|
46
|
-
this.unknownPathLabel = options.unknownPathLabel ?? 'UNKNOWN';
|
|
42
|
+
const collectorConfiguration = resolveHttpMetricsCollectorConfiguration(options);
|
|
43
|
+
this.pathLabelMode = collectorConfiguration.pathLabelMode;
|
|
44
|
+
this.pathLabelNormalizer = collectorConfiguration.pathLabelNormalizer;
|
|
45
|
+
this.unknownPathLabel = collectorConfiguration.unknownPathLabel;
|
|
47
46
|
this.requestsTotal = getOrCreateHttpCounter(registry, {
|
|
48
47
|
help: 'Total number of HTTP requests',
|
|
49
48
|
labelNames: ['method', 'path', 'status'],
|
|
50
49
|
name: 'http_requests_total'
|
|
51
|
-
});
|
|
50
|
+
}, collectorConfiguration);
|
|
52
51
|
this.errorsTotal = getOrCreateHttpCounter(registry, {
|
|
53
52
|
help: 'Total number of HTTP error responses (4xx/5xx)',
|
|
54
53
|
labelNames: ['method', 'path', 'status'],
|
|
55
54
|
name: 'http_errors_total'
|
|
56
|
-
});
|
|
55
|
+
}, collectorConfiguration);
|
|
57
56
|
this.requestDuration = getOrCreateHttpHistogram(registry, {
|
|
58
57
|
help: 'HTTP request duration in seconds',
|
|
59
58
|
labelNames: ['method', 'path', 'status'],
|
|
60
59
|
name: 'http_request_duration_seconds'
|
|
61
|
-
});
|
|
60
|
+
}, collectorConfiguration);
|
|
62
61
|
}
|
|
63
62
|
resolvePathLabel(request) {
|
|
64
63
|
if (this.pathLabelNormalizer) {
|
|
@@ -119,13 +118,14 @@ export class HttpMetricsMiddleware {
|
|
|
119
118
|
}
|
|
120
119
|
}
|
|
121
120
|
}
|
|
122
|
-
function getOrCreateHttpCounter(registry, config) {
|
|
121
|
+
function getOrCreateHttpCounter(registry, config, collectorConfiguration) {
|
|
123
122
|
const existing = registry.getSingleMetric(config.name);
|
|
124
123
|
if (existing instanceof Counter) {
|
|
125
124
|
if (!FRAMEWORK_HTTP_COUNTERS.has(existing)) {
|
|
126
125
|
throw new Error(`Metric name "${config.name}" is already registered by the application. Built-in HTTP metrics require framework-owned collectors.`);
|
|
127
126
|
}
|
|
128
127
|
assertHttpMetricLabelSchema(existing, config);
|
|
128
|
+
assertHttpMetricConfiguration(existing, config.name, collectorConfiguration);
|
|
129
129
|
return existing;
|
|
130
130
|
}
|
|
131
131
|
const counter = createPrometheusCounter(registry, {
|
|
@@ -134,15 +134,17 @@ function getOrCreateHttpCounter(registry, config) {
|
|
|
134
134
|
name: config.name
|
|
135
135
|
});
|
|
136
136
|
FRAMEWORK_HTTP_COUNTERS.add(counter);
|
|
137
|
+
FRAMEWORK_HTTP_COLLECTOR_CONFIGURATION.set(counter, collectorConfiguration);
|
|
137
138
|
return counter;
|
|
138
139
|
}
|
|
139
|
-
function getOrCreateHttpHistogram(registry, config) {
|
|
140
|
+
function getOrCreateHttpHistogram(registry, config, collectorConfiguration) {
|
|
140
141
|
const existing = registry.getSingleMetric(config.name);
|
|
141
142
|
if (existing instanceof Histogram) {
|
|
142
143
|
if (!FRAMEWORK_HTTP_HISTOGRAMS.has(existing)) {
|
|
143
144
|
throw new Error(`Metric name "${config.name}" is already registered by the application. Built-in HTTP metrics require framework-owned collectors.`);
|
|
144
145
|
}
|
|
145
146
|
assertHttpMetricLabelSchema(existing, config);
|
|
147
|
+
assertHttpMetricConfiguration(existing, config.name, collectorConfiguration);
|
|
146
148
|
return existing;
|
|
147
149
|
}
|
|
148
150
|
const histogram = createPrometheusHistogram(registry, {
|
|
@@ -151,6 +153,7 @@ function getOrCreateHttpHistogram(registry, config) {
|
|
|
151
153
|
name: config.name
|
|
152
154
|
});
|
|
153
155
|
FRAMEWORK_HTTP_HISTOGRAMS.add(histogram);
|
|
156
|
+
FRAMEWORK_HTTP_COLLECTOR_CONFIGURATION.set(histogram, collectorConfiguration);
|
|
154
157
|
return histogram;
|
|
155
158
|
}
|
|
156
159
|
function assertHttpMetricLabelSchema(metric, config) {
|
|
@@ -160,6 +163,32 @@ function assertHttpMetricLabelSchema(metric, config) {
|
|
|
160
163
|
throw new Error(`Metric name "${config.name}" is already registered with labels [${registeredLabels}]. Built-in HTTP metrics require labels [${expectedLabels}].`);
|
|
161
164
|
}
|
|
162
165
|
}
|
|
166
|
+
function resolveHttpMetricsCollectorConfiguration(options) {
|
|
167
|
+
if (options.pathLabelMode === 'raw' && options.allowUnsafeRawPathLabelMode !== true) {
|
|
168
|
+
throw new Error('HttpMetricsMiddleware pathLabelMode "raw" is disabled by default. Pass allowUnsafeRawPathLabelMode: true only when you have bounded path cardinality.');
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
pathLabelMode: options.pathLabelMode ?? 'template',
|
|
172
|
+
pathLabelNormalizer: options.pathLabelNormalizer,
|
|
173
|
+
unknownPathLabel: options.unknownPathLabel ?? 'UNKNOWN'
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function assertHttpMetricConfiguration(metric, metricName, expected) {
|
|
177
|
+
const registered = FRAMEWORK_HTTP_COLLECTOR_CONFIGURATION.get(metric);
|
|
178
|
+
if (!registered) {
|
|
179
|
+
throw new Error(`Metric name "${metricName}" is already registered as a framework-owned HTTP collector without path-label configuration metadata. Built-in HTTP metrics require matching path-label configuration before reuse.`);
|
|
180
|
+
}
|
|
181
|
+
if (hasSameHttpMetricConfiguration(registered, expected)) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
throw new Error(`Metric name "${metricName}" is already registered with framework HTTP path-label configuration ${describeHttpMetricConfiguration(registered)}. Built-in HTTP metrics require matching path-label configuration before reuse; received ${describeHttpMetricConfiguration(expected)}.`);
|
|
185
|
+
}
|
|
186
|
+
function hasSameHttpMetricConfiguration(left, right) {
|
|
187
|
+
return left.pathLabelMode === right.pathLabelMode && left.pathLabelNormalizer === right.pathLabelNormalizer && left.unknownPathLabel === right.unknownPathLabel;
|
|
188
|
+
}
|
|
189
|
+
function describeHttpMetricConfiguration(configuration) {
|
|
190
|
+
return `pathLabelMode="${configuration.pathLabelMode}", pathLabelNormalizer=${configuration.pathLabelNormalizer ? 'custom' : 'none'}, unknownPathLabel="${configuration.unknownPathLabel}"`;
|
|
191
|
+
}
|
|
163
192
|
function normalizePathToTemplate(path, params) {
|
|
164
193
|
if (!path) {
|
|
165
194
|
return '/';
|
package/dist/metrics-module.d.ts
CHANGED
|
@@ -42,6 +42,7 @@ export interface MetricsModuleOptions {
|
|
|
42
42
|
/** Module entry point that exposes `/metrics` and optional HTTP/runtime telemetry. */
|
|
43
43
|
export declare class MetricsModule {
|
|
44
44
|
private static registeredRegistries;
|
|
45
|
+
private static httpInstrumentationRegistrations;
|
|
45
46
|
/**
|
|
46
47
|
* Register framework metrics, optional HTTP middleware, and a scrape endpoint.
|
|
47
48
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metrics-module.d.ts","sourceRoot":"","sources":["../src/metrics-module.ts"],"names":[],"mappings":"AAEA,OAAO,EAA8B,KAAK,UAAU,EAAE,KAAK,cAAc,EAAuB,MAAM,cAAc,CAAC;AACrH,OAAO,
|
|
1
|
+
{"version":3,"file":"metrics-module.d.ts","sourceRoot":"","sources":["../src/metrics-module.ts"],"names":[],"mappings":"AAEA,OAAO,EAA8B,KAAK,UAAU,EAAE,KAAK,cAAc,EAAuB,MAAM,cAAc,CAAC;AACrH,OAAO,EAEL,KAAK,UAAU,EAIhB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAgE,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE1G,OAAO,EAGL,KAAK,wBAAwB,EAC7B,KAAK,8BAA8B,EACpC,MAAM,8BAA8B,CAAC;AAKtC,qFAAqF;AACrF,MAAM,WAAW,kBAAkB;IACjC,iGAAiG;IACjG,aAAa,CAAC,EAAE,wBAAwB,CAAC;IACzC,kFAAkF;IAClF,mBAAmB,CAAC,EAAE,8BAA8B,CAAC;IACrD,+DAA+D;IAC/D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,2EAA2E;IAC3E,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,kGAAkG;IAClG,IAAI,CAAC,EAAE,OAAO,GAAG,kBAAkB,CAAC;IACpC,yHAAyH;IACzH,IAAI,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACtB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,sHAAsH;IACtH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,mGAAmG;IACnG,UAAU,CAAC,EAAE,cAAc,EAAE,CAAC;IAC9B,uGAAuG;IACvG,kBAAkB,CAAC,EAAE,KAAK,CAAC,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,UAAU,CAAC,CAAC;IAC/D,2EAA2E;IAC3E,iBAAiB,CAAC,EAAE;QAClB,iEAAiE;QACjE,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,iDAAiD;QACjD,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,iFAAiF;IACjF,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED,sFAAsF;AACtF,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAA2B;IAC9D,OAAO,CAAC,MAAM,CAAC,gCAAgC,CAA4D;IAE3G;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,oBAAyB,GAAG,UAAU;IA6H9D,OAAO,CAAC,MAAM,CAAC,cAAc;CAU9B"}
|
package/dist/metrics-module.js
CHANGED
|
@@ -7,6 +7,7 @@ import { Inject } from '@fluojs/core';
|
|
|
7
7
|
import { ContainerResolutionError } from '@fluojs/di';
|
|
8
8
|
import { Controller, Get, forRoutes } from '@fluojs/http';
|
|
9
9
|
import { defineModule, PLATFORM_SHELL } from '@fluojs/runtime';
|
|
10
|
+
import { RUNTIME_CONTAINER } from '@fluojs/runtime/internal';
|
|
10
11
|
import { collectDefaultMetrics, Gauge, Registry as PrometheusRegistry } from 'prom-client';
|
|
11
12
|
import { HttpMetricsMiddleware } from './http-metrics-middleware.js';
|
|
12
13
|
import { METER_PROVIDER } from './providers/meter-provider.js';
|
|
@@ -22,6 +23,7 @@ import { PrometheusMeterProvider } from './providers/prometheus-meter-provider.j
|
|
|
22
23
|
/** Module entry point that exposes `/metrics` and optional HTTP/runtime telemetry. */
|
|
23
24
|
export class MetricsModule {
|
|
24
25
|
static registeredRegistries = new WeakSet();
|
|
26
|
+
static httpInstrumentationRegistrations = new WeakMap();
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
29
|
* Register framework metrics, optional HTTP middleware, and a scrape endpoint.
|
|
@@ -44,17 +46,46 @@ export class MetricsModule {
|
|
|
44
46
|
}
|
|
45
47
|
const httpOptions = resolveHttpOptions(options.http);
|
|
46
48
|
const metricsPath = options.path === undefined ? '/metrics' : options.path;
|
|
47
|
-
|
|
49
|
+
let registryToken = Symbol('MetricsModule.registry');
|
|
48
50
|
const platformTelemetryToken = Symbol('MetricsModule.platformTelemetry');
|
|
49
|
-
|
|
51
|
+
let httpMetricsMiddleware = httpOptions ? createHttpMetricsMiddleware(registryToken, httpOptions) : undefined;
|
|
50
52
|
const endpointMiddleware = typeof metricsPath === 'string' ? (options.endpointMiddleware ?? []).map(middlewareClass => forRoutes(middlewareClass, metricsPath)) : [];
|
|
51
|
-
const middleware = [...
|
|
52
|
-
const
|
|
53
|
+
const middleware = [...endpointMiddleware, ...(options.middleware ?? [])];
|
|
54
|
+
const registryProvider = {
|
|
53
55
|
provide: registryToken,
|
|
54
56
|
useFactory: () => MetricsModule.createRegistry(options)
|
|
55
|
-
}
|
|
57
|
+
};
|
|
58
|
+
const imports = [];
|
|
59
|
+
const validationProviders = [];
|
|
60
|
+
let includeRuntimeRegistryProvider = httpMetricsMiddleware === undefined;
|
|
61
|
+
if (httpOptions && httpMetricsMiddleware) {
|
|
62
|
+
const existingRegistration = options.registry ? MetricsModule.httpInstrumentationRegistrations.get(options.registry) : undefined;
|
|
63
|
+
if (existingRegistration) {
|
|
64
|
+
registryToken = existingRegistration.registryToken;
|
|
65
|
+
httpMetricsMiddleware = undefined;
|
|
66
|
+
validationProviders.push(createHttpCollectorValidationProvider(registryToken, httpOptions));
|
|
67
|
+
imports.push(existingRegistration.moduleType);
|
|
68
|
+
} else {
|
|
69
|
+
includeRuntimeRegistryProvider = false;
|
|
70
|
+
class MetricsHttpInstrumentationModule {}
|
|
71
|
+
defineModule(MetricsHttpInstrumentationModule, {
|
|
72
|
+
exports: [registryToken],
|
|
73
|
+
global: true,
|
|
74
|
+
middleware: [httpMetricsMiddleware],
|
|
75
|
+
providers: [registryProvider, httpMetricsMiddleware]
|
|
76
|
+
});
|
|
77
|
+
if (options.registry) {
|
|
78
|
+
MetricsModule.httpInstrumentationRegistrations.set(options.registry, {
|
|
79
|
+
moduleType: MetricsHttpInstrumentationModule,
|
|
80
|
+
registryToken
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
imports.push(MetricsHttpInstrumentationModule);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const providers = [...(includeRuntimeRegistryProvider ? [registryProvider] : []), ...validationProviders, {
|
|
56
87
|
provide: MetricsService,
|
|
57
|
-
inject: [registryToken],
|
|
88
|
+
inject: [registryToken, platformTelemetryToken],
|
|
58
89
|
useFactory: registry => new MetricsService(assertPrometheusRegistry(registry))
|
|
59
90
|
}, {
|
|
60
91
|
provide: METER_PROVIDER,
|
|
@@ -62,8 +93,8 @@ export class MetricsModule {
|
|
|
62
93
|
useFactory: registry => new PrometheusMeterProvider(assertPrometheusRegistry(registry))
|
|
63
94
|
}, {
|
|
64
95
|
provide: platformTelemetryToken,
|
|
65
|
-
inject: [registryToken],
|
|
66
|
-
useFactory: registry => new RuntimePlatformTelemetry(assertPrometheusRegistry(registry), options.registry ? 'shared' : 'isolated', options.platformTelemetry)
|
|
96
|
+
inject: [registryToken, RUNTIME_CONTAINER],
|
|
97
|
+
useFactory: (registry, container) => new RuntimePlatformTelemetry(assertPrometheusRegistry(registry), assertRuntimeContainer(container), options.registry ? 'shared' : 'isolated', options.platformTelemetry)
|
|
67
98
|
}];
|
|
68
99
|
const controllers = [];
|
|
69
100
|
if (typeof metricsPath === 'string') {
|
|
@@ -84,7 +115,7 @@ export class MetricsModule {
|
|
|
84
115
|
}
|
|
85
116
|
async getMetrics(_input, ctx) {
|
|
86
117
|
ctx.response.setHeader('content-type', this.registry.contentType);
|
|
87
|
-
return this.platformTelemetry.collectMetrics(
|
|
118
|
+
return this.platformTelemetry.collectMetrics(this.registry);
|
|
88
119
|
}
|
|
89
120
|
static {
|
|
90
121
|
_initClass();
|
|
@@ -95,6 +126,8 @@ export class MetricsModule {
|
|
|
95
126
|
class MetricsRuntimeModule {}
|
|
96
127
|
defineModule(MetricsRuntimeModule, {
|
|
97
128
|
controllers,
|
|
129
|
+
exports: [MetricsService, METER_PROVIDER],
|
|
130
|
+
imports,
|
|
98
131
|
middleware,
|
|
99
132
|
providers
|
|
100
133
|
});
|
|
@@ -114,10 +147,10 @@ export class MetricsModule {
|
|
|
114
147
|
const PLATFORM_COMPONENT_LABELS = ['component_id', 'component_kind', 'operation', 'result', 'env', 'instance'];
|
|
115
148
|
const REGISTRY_MODE_LABELS = ['mode'];
|
|
116
149
|
const FRAMEWORK_PLATFORM_GAUGES = new WeakSet();
|
|
150
|
+
const PLATFORM_TELEMETRY_REGISTRY_STATES = new WeakMap();
|
|
117
151
|
const HEALTH_STATUSES = ['healthy', 'unhealthy', 'degraded'];
|
|
118
152
|
const READINESS_STATUSES = ['ready', 'not-ready', 'degraded'];
|
|
119
|
-
const
|
|
120
|
-
const PLATFORM_SHELL_TOKEN_NAMES = new Set([PLATFORM_SHELL_TOKEN_NAME, String(PLATFORM_SHELL)]);
|
|
153
|
+
const PLATFORM_SHELL_TOKEN_NAMES = new Set([String(PLATFORM_SHELL)]);
|
|
121
154
|
function createHttpMetricsMiddleware(registryToken, httpOptions) {
|
|
122
155
|
let _initClass2;
|
|
123
156
|
let _MetricsHttpMiddlewar;
|
|
@@ -138,12 +171,42 @@ function createHttpMetricsMiddleware(registryToken, httpOptions) {
|
|
|
138
171
|
}
|
|
139
172
|
return _MetricsHttpMiddlewar;
|
|
140
173
|
}
|
|
174
|
+
function createHttpCollectorValidationProvider(registryToken, httpOptions) {
|
|
175
|
+
return {
|
|
176
|
+
provide: Symbol('MetricsModule.httpCollectorValidation'),
|
|
177
|
+
inject: [registryToken],
|
|
178
|
+
useFactory: registry => new HttpMetricsMiddleware(assertPrometheusRegistry(registry), httpOptions)
|
|
179
|
+
};
|
|
180
|
+
}
|
|
141
181
|
function assertPrometheusRegistry(value) {
|
|
142
182
|
if (!(value instanceof PrometheusRegistry)) {
|
|
143
183
|
throw new Error('MetricsModule registry provider resolved an invalid Prometheus registry.');
|
|
144
184
|
}
|
|
145
185
|
return value;
|
|
146
186
|
}
|
|
187
|
+
function assertRuntimeContainer(value) {
|
|
188
|
+
if (!isRuntimeContainer(value)) {
|
|
189
|
+
throw new Error('MetricsModule runtime container provider resolved an invalid container.');
|
|
190
|
+
}
|
|
191
|
+
return value;
|
|
192
|
+
}
|
|
193
|
+
function isRuntimeContainer(value) {
|
|
194
|
+
return typeof value === 'object' && value !== null && 'resolve' in value && typeof value.resolve === 'function';
|
|
195
|
+
}
|
|
196
|
+
function getRuntimePlatformTelemetryRegistryState(registry) {
|
|
197
|
+
const existing = PLATFORM_TELEMETRY_REGISTRY_STATES.get(registry);
|
|
198
|
+
if (existing) {
|
|
199
|
+
return existing;
|
|
200
|
+
}
|
|
201
|
+
const state = {
|
|
202
|
+
lastHealthStatuses: new Map(),
|
|
203
|
+
lastReadinessStatuses: new Map(),
|
|
204
|
+
registrations: [],
|
|
205
|
+
scrapeChain: Promise.resolve()
|
|
206
|
+
};
|
|
207
|
+
PLATFORM_TELEMETRY_REGISTRY_STATES.set(registry, state);
|
|
208
|
+
return state;
|
|
209
|
+
}
|
|
147
210
|
function toReadinessValue(status) {
|
|
148
211
|
return status === 'ready' ? 1 : 0;
|
|
149
212
|
}
|
|
@@ -179,12 +242,13 @@ class RuntimePlatformTelemetry {
|
|
|
179
242
|
readinessGauge;
|
|
180
243
|
healthGauge;
|
|
181
244
|
registryModeGauge;
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
245
|
+
telemetryState;
|
|
246
|
+
constructor(registry, container, registryMode, labels = {}) {
|
|
247
|
+
this.registry = registry;
|
|
248
|
+
this.container = container;
|
|
186
249
|
this.registryMode = registryMode;
|
|
187
250
|
this.labels = labels;
|
|
251
|
+
this.telemetryState = getRuntimePlatformTelemetryRegistryState(registry);
|
|
188
252
|
this.readinessGauge = getOrCreateGauge(registry, {
|
|
189
253
|
help: 'Runtime platform component readiness from shared platform snapshot semantics.',
|
|
190
254
|
labelNames: PLATFORM_COMPONENT_LABELS,
|
|
@@ -201,22 +265,54 @@ class RuntimePlatformTelemetry {
|
|
|
201
265
|
name: 'fluo_metrics_registry_mode'
|
|
202
266
|
});
|
|
203
267
|
this.registryModeGauge.labels(this.registryMode).set(1);
|
|
268
|
+
this.installRegistryRefresh();
|
|
204
269
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
await this.refresh(ctx);
|
|
208
|
-
return registry.metrics();
|
|
209
|
-
});
|
|
210
|
-
this.scrapeChain = collect.then(() => undefined, () => undefined);
|
|
211
|
-
return collect;
|
|
270
|
+
collectMetrics(registry) {
|
|
271
|
+
return registry.metrics();
|
|
212
272
|
}
|
|
213
|
-
|
|
214
|
-
const
|
|
215
|
-
if (
|
|
216
|
-
this.
|
|
273
|
+
onModuleDestroy() {
|
|
274
|
+
const registrationIndex = this.telemetryState.registrations.lastIndexOf(this);
|
|
275
|
+
if (registrationIndex >= 0) {
|
|
276
|
+
this.telemetryState.registrations.splice(registrationIndex, 1);
|
|
277
|
+
}
|
|
278
|
+
if (this.telemetryState.registrations.length > 0) {
|
|
217
279
|
return;
|
|
218
280
|
}
|
|
219
|
-
const
|
|
281
|
+
const originalMetrics = this.telemetryState.originalMetrics;
|
|
282
|
+
if (originalMetrics) {
|
|
283
|
+
this.registry.metrics = originalMetrics;
|
|
284
|
+
this.telemetryState.originalMetrics = undefined;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
installRegistryRefresh() {
|
|
288
|
+
this.telemetryState.registrations.push(this);
|
|
289
|
+
if (this.telemetryState.originalMetrics) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const registry = this.registry;
|
|
293
|
+
const telemetryState = this.telemetryState;
|
|
294
|
+
const originalMetrics = registry.metrics;
|
|
295
|
+
telemetryState.originalMetrics = originalMetrics;
|
|
296
|
+
registry.metrics = async () => {
|
|
297
|
+
const activeRegistration = telemetryState.registrations.at(-1);
|
|
298
|
+
await activeRegistration?.refresh();
|
|
299
|
+
return await originalMetrics.call(registry);
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
async refresh() {
|
|
303
|
+
const collect = this.telemetryState.scrapeChain.then(async () => {
|
|
304
|
+
const platformShell = await this.resolvePlatformShell();
|
|
305
|
+
if (!platformShell) {
|
|
306
|
+
this.clearPlatformTelemetry();
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const snapshot = await platformShell.snapshot();
|
|
310
|
+
this.syncSnapshot(snapshot);
|
|
311
|
+
});
|
|
312
|
+
this.telemetryState.scrapeChain = collect.then(() => undefined, () => undefined);
|
|
313
|
+
await collect;
|
|
314
|
+
}
|
|
315
|
+
syncSnapshot(snapshot) {
|
|
220
316
|
const env = this.labels?.env ?? 'unknown';
|
|
221
317
|
const instance = this.labels?.instance ?? 'local';
|
|
222
318
|
const components = [{
|
|
@@ -231,21 +327,17 @@ class RuntimePlatformTelemetry {
|
|
|
231
327
|
readiness: component.readiness.status
|
|
232
328
|
}))];
|
|
233
329
|
this.syncGaugeStatuses({
|
|
234
|
-
currentStatuses:
|
|
235
|
-
env,
|
|
330
|
+
currentStatuses: this.toComponentStatusMap(components, env, instance, component => component.health),
|
|
236
331
|
gauge: this.healthGauge,
|
|
237
|
-
|
|
238
|
-
lastStatuses: this.lastHealthStatuses,
|
|
332
|
+
lastStatuses: this.telemetryState.lastHealthStatuses,
|
|
239
333
|
operation: 'health',
|
|
240
334
|
statuses: HEALTH_STATUSES,
|
|
241
335
|
toMetricValue: toHealthValue
|
|
242
336
|
});
|
|
243
337
|
this.syncGaugeStatuses({
|
|
244
|
-
currentStatuses:
|
|
245
|
-
env,
|
|
338
|
+
currentStatuses: this.toComponentStatusMap(components, env, instance, component => component.readiness),
|
|
246
339
|
gauge: this.readinessGauge,
|
|
247
|
-
|
|
248
|
-
lastStatuses: this.lastReadinessStatuses,
|
|
340
|
+
lastStatuses: this.telemetryState.lastReadinessStatuses,
|
|
249
341
|
operation: 'readiness',
|
|
250
342
|
statuses: READINESS_STATUSES,
|
|
251
343
|
toMetricValue: toReadinessValue
|
|
@@ -253,32 +345,30 @@ class RuntimePlatformTelemetry {
|
|
|
253
345
|
}
|
|
254
346
|
clearPlatformTelemetry() {
|
|
255
347
|
this.clearGaugeStatuses({
|
|
256
|
-
env: this.labels?.env ?? 'unknown',
|
|
257
348
|
gauge: this.healthGauge,
|
|
258
|
-
|
|
259
|
-
lastStatuses: this.lastHealthStatuses,
|
|
349
|
+
lastStatuses: this.telemetryState.lastHealthStatuses,
|
|
260
350
|
operation: 'health',
|
|
261
351
|
statuses: HEALTH_STATUSES
|
|
262
352
|
});
|
|
263
353
|
this.clearGaugeStatuses({
|
|
264
|
-
env: this.labels?.env ?? 'unknown',
|
|
265
354
|
gauge: this.readinessGauge,
|
|
266
|
-
|
|
267
|
-
lastStatuses: this.lastReadinessStatuses,
|
|
355
|
+
lastStatuses: this.telemetryState.lastReadinessStatuses,
|
|
268
356
|
operation: 'readiness',
|
|
269
357
|
statuses: READINESS_STATUSES
|
|
270
358
|
});
|
|
271
359
|
}
|
|
272
360
|
clearGaugeStatuses({
|
|
273
|
-
env,
|
|
274
361
|
gauge,
|
|
275
|
-
instance,
|
|
276
362
|
lastStatuses,
|
|
277
363
|
operation,
|
|
278
364
|
statuses
|
|
279
365
|
}) {
|
|
280
|
-
for (const
|
|
281
|
-
|
|
366
|
+
for (const {
|
|
367
|
+
componentId,
|
|
368
|
+
componentKind,
|
|
369
|
+
env,
|
|
370
|
+
instance
|
|
371
|
+
} of this.componentStatusEntries(lastStatuses)) {
|
|
282
372
|
for (const status of statuses) {
|
|
283
373
|
gauge.remove(componentId, componentKind, operation, status, env, instance);
|
|
284
374
|
}
|
|
@@ -287,20 +377,23 @@ class RuntimePlatformTelemetry {
|
|
|
287
377
|
}
|
|
288
378
|
syncGaugeStatuses({
|
|
289
379
|
currentStatuses,
|
|
290
|
-
env,
|
|
291
380
|
gauge,
|
|
292
|
-
instance,
|
|
293
381
|
lastStatuses,
|
|
294
382
|
operation,
|
|
295
383
|
statuses,
|
|
296
384
|
toMetricValue
|
|
297
385
|
}) {
|
|
298
|
-
for (const
|
|
299
|
-
|
|
386
|
+
for (const {
|
|
387
|
+
componentId,
|
|
388
|
+
componentKind,
|
|
389
|
+
env,
|
|
390
|
+
instance,
|
|
391
|
+
status: previousStatus
|
|
392
|
+
} of this.componentStatusEntries(lastStatuses)) {
|
|
393
|
+
const nextStatus = this.getComponentStatus(currentStatuses, env, instance, componentId, componentKind);
|
|
300
394
|
if (nextStatus === previousStatus) {
|
|
301
395
|
continue;
|
|
302
396
|
}
|
|
303
|
-
const [componentId, componentKind] = this.fromComponentKey(componentKey);
|
|
304
397
|
for (const status of statuses) {
|
|
305
398
|
if (status !== previousStatus) {
|
|
306
399
|
continue;
|
|
@@ -308,41 +401,100 @@ class RuntimePlatformTelemetry {
|
|
|
308
401
|
gauge.remove(componentId, componentKind, operation, status, env, instance);
|
|
309
402
|
}
|
|
310
403
|
}
|
|
311
|
-
for (const
|
|
312
|
-
|
|
404
|
+
for (const {
|
|
405
|
+
componentId,
|
|
406
|
+
componentKind,
|
|
407
|
+
env,
|
|
408
|
+
instance,
|
|
409
|
+
status: currentStatus
|
|
410
|
+
} of this.componentStatusEntries(currentStatuses)) {
|
|
313
411
|
gauge.labels(componentId, componentKind, operation, currentStatus, env, instance).set(toMetricValue(currentStatus));
|
|
314
412
|
}
|
|
315
413
|
lastStatuses.clear();
|
|
316
|
-
for (const
|
|
317
|
-
|
|
414
|
+
for (const {
|
|
415
|
+
componentId,
|
|
416
|
+
componentKind,
|
|
417
|
+
env,
|
|
418
|
+
instance,
|
|
419
|
+
status: currentStatus
|
|
420
|
+
} of this.componentStatusEntries(currentStatuses)) {
|
|
421
|
+
this.setComponentStatus(lastStatuses, env, instance, componentId, componentKind, currentStatus);
|
|
318
422
|
}
|
|
319
423
|
}
|
|
320
|
-
|
|
321
|
-
const
|
|
322
|
-
|
|
424
|
+
toComponentStatusMap(components, env, instance, getStatus) {
|
|
425
|
+
const statuses = new Map();
|
|
426
|
+
for (const component of components) {
|
|
427
|
+
this.setComponentStatus(statuses, env, instance, component.id, component.kind, getStatus(component));
|
|
428
|
+
}
|
|
429
|
+
return statuses;
|
|
323
430
|
}
|
|
324
|
-
|
|
325
|
-
|
|
431
|
+
setComponentStatus(statuses, env, instance, componentId, componentKind, status) {
|
|
432
|
+
let statusByInstance = statuses.get(env);
|
|
433
|
+
if (!statusByInstance) {
|
|
434
|
+
statusByInstance = new Map();
|
|
435
|
+
statuses.set(env, statusByInstance);
|
|
436
|
+
}
|
|
437
|
+
let statusByComponent = statusByInstance.get(instance);
|
|
438
|
+
if (!statusByComponent) {
|
|
439
|
+
statusByComponent = new Map();
|
|
440
|
+
statusByInstance.set(instance, statusByComponent);
|
|
441
|
+
}
|
|
442
|
+
let statusByKind = statusByComponent.get(componentId);
|
|
443
|
+
if (!statusByKind) {
|
|
444
|
+
statusByKind = new Map();
|
|
445
|
+
statusByComponent.set(componentId, statusByKind);
|
|
446
|
+
}
|
|
447
|
+
statusByKind.set(componentKind, status);
|
|
448
|
+
}
|
|
449
|
+
getComponentStatus(statuses, env, instance, componentId, componentKind) {
|
|
450
|
+
return statuses.get(env)?.get(instance)?.get(componentId)?.get(componentKind);
|
|
451
|
+
}
|
|
452
|
+
*componentStatusEntries(statuses) {
|
|
453
|
+
for (const [env, statusByInstance] of statuses) {
|
|
454
|
+
for (const [instance, statusByComponent] of statusByInstance) {
|
|
455
|
+
for (const [componentId, statusByKind] of statusByComponent) {
|
|
456
|
+
for (const [componentKind, status] of statusByKind) {
|
|
457
|
+
yield {
|
|
458
|
+
componentId,
|
|
459
|
+
componentKind,
|
|
460
|
+
env,
|
|
461
|
+
instance,
|
|
462
|
+
status
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
326
468
|
}
|
|
327
|
-
async resolvePlatformShell(
|
|
469
|
+
async resolvePlatformShell() {
|
|
470
|
+
const hasPlatformShell = hasContainerToken(this.container, PLATFORM_SHELL);
|
|
471
|
+
if (hasPlatformShell === false) {
|
|
472
|
+
return undefined;
|
|
473
|
+
}
|
|
328
474
|
try {
|
|
329
|
-
return await
|
|
475
|
+
return await this.container.resolve(PLATFORM_SHELL);
|
|
330
476
|
} catch (error) {
|
|
331
|
-
if (isMissingPlatformShellResolutionError(error)) {
|
|
477
|
+
if (hasPlatformShell !== true && isMissingPlatformShellResolutionError(error)) {
|
|
332
478
|
return undefined;
|
|
333
479
|
}
|
|
334
480
|
throw error;
|
|
335
481
|
}
|
|
336
482
|
}
|
|
337
483
|
}
|
|
484
|
+
function hasContainerToken(container, token) {
|
|
485
|
+
const has = container.has;
|
|
486
|
+
if (typeof has !== 'function') {
|
|
487
|
+
return undefined;
|
|
488
|
+
}
|
|
489
|
+
return has.call(container, token);
|
|
490
|
+
}
|
|
338
491
|
function isMissingPlatformShellResolutionError(error) {
|
|
339
492
|
if (!(error instanceof ContainerResolutionError)) {
|
|
340
493
|
return false;
|
|
341
494
|
}
|
|
342
495
|
const containerError = error;
|
|
343
496
|
const token = typeof containerError.meta?.token === 'string' ? containerError.meta.token : undefined;
|
|
344
|
-
|
|
345
|
-
return token !== undefined && PLATFORM_SHELL_TOKEN_NAMES.has(token) && hint === 'Ensure the provider is registered in a module\'s providers array, or that the module exporting it is imported by the consuming module.';
|
|
497
|
+
return token !== undefined && PLATFORM_SHELL_TOKEN_NAMES.has(token);
|
|
346
498
|
}
|
|
347
499
|
function resolveHttpOptions(http) {
|
|
348
500
|
if (!http) {
|
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"monitoring",
|
|
9
9
|
"observability"
|
|
10
10
|
],
|
|
11
|
-
"version": "
|
|
11
|
+
"version": "2.0.0",
|
|
12
12
|
"private": false,
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"repository": {
|
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"prom-client": "^15.1.3",
|
|
39
|
-
"@fluojs/core": "^1.0
|
|
40
|
-
"@fluojs/
|
|
41
|
-
"@fluojs/
|
|
42
|
-
"@fluojs/runtime": "^
|
|
39
|
+
"@fluojs/core": "^1.1.0",
|
|
40
|
+
"@fluojs/di": "^2.0.0",
|
|
41
|
+
"@fluojs/http": "^2.0.1",
|
|
42
|
+
"@fluojs/runtime": "^2.0.1"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/node": "^22.0.0",
|