@fluojs/metrics 1.0.0-beta.2 → 1.0.0-beta.4
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 +54 -53
- package/README.md +29 -9
- package/dist/http-metrics-middleware.d.ts +1 -1
- package/dist/http-metrics-middleware.d.ts.map +1 -1
- package/dist/http-metrics-middleware.js +38 -3
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/metrics-module.js +18 -3
- package/package.json +4 -4
package/README.ko.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<p><a href="./README.md"><kbd>English</kbd></a> <strong><kbd>한국어</kbd></strong></p>
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
HTTP metric과 platform telemetry를 포함해 fluo 애플리케이션을 위한 Prometheus metric을 노출합니다.
|
|
6
6
|
|
|
7
7
|
## 목차
|
|
8
8
|
|
|
@@ -22,50 +22,54 @@ pnpm add @fluojs/metrics
|
|
|
22
22
|
|
|
23
23
|
## 사용 시점
|
|
24
24
|
|
|
25
|
-
- Prometheus
|
|
26
|
-
-
|
|
27
|
-
-
|
|
25
|
+
- 애플리케이션이 Prometheus-compatible scraping을 위한 `/metrics` endpoint를 노출해야 할 때
|
|
26
|
+
- 손으로 middleware를 작성하지 않고 HTTP latency와 request count를 계측해야 할 때
|
|
27
|
+
- application telemetry를 fluo readiness 및 health state와 맞춰야 할 때
|
|
28
28
|
|
|
29
29
|
## 빠른 시작
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
```typescript
|
|
34
|
-
import { Module } from '@fluojs/core';
|
|
31
|
+
```ts
|
|
35
32
|
import { MetricsModule } from '@fluojs/metrics';
|
|
33
|
+
import { Module } from '@fluojs/core';
|
|
36
34
|
|
|
37
35
|
@Module({
|
|
38
|
-
imports: [
|
|
39
|
-
MetricsModule.forRoot(),
|
|
40
|
-
],
|
|
36
|
+
imports: [MetricsModule.forRoot({ http: true })],
|
|
41
37
|
})
|
|
42
38
|
class AppModule {}
|
|
43
|
-
|
|
44
|
-
// GET /metrics → Prometheus 텍스트 형식
|
|
45
39
|
```
|
|
46
40
|
|
|
47
|
-
`MetricsModule.forRoot()`는 기본적으로 `GET /metrics`를 노출합니다. 운영 환경에서는
|
|
41
|
+
`MetricsModule.forRoot()`는 기본적으로 `GET /metrics`를 노출합니다. HTTP request instrumentation middleware를 설치하려면 `http: true` 또는 `http` option object를 전달하세요. HTTP 계측이 활성화되면 request total, error count, request duration을 기록합니다. 운영 환경에서는 scrape endpoint boundary를 명시적으로 다루세요. platform-level proxy가 준비될 때까지 `path: false`로 끄거나 dedicated endpoint middleware를 연결할 수 있습니다.
|
|
48
42
|
|
|
49
|
-
|
|
43
|
+
Scrape endpoint는 active `prom-client` Registry output을 해당 Registry의 Prometheus content type으로 반환합니다. `MetricsModule.forRoot()`는 `registry` option을 전달하지 않는 한 격리된 Registry를 생성합니다. framework metric과 application-defined metric이 하나의 scrape surface를 의도적으로 공유해야 할 때만 shared `Registry`를 전달하세요.
|
|
50
44
|
|
|
51
|
-
|
|
45
|
+
## 공통 패턴
|
|
52
46
|
|
|
53
|
-
|
|
47
|
+
### HTTP path label 정규화
|
|
54
48
|
|
|
55
|
-
```
|
|
49
|
+
```ts
|
|
56
50
|
MetricsModule.forRoot({
|
|
57
51
|
http: {
|
|
58
|
-
pathLabelMode: 'template',
|
|
52
|
+
pathLabelMode: 'template',
|
|
59
53
|
unknownPathLabel: 'UNKNOWN',
|
|
60
54
|
},
|
|
61
|
-
})
|
|
55
|
+
});
|
|
62
56
|
```
|
|
63
57
|
|
|
64
|
-
`pathLabelMode: 'raw'`는
|
|
58
|
+
`pathLabelMode: 'raw'`는 안전하지 않은 opt-in입니다. 경로 공간이 유한하다는 것을 증명할 수 있을 때만 `allowUnsafeRawPathLabelMode: true`와 함께 사용하세요.
|
|
59
|
+
|
|
60
|
+
### Custom path label 정규화
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
MetricsModule.forRoot({
|
|
64
|
+
http: {
|
|
65
|
+
pathLabelNormalizer: ({ path }) => (path.startsWith('/api/v1') ? '/api/v1/:resource' : path),
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
```
|
|
65
69
|
|
|
66
70
|
### 메트릭 엔드포인트 보호 또는 비활성화
|
|
67
71
|
|
|
68
|
-
```
|
|
72
|
+
```ts
|
|
69
73
|
import { ForbiddenException, type MiddlewareContext, type Next } from '@fluojs/http';
|
|
70
74
|
|
|
71
75
|
class MetricsTokenMiddleware {
|
|
@@ -87,57 +91,51 @@ MetricsModule.forRoot({
|
|
|
87
91
|
});
|
|
88
92
|
```
|
|
89
93
|
|
|
90
|
-
|
|
94
|
+
`endpointMiddleware`는 class-based `@fluojs/http` middleware constructor를 받으며 metrics scrape endpoint에만 바인딩됩니다. middleware function이나 global middleware declaration은 이 option의 패키지 계약이 아닙니다.
|
|
91
95
|
|
|
92
|
-
|
|
93
|
-
MetricsModule.forRoot({
|
|
94
|
-
http: {
|
|
95
|
-
pathLabelNormalizer: ({ path }) => (path.startsWith('/api/v1') ? '/api/v1/:resource' : path),
|
|
96
|
-
},
|
|
97
|
-
})
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
### 공유 Registry (권장)
|
|
101
|
-
|
|
102
|
-
커스텀 애플리케이션 메트릭을 프레임워크가 제공하는 메트릭과 하나의 `/metrics` 엔드포인트에서 통합하려면, 공유 `Registry` 인스턴스를 전달하세요.
|
|
96
|
+
### Framework metric과 app metric이 하나의 registry를 공유하기
|
|
103
97
|
|
|
104
|
-
```
|
|
105
|
-
import {
|
|
106
|
-
import { MetricsModule
|
|
98
|
+
```ts
|
|
99
|
+
import { Counter, Registry } from 'prom-client';
|
|
100
|
+
import { MetricsModule } from '@fluojs/metrics';
|
|
107
101
|
|
|
108
|
-
const
|
|
102
|
+
const registry = new Registry();
|
|
109
103
|
|
|
110
|
-
|
|
111
|
-
const ordersTotal = new Counter({
|
|
104
|
+
new Counter({
|
|
112
105
|
name: 'orders_total',
|
|
113
|
-
help: '
|
|
114
|
-
registers: [
|
|
106
|
+
help: 'Total orders processed',
|
|
107
|
+
registers: [registry],
|
|
115
108
|
});
|
|
116
109
|
|
|
117
110
|
@Module({
|
|
118
|
-
imports: [
|
|
119
|
-
MetricsModule.forRoot({ registry: sharedRegistry }),
|
|
120
|
-
],
|
|
111
|
+
imports: [MetricsModule.forRoot({ http: true, registry })],
|
|
121
112
|
})
|
|
122
113
|
class AppModule {}
|
|
123
114
|
```
|
|
124
115
|
|
|
116
|
+
여러 `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 규칙대로 계속 빠르게 실패합니다.
|
|
117
|
+
|
|
118
|
+
### 중복 메트릭 이름은 계속 빠르게 실패합니다
|
|
119
|
+
|
|
120
|
+
Prometheus 메트릭 이름은 하나의 Registry 안에서 고유해야 합니다. 공유 Registry 모드는 애플리케이션 메트릭의 중복 이름을 조용히 덮어쓰지 않고 이 동작을 유지합니다. 애플리케이션이 내장 HTTP collector 또는 플랫폼 텔레메트리 Gauge 이름을 미리 등록한 경우, `MetricsModule.forRoot()`는 app-owned collector를 재사용하지 않고 충돌을 거부합니다.
|
|
121
|
+
|
|
125
122
|
### 런타임 플랫폼 텔레메트리
|
|
126
123
|
|
|
127
124
|
이 모듈은 플랫폼 셸 및 등록된 컴포넌트의 내부 상태를 반영하는 fluo 전용 Gauge를 자동으로 생성합니다.
|
|
128
125
|
|
|
129
126
|
- `fluo_component_ready`: 준비 완료 시 1, 아닐 시 0.
|
|
130
127
|
- `fluo_component_health`: 정상 상태 시 1, 아닐 시 0.
|
|
128
|
+
- `fluo_metrics_registry_mode`: active registry mode가 `isolated`인지 `shared`인지 나타냅니다.
|
|
131
129
|
|
|
132
130
|
이 데이터는 매 스크레이프 시점에 `PLATFORM_SHELL`을 쿼리하여 갱신됩니다. 초기화 시 환경 라벨을 제공할 수 있습니다.
|
|
133
131
|
|
|
134
|
-
```
|
|
132
|
+
```ts
|
|
135
133
|
MetricsModule.forRoot({
|
|
136
134
|
platformTelemetry: {
|
|
137
135
|
env: 'production',
|
|
138
136
|
instance: 'web-01',
|
|
139
137
|
},
|
|
140
|
-
})
|
|
138
|
+
});
|
|
141
139
|
```
|
|
142
140
|
|
|
143
141
|
### 런타임 플랫폼 텔레메트리 스크레이프 계약
|
|
@@ -145,17 +143,17 @@ MetricsModule.forRoot({
|
|
|
145
143
|
플랫폼 텔레메트리는 매 `/metrics` 스크레이프마다 `PLATFORM_SHELL`을 resolve하여 `fluo_component_ready`와 `fluo_component_health`를 갱신합니다.
|
|
146
144
|
|
|
147
145
|
- `PLATFORM_SHELL` 등록 자체가 빠진 경우에는 스크레이프가 계속 성공하고 플랫폼 텔레메트리 시리즈만 생략됩니다.
|
|
148
|
-
-
|
|
146
|
+
- 직전 성공 스크레이프에서 플랫폼 텔레메트리를 노출한 뒤 `PLATFORM_SHELL`을 사용할 수 없게 되면, stale `fluo_component_ready` 및 `fluo_component_health` 시리즈를 제거한 뒤 메트릭을 반환합니다.
|
|
149
147
|
- 그 외의 `PLATFORM_SHELL` resolve 실패는 조용히 삼키지 않고 스크레이프 실패로 그대로 드러납니다.
|
|
150
148
|
|
|
151
149
|
### 기본 프로세스/Node 메트릭 비활성화
|
|
152
150
|
|
|
153
151
|
`defaultMetrics`의 기본값은 `true`입니다. 따라서 별도 설정이 없으면 Registry마다 Prometheus 기본 프로세스/Node.js collector를 한 번 등록합니다. 최소 Registry만 노출하고 싶다면 비활성화하세요.
|
|
154
152
|
|
|
155
|
-
```
|
|
153
|
+
```ts
|
|
156
154
|
MetricsModule.forRoot({
|
|
157
155
|
defaultMetrics: false,
|
|
158
|
-
})
|
|
156
|
+
});
|
|
159
157
|
```
|
|
160
158
|
|
|
161
159
|
## 공개 API
|
|
@@ -165,17 +163,20 @@ MetricsModule.forRoot({
|
|
|
165
163
|
- `METER_PROVIDER` (Token)
|
|
166
164
|
- `PrometheusMeterProvider`
|
|
167
165
|
- `HttpMetricsMiddleware` 및 HTTP path-label 옵션 타입
|
|
166
|
+
- `provider`(현재는 `'prometheus'`만 지원)와 endpoint `middleware`를 포함한 module option
|
|
168
167
|
- `prom-client`의 `Registry`
|
|
169
168
|
|
|
170
169
|
### 운영 기본값
|
|
171
170
|
|
|
172
171
|
- `path`의 기본값은 `'/metrics'`이며, `path: false`로 스크레이프 엔드포인트를 완전히 비활성화할 수 있습니다.
|
|
172
|
+
- scrape response는 active Registry의 Prometheus content type과 Registry contents를 사용합니다.
|
|
173
173
|
- `defaultMetrics`의 기본값은 `true`이며, `defaultMetrics: false`로 해당 Registry의 Prometheus 기본 프로세스/Node.js collector를 끌 수 있습니다.
|
|
174
|
-
- `endpointMiddleware`는
|
|
175
|
-
- HTTP 메트릭은 기본적으로 템플릿 기반 경로 라벨 정규화를 사용합니다.
|
|
174
|
+
- `endpointMiddleware`는 class-based route-scoped middleware를 스크레이프 엔드포인트에만 바인딩합니다.
|
|
175
|
+
- HTTP 메트릭은 `http: true` 또는 `http` 옵션 객체를 전달한 경우에만 설치되며, 설치된 뒤에는 기본적으로 템플릿 기반 경로 라벨 정규화를 사용합니다.
|
|
176
|
+
- 내장 HTTP collector와 플랫폼 텔레메트리 Gauge는 같은 Registry를 공유하는 모듈 인스턴스 사이에서 framework-owned이고 예상 label schema를 가진 경우에만 재사용되며, 커스텀 애플리케이션 메트릭 이름 충돌은 Prometheus의 중복 이름 실패 동작을 유지합니다.
|
|
176
177
|
- raw path 라벨은 `allowUnsafeRawPathLabelMode: true`를 명시한 bounded internal route에서만 사용해야 합니다.
|
|
177
178
|
- 플랫폼 텔레메트리는 `PLATFORM_SHELL`이 실제로 누락된 경우에만 생략되며, 그 외 resolve 실패는 스크레이프를 실패시킵니다.
|
|
178
|
-
-
|
|
179
|
+
- 직전 성공 스크레이프에서 노출된 플랫폼 텔레메트리 시리즈는 `PLATFORM_SHELL`을 사용할 수 없게 된 스크레이프에서 제거됩니다.
|
|
179
180
|
|
|
180
181
|
## 관련 패키지
|
|
181
182
|
|
package/README.md
CHANGED
|
@@ -33,12 +33,14 @@ import { MetricsModule } from '@fluojs/metrics';
|
|
|
33
33
|
import { Module } from '@fluojs/core';
|
|
34
34
|
|
|
35
35
|
@Module({
|
|
36
|
-
imports: [MetricsModule.forRoot()],
|
|
36
|
+
imports: [MetricsModule.forRoot({ http: true })],
|
|
37
37
|
})
|
|
38
38
|
class AppModule {}
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
`MetricsModule.forRoot()`
|
|
41
|
+
`MetricsModule.forRoot()` exposes `GET /metrics` by default. Pass `http: true` (or an `http` options object) when you want the module to install HTTP request instrumentation middleware. When HTTP instrumentation is enabled, the module records request totals, error counts, and request duration. For production deployments, make the scrape endpoint boundary explicit: either disable it with `path: false` until a platform-level proxy is in place, or attach dedicated endpoint middleware.
|
|
42
|
+
|
|
43
|
+
The scrape endpoint returns the active `prom-client` registry output with that registry's Prometheus content type. `MetricsModule.forRoot()` creates an isolated registry unless you pass a `registry` option; pass a shared `Registry` only when framework metrics and application-defined metrics intentionally share one scrape surface.
|
|
42
44
|
|
|
43
45
|
## Common Patterns
|
|
44
46
|
|
|
@@ -53,7 +55,17 @@ MetricsModule.forRoot({
|
|
|
53
55
|
});
|
|
54
56
|
```
|
|
55
57
|
|
|
56
|
-
`pathLabelMode: 'raw'` is
|
|
58
|
+
`pathLabelMode: 'raw'` is an unsafe opt-in. You must pass `allowUnsafeRawPathLabelMode: true` only when you can prove the path space is bounded.
|
|
59
|
+
|
|
60
|
+
### Custom path label normalization
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
MetricsModule.forRoot({
|
|
64
|
+
http: {
|
|
65
|
+
pathLabelNormalizer: ({ path }) => (path.startsWith('/api/v1') ? '/api/v1/:resource' : path),
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
```
|
|
57
69
|
|
|
58
70
|
### Protect or disable the metrics endpoint
|
|
59
71
|
|
|
@@ -79,6 +91,8 @@ MetricsModule.forRoot({
|
|
|
79
91
|
});
|
|
80
92
|
```
|
|
81
93
|
|
|
94
|
+
`endpointMiddleware` accepts class-based `@fluojs/http` middleware constructors and binds them only to the metrics scrape endpoint. Middleware functions or global middleware declarations are not the package contract for this option.
|
|
95
|
+
|
|
82
96
|
### Share one registry for framework and app metrics
|
|
83
97
|
|
|
84
98
|
```ts
|
|
@@ -94,14 +108,16 @@ new Counter({
|
|
|
94
108
|
});
|
|
95
109
|
|
|
96
110
|
@Module({
|
|
97
|
-
imports: [MetricsModule.forRoot({ registry })],
|
|
111
|
+
imports: [MetricsModule.forRoot({ http: true, registry })],
|
|
98
112
|
})
|
|
99
113
|
class AppModule {}
|
|
100
114
|
```
|
|
101
115
|
|
|
116
|
+
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.
|
|
117
|
+
|
|
102
118
|
### Duplicate metric names still fail fast
|
|
103
119
|
|
|
104
|
-
Prometheus metric names must stay unique inside a registry. Shared-registry mode keeps that behavior intact instead of silently shadowing metrics.
|
|
120
|
+
Prometheus metric names must stay unique inside a registry. Shared-registry mode keeps that behavior intact instead of silently shadowing metrics. If an application predefines a built-in HTTP collector or platform telemetry gauge name, `MetricsModule.forRoot()` rejects the collision instead of reusing an app-owned collector.
|
|
105
121
|
|
|
106
122
|
### Runtime platform telemetry
|
|
107
123
|
|
|
@@ -109,6 +125,7 @@ The module emits fluo-specific gauges that mirror the platform shell and registe
|
|
|
109
125
|
|
|
110
126
|
- `fluo_component_ready`: `1` when a component is ready, otherwise `0`.
|
|
111
127
|
- `fluo_component_health`: `1` when a component is healthy, otherwise `0`.
|
|
128
|
+
- `fluo_metrics_registry_mode`: `isolated` or `shared` for the active registry mode.
|
|
112
129
|
|
|
113
130
|
The platform snapshot is refreshed during each scrape, and you can attach environment labels up front.
|
|
114
131
|
|
|
@@ -126,7 +143,7 @@ MetricsModule.forRoot({
|
|
|
126
143
|
Platform telemetry refreshes `fluo_component_ready` and `fluo_component_health` on each `/metrics` scrape by resolving `PLATFORM_SHELL`.
|
|
127
144
|
|
|
128
145
|
- If `PLATFORM_SHELL` is not registered, the scrape still succeeds and omits the platform telemetry series.
|
|
129
|
-
- If `PLATFORM_SHELL` becomes unavailable after
|
|
146
|
+
- 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.
|
|
130
147
|
- If resolving `PLATFORM_SHELL` fails for any other reason, the scrape surfaces that failure instead of swallowing it.
|
|
131
148
|
|
|
132
149
|
### Disable default process and Node metrics
|
|
@@ -146,17 +163,20 @@ MetricsModule.forRoot({
|
|
|
146
163
|
- `METER_PROVIDER`
|
|
147
164
|
- `PrometheusMeterProvider`
|
|
148
165
|
- `HttpMetricsMiddleware` and HTTP path-label option types
|
|
166
|
+
- Module options including `provider` (currently only `'prometheus'`) and endpoint `middleware`
|
|
149
167
|
- `Registry` from `prom-client`
|
|
150
168
|
|
|
151
169
|
### Operational defaults
|
|
152
170
|
|
|
153
171
|
- `path` defaults to `'/metrics'`, and `path: false` disables the scrape endpoint entirely.
|
|
172
|
+
- The scrape response uses the active registry's Prometheus content type and registry contents.
|
|
154
173
|
- `defaultMetrics` defaults to `true`, and `defaultMetrics: false` disables Prometheus default process and Node.js collectors for that registry.
|
|
155
|
-
- `endpointMiddleware` binds route-scoped middleware only to the scrape endpoint.
|
|
156
|
-
- HTTP metrics default to template-normalized path labels.
|
|
174
|
+
- `endpointMiddleware` binds class-based route-scoped middleware only to the scrape endpoint.
|
|
175
|
+
- HTTP metrics are installed only when `http: true` or an `http` options object is provided, and then default to template-normalized path labels.
|
|
176
|
+
- Built-in HTTP collectors and platform telemetry gauges are reused when module instances share one registry only if they are framework-owned and have the expected label schema; custom application metric name collisions keep Prometheus' duplicate-name failure behavior.
|
|
157
177
|
- Raw path labels require `allowUnsafeRawPathLabelMode: true` and should stay limited to bounded internal routes.
|
|
158
178
|
- Platform telemetry is omitted only when `PLATFORM_SHELL` is genuinely missing; other resolution failures fail the scrape.
|
|
159
|
-
- Stale platform telemetry series are removed when `PLATFORM_SHELL` becomes unavailable after
|
|
179
|
+
- Stale platform telemetry series are removed when `PLATFORM_SHELL` becomes unavailable after the last successful scrape.
|
|
160
180
|
|
|
161
181
|
## Related Packages
|
|
162
182
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FrameworkRequest, Middleware, MiddlewareContext, Next } from '@fluojs/http';
|
|
2
|
-
import type
|
|
2
|
+
import { type Registry } from 'prom-client';
|
|
3
3
|
/** Strategy used to label request paths in emitted HTTP metrics. */
|
|
4
4
|
export type HttpMetricsPathLabelMode = 'raw' | 'template';
|
|
5
5
|
/** Context passed to a custom path-label normalizer. */
|
|
@@ -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,KAAK,
|
|
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;AAqBhE,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;IA2B1E,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"}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { Counter, Histogram } from 'prom-client';
|
|
1
2
|
import { createPrometheusCounter, createPrometheusHistogram } from './providers/prometheus-metrics-factory.js';
|
|
3
|
+
const FRAMEWORK_HTTP_COUNTERS = new WeakSet();
|
|
4
|
+
const FRAMEWORK_HTTP_HISTOGRAMS = new WeakSet();
|
|
2
5
|
|
|
3
6
|
/** Strategy used to label request paths in emitted HTTP metrics. */
|
|
4
7
|
|
|
@@ -41,17 +44,17 @@ export class HttpMetricsMiddleware {
|
|
|
41
44
|
this.pathLabelMode = options.pathLabelMode ?? 'template';
|
|
42
45
|
this.pathLabelNormalizer = options.pathLabelNormalizer;
|
|
43
46
|
this.unknownPathLabel = options.unknownPathLabel ?? 'UNKNOWN';
|
|
44
|
-
this.requestsTotal =
|
|
47
|
+
this.requestsTotal = getOrCreateHttpCounter(registry, {
|
|
45
48
|
help: 'Total number of HTTP requests',
|
|
46
49
|
labelNames: ['method', 'path', 'status'],
|
|
47
50
|
name: 'http_requests_total'
|
|
48
51
|
});
|
|
49
|
-
this.errorsTotal =
|
|
52
|
+
this.errorsTotal = getOrCreateHttpCounter(registry, {
|
|
50
53
|
help: 'Total number of HTTP error responses (4xx/5xx)',
|
|
51
54
|
labelNames: ['method', 'path', 'status'],
|
|
52
55
|
name: 'http_errors_total'
|
|
53
56
|
});
|
|
54
|
-
this.requestDuration =
|
|
57
|
+
this.requestDuration = getOrCreateHttpHistogram(registry, {
|
|
55
58
|
help: 'HTTP request duration in seconds',
|
|
56
59
|
labelNames: ['method', 'path', 'status'],
|
|
57
60
|
name: 'http_request_duration_seconds'
|
|
@@ -116,6 +119,38 @@ export class HttpMetricsMiddleware {
|
|
|
116
119
|
}
|
|
117
120
|
}
|
|
118
121
|
}
|
|
122
|
+
function getOrCreateHttpCounter(registry, config) {
|
|
123
|
+
const existing = registry.getSingleMetric(config.name);
|
|
124
|
+
if (existing instanceof Counter) {
|
|
125
|
+
if (!FRAMEWORK_HTTP_COUNTERS.has(existing)) {
|
|
126
|
+
throw new Error(`Metric name "${config.name}" is already registered by the application. Built-in HTTP metrics require framework-owned collectors.`);
|
|
127
|
+
}
|
|
128
|
+
return existing;
|
|
129
|
+
}
|
|
130
|
+
const counter = createPrometheusCounter(registry, {
|
|
131
|
+
help: config.help,
|
|
132
|
+
labelNames: [...config.labelNames],
|
|
133
|
+
name: config.name
|
|
134
|
+
});
|
|
135
|
+
FRAMEWORK_HTTP_COUNTERS.add(counter);
|
|
136
|
+
return counter;
|
|
137
|
+
}
|
|
138
|
+
function getOrCreateHttpHistogram(registry, config) {
|
|
139
|
+
const existing = registry.getSingleMetric(config.name);
|
|
140
|
+
if (existing instanceof Histogram) {
|
|
141
|
+
if (!FRAMEWORK_HTTP_HISTOGRAMS.has(existing)) {
|
|
142
|
+
throw new Error(`Metric name "${config.name}" is already registered by the application. Built-in HTTP metrics require framework-owned collectors.`);
|
|
143
|
+
}
|
|
144
|
+
return existing;
|
|
145
|
+
}
|
|
146
|
+
const histogram = createPrometheusHistogram(registry, {
|
|
147
|
+
help: config.help,
|
|
148
|
+
labelNames: [...config.labelNames],
|
|
149
|
+
name: config.name
|
|
150
|
+
});
|
|
151
|
+
FRAMEWORK_HTTP_HISTOGRAMS.add(histogram);
|
|
152
|
+
return histogram;
|
|
153
|
+
}
|
|
119
154
|
function normalizePathToTemplate(path, params) {
|
|
120
155
|
if (!path) {
|
|
121
156
|
return '/';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prometheus registry constructor re-exported for applications that want custom
|
|
3
|
+
* application metrics and Fluo framework metrics to share one scrape endpoint.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* The implementation is `prom-client`'s `Registry`; duplicate metric names still
|
|
7
|
+
* follow Prometheus registry semantics and fail fast.
|
|
8
|
+
*/
|
|
1
9
|
export { Registry } from 'prom-client';
|
|
2
10
|
export * from './metrics-module.js';
|
|
3
11
|
export * from './metrics-service.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,0CAA0C,CAAC;AACzD,cAAc,8BAA8B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,0CAA0C,CAAC;AACzD,cAAc,8BAA8B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prometheus registry constructor re-exported for applications that want custom
|
|
3
|
+
* application metrics and Fluo framework metrics to share one scrape endpoint.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* The implementation is `prom-client`'s `Registry`; duplicate metric names still
|
|
7
|
+
* follow Prometheus registry semantics and fail fast.
|
|
8
|
+
*/
|
|
1
9
|
export { Registry } from 'prom-client';
|
|
2
10
|
export * from './metrics-module.js';
|
|
3
11
|
export * from './metrics-service.js';
|
package/dist/metrics-module.js
CHANGED
|
@@ -98,6 +98,7 @@ export class MetricsModule {
|
|
|
98
98
|
}
|
|
99
99
|
const PLATFORM_COMPONENT_LABELS = ['component_id', 'component_kind', 'operation', 'result', 'env', 'instance'];
|
|
100
100
|
const REGISTRY_MODE_LABELS = ['mode'];
|
|
101
|
+
const FRAMEWORK_PLATFORM_GAUGES = new WeakSet();
|
|
101
102
|
const HEALTH_STATUSES = ['healthy', 'unhealthy', 'degraded'];
|
|
102
103
|
const READINESS_STATUSES = ['ready', 'not-ready', 'degraded'];
|
|
103
104
|
const PLATFORM_SHELL_TOKEN_NAME = 'PLATFORM_SHELL';
|
|
@@ -111,14 +112,27 @@ function toHealthValue(status) {
|
|
|
111
112
|
function getOrCreateGauge(registry, config) {
|
|
112
113
|
const existing = registry.getSingleMetric(config.name);
|
|
113
114
|
if (existing instanceof Gauge) {
|
|
115
|
+
if (!FRAMEWORK_PLATFORM_GAUGES.has(existing)) {
|
|
116
|
+
throw new Error(`Metric name "${config.name}" is already registered by the application. Built-in platform telemetry requires framework-owned gauges.`);
|
|
117
|
+
}
|
|
118
|
+
assertGaugeLabelSchema(existing, config);
|
|
114
119
|
return existing;
|
|
115
120
|
}
|
|
116
|
-
|
|
121
|
+
const gauge = new Gauge({
|
|
117
122
|
help: config.help,
|
|
118
123
|
labelNames: [...config.labelNames],
|
|
119
124
|
name: config.name,
|
|
120
125
|
registers: [registry]
|
|
121
126
|
});
|
|
127
|
+
FRAMEWORK_PLATFORM_GAUGES.add(gauge);
|
|
128
|
+
return gauge;
|
|
129
|
+
}
|
|
130
|
+
function assertGaugeLabelSchema(gauge, config) {
|
|
131
|
+
const registeredLabels = (gauge.labelNames ?? []).join(',');
|
|
132
|
+
const expectedLabels = config.labelNames.join(',');
|
|
133
|
+
if (registeredLabels !== expectedLabels) {
|
|
134
|
+
throw new Error(`Metric name "${config.name}" is already registered with labels [${registeredLabels}]. Built-in platform telemetry requires labels [${expectedLabels}].`);
|
|
135
|
+
}
|
|
122
136
|
}
|
|
123
137
|
class RuntimePlatformTelemetry {
|
|
124
138
|
readinessGauge;
|
|
@@ -285,12 +299,13 @@ function isMissingPlatformShellResolutionError(error) {
|
|
|
285
299
|
return false;
|
|
286
300
|
}
|
|
287
301
|
const containerError = error;
|
|
302
|
+
const message = String(containerError.message ?? '');
|
|
288
303
|
const token = typeof containerError.meta?.['token'] === 'string' ? containerError.meta['token'] : undefined;
|
|
289
304
|
if (token && PLATFORM_SHELL_TOKEN_NAMES.has(token)) {
|
|
290
|
-
return
|
|
305
|
+
return message.startsWith(`No provider registered for token ${token}.`);
|
|
291
306
|
}
|
|
292
307
|
for (const tokenName of PLATFORM_SHELL_TOKEN_NAMES) {
|
|
293
|
-
if (
|
|
308
|
+
if (message.startsWith(`No provider registered for token ${tokenName}.`)) {
|
|
294
309
|
return true;
|
|
295
310
|
}
|
|
296
311
|
}
|
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"monitoring",
|
|
9
9
|
"observability"
|
|
10
10
|
],
|
|
11
|
-
"version": "1.0.0-beta.
|
|
11
|
+
"version": "1.0.0-beta.4",
|
|
12
12
|
"private": false,
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"repository": {
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"prom-client": "^15.1.3",
|
|
39
|
-
"@fluojs/di": "^1.0.0-beta.
|
|
40
|
-
"@fluojs/http": "^1.0.0-beta.
|
|
41
|
-
"@fluojs/runtime": "^1.0.0-beta.
|
|
39
|
+
"@fluojs/di": "^1.0.0-beta.7",
|
|
40
|
+
"@fluojs/http": "^1.0.0-beta.10",
|
|
41
|
+
"@fluojs/runtime": "^1.0.0-beta.12"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/node": "^22.0.0",
|